Source code for sdgx.manager
from __future__ import annotations
import glob
import importlib
from os.path import basename, dirname, isfile, join
from typing import Any
import pluggy
from sdgx import models
from sdgx.exceptions import InitializationError, NotFoundError, RegisterError
from sdgx.utils import Singleton, logger
[docs]
class Manager(metaclass=Singleton):
"""
Base class for all manager.
Manager is a singleton class for preventing multiple initialization.
Define following attributes in subclass:
* register_type: Base class for registered class
* project_name: Name of entry-point for extensio
* hookspecs_model: Hook specification model(where @hookspec is defined)
For available managers, please refer to :ref:`Plugin-supported modules`
"""
register_type: type = object
"""
Base class for registered class
"""
project_name: str = ""
"""
Name of entry-point for extension
"""
hookspecs_model = None
"""
Hook specification model(where @hookspec is defined)
"""
def __init__(self):
self.pm = pluggy.PluginManager(self.project_name)
self.pm.add_hookspecs(self.hookspecs_model)
self._registed_cls: dict[str, type[self.register_type]] = {}
# Load all
self.pm.load_setuptools_entrypoints(self.project_name)
# Load all local model
self.load_all_local_model()
[docs]
def load_all_local_model(self):
"""
Implement this function to load all local model
"""
return
@property
def registed_cls(self) -> dict[str, type]:
"""
Access all registed class.
Lazy load, only load once.
"""
if self._registed_cls:
return self._registed_cls
for f in self.pm.hook.register(manager=self):
try:
f()
except Exception as e:
logger.exception(RegisterError(e))
continue
return self._registed_cls
[docs]
def _load_dir(self, module):
"""
Import all python files in a submodule.
"""
modules = glob.glob(join(dirname(module.__file__), "*.py"))
sub_packages = (
basename(f)[:-3] for f in modules if isfile(f) and not f.endswith("__init__.py")
)
packages = (str(module.__package__) + "." + i for i in sub_packages)
for p in packages:
self.pm.register(importlib.import_module(p))
[docs]
def _normalize_name(self, name: str) -> str:
return name.strip().lower()
[docs]
def register(self, cls_name, cls: type):
"""
Register a new model, if the model is already registed, skip it.
"""
cls_name = self._normalize_name(cls_name)
logger.debug(f"Register for new model: {cls_name}")
if cls in self._registed_cls.values():
logger.error(f"SKIP: {cls_name} is already registed")
return
if not issubclass(cls, self.register_type):
logger.error(f"SKIP: {cls_name} is not a subclass of {self.register_type}")
return
self._registed_cls[cls_name] = cls
[docs]
def init(self, c, **kwargs: dict[str, Any]):
"""
Init a new subclass of self.register_type.
Raises:
NotFoundError: if cls_name is not registered
InitializationError: if failed to initialize
"""
if isinstance(c, self.register_type):
return c
if isinstance(c, type):
cls_type = c
else:
c = self._normalize_name(c)
if not c in self.registed_cls:
raise NotFoundError
cls_type = self.registed_cls[c]
try:
instance = cls_type(**kwargs)
if not isinstance(instance, self.register_type):
raise InitializationError(f"{c} is not a subclass of {self.register_type}.")
return instance
except Exception as e:
raise InitializationError(e)