blob: 8e63cd54544b11081a98f1cae8501317b10bd4cc [file] [log] [blame]
# Copyright 2005-2020 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2
from portage import os
from portage.exception import PortageException
from portage.cache.mappings import ProtectedDict
from portage.localization import _
from portage.util import writemsg
class InvalidModuleName(PortageException):
"""An invalid or unknown module name."""
class ModuleVersionError(PortageException):
"""An incompatible module version"""
class Module:
"""Class to define and hold our plug-in module
@type name: string
@param name: the module name
@type path: the path to the new module
"""
def __init__(self, name, namepath):
"""Some variables initialization"""
self.name = name
self._namepath = namepath
self.kids_names = []
self.kids = {}
self.initialized = self._initialize()
def _initialize(self):
"""Initialize the plug-in module
@rtype: boolean
"""
self.valid = False
try:
mod_name = ".".join([self._namepath, self.name])
self._module = __import__(mod_name, [], [], ["not empty"])
self.valid = True
except ImportError as e:
print("MODULE; failed import", mod_name, " error was:", e)
return False
self.module_spec = self._module.module_spec
for submodule in self.module_spec["provides"]:
kid = self.module_spec["provides"][submodule]
kidname = kid["name"]
try:
kid["module_name"] = ".".join([mod_name, kid["sourcefile"]])
except KeyError:
kid["module_name"] = ".".join([mod_name, self.name])
writemsg(
_(
f"{self.name} module's module_spec is old, missing attribute: "
"'sourcefile'. Backward compatibility may be "
f"removed in the future.\nFile: {self._module.__file__}\n"
)
)
kid["is_imported"] = False
self.kids[kidname] = kid
self.kids_names.append(kidname)
return True
def get_class(self, name):
if not name or name not in self.kids_names:
raise InvalidModuleName(
(
f"Module name '{name}' is invalid or not"
f"part of the module '{self.name}'"
)
)
kid = self.kids[name]
if kid["is_imported"]:
module = kid["instance"]
else:
try:
module = __import__(kid["module_name"], [], [], ["not empty"])
kid["instance"] = module
kid["is_imported"] = True
except ImportError:
raise
mod_class = getattr(module, kid["class"])
return mod_class
class Modules:
"""Dynamic modules system for loading and retrieving any of the
installed emaint modules and/or provided class's
@param path: Path to the "modules" directory
@param namepath: Python import path to the "modules" directory
"""
def __init__(self, path, namepath, compat_versions=None):
self._module_path = path
self._namepath = namepath
self.compat_versions = compat_versions
self.parents = []
self._modules = self._get_all_modules()
self.modules = ProtectedDict(self._modules)
self.module_names = sorted(self._modules)
def _get_all_modules(self):
"""scans the _module_path dir for loadable modules
@rtype: dictionary of module_plugins
"""
module_dir = self._module_path
names = os.listdir(module_dir)
def _a_real_module(entry):
try:
# test for statinfo to ensure it should a real module
# it will bail if it errors
os.lstat(os.path.join(module_dir, entry, "__init__.py"))
except EnvironmentError:
return False
return True
# The importables list cannot be a generator.
# If it was a generator, it would be consumed by self.parents.extend()
# and the following for loop wouldn't have anything to iterate with.
importables = [
entry
for entry in names
if not entry.startswith("__") and _a_real_module(entry)
]
self.parents.extend(importables)
kids = {}
for entry in importables:
new_module = Module(entry, self._namepath)
self._check_compat(new_module)
for module_name in new_module.kids:
kid = new_module.kids[module_name]
kid["parent"] = new_module
kids[kid["name"]] = kid
return kids
def get_module_names(self):
"""Convenience function to return the list of installed modules
available
@rtype: list
@return: the installed module names available
"""
return self.module_names
def get_class(self, modname):
"""Retrieves a module class desired
@type modname: string
@param modname: the module class name
"""
if modname and modname in self.module_names:
mod = self._modules[modname]["parent"].get_class(modname)
else:
raise InvalidModuleName(f"Module name '{modname}' is invalid or not found")
return mod
def get_description(self, modname):
"""Retrieves the module class decription
@type modname: string
@param modname: the module class name
@type string
@return: the modules class decription
"""
if modname and modname in self.module_names:
mod = self._modules[modname]["description"]
else:
raise InvalidModuleName(f"Module name '{modname}' is invalid or not found")
return mod
def get_functions(self, modname):
"""Retrieves the module class exported function names
@type modname: string
@param modname: the module class name
@type list
@return: the modules class exported function names
"""
if modname and modname in self.module_names:
mod = self._modules[modname]["functions"]
else:
raise InvalidModuleName(f"Module name '{modname}' is invalid or not found")
return mod
def get_func_descriptions(self, modname):
"""Retrieves the module class exported functions descriptions
@type modname: string
@param modname: the module class name
@type dictionary
@return: the modules class exported functions descriptions
"""
if modname and modname in self.module_names:
desc = self._modules[modname]["func_desc"]
else:
raise InvalidModuleName(f"Module name '{modname}' is invalid or not found")
return desc
def get_opt_descriptions(self, modname):
"""Retrieves the module class exported options descriptions
@type modname: string
@param modname: the module class name
@type dictionary
@return: the modules class exported options descriptions
"""
if modname and modname in self.module_names:
desc = self._modules[modname].get("opt_desc")
else:
raise InvalidModuleName(f"Module name '{modname}' is invalid or not found")
return desc
def get_spec(self, modname, var):
"""Retrieves the module class exported spec variable
@type modname: string
@param modname: the module class name
@type var: string
@param var: the base level variable to return
@type dictionary
@return: the modules class exported options descriptions
"""
if modname and modname in self.module_names:
value = self._modules[modname].get(var, None)
else:
raise InvalidModuleName(f"Module name '{modname}' is invalid or not found")
return value
def _check_compat(self, module):
if self.compat_versions:
if not module.module_spec["version"] in self.compat_versions:
raise ModuleVersionError(
(
f"Error loading '{self._namepath}' plugin module: {module.module_spec['name']}, version: {module.module_spec['version']}\n"
"Module is not compatible with the current application version\n"
f"Compatible module API versions are: {self.compat_versions}"
)
)