Source code for doitoml.entry_points
"""Loads entry points."""
import sys
from functools import lru_cache
from typing import TYPE_CHECKING, Any, Dict, List, Tuple, Union
from doitoml.errors import EntryPointError, MissingDependencyError
from .constants import ENTRY_POINTS
if sys.version_info < (3, 10): # pragma: no cover
from importlib_metadata import entry_points
else: # pragma: no cover
from importlib.metadata import entry_points
if TYPE_CHECKING:
from .actors._actor import Actor
from .doitoml import DoiTOML
from .dsl import DSL
from .skippers._skipper import Skipper
from .sources._config import ConfigParser
from .sources._source import Parser
from .templaters._templater import Templater
from .updaters._updater import Updater
[docs]
class EntryPoints:
"""A collection of named ``entry_points``."""
doitoml: "DoiTOML"
dsl: Dict[str, "DSL"]
parsers: Dict[str, "Parser"]
config_parsers: Dict[str, "ConfigParser"]
actors: Dict[str, "Actor"]
templaters: Dict[str, "Templater"]
updaters: Dict[str, "Updater"]
skippers: Dict[str, "Skipper"]
def __init__(self, doitoml: "DoiTOML") -> None:
"""Create a new collection of loaded ``entry_points``."""
self.doitoml = doitoml
[docs]
@staticmethod
@lru_cache(1)
def raw_entry_points(
group: str,
) -> Tuple[Dict[str, Any], List[Tuple[str, str, str]]]:
"""Load and cache raw entry points."""
raw_eps = {}
logs: List[Tuple[str, str, str]] = []
for entry_point in entry_points(group=group):
try:
raw_eps[entry_point.name] = entry_point.load()
except Exception as err: # pragma: no cover
logs.append((group, entry_point.name, str(err)))
return dict(sorted(raw_eps.items())), sorted(logs)
[docs]
def initialize(self) -> None:
"""Load all ``entry_points``."""
# load the parsers
self.config_parsers = self.load_entry_point_group(ENTRY_POINTS.CONFIG)
self.parsers = self.load_entry_point_group(ENTRY_POINTS.PARSER)
# load DSL, which might reference parsers
self.dsl = self.load_entry_point_group(ENTRY_POINTS.DSL)
self.actors = self.load_entry_point_group(ENTRY_POINTS.ACTOR)
self.templaters = self.load_entry_point_group(ENTRY_POINTS.TEMPLATER)
self.updaters = self.load_entry_point_group(ENTRY_POINTS.UPDATER)
self.skippers = self.load_entry_point_group(ENTRY_POINTS.SKIPPER)
[docs]
def load_entry_point_group(self, group: str) -> Dict[str, Any]:
"""Find and load ``entry_points`` from installed packages."""
eps = {}
raw_eps, logs = self.raw_entry_points(group)
for name, entry_point in raw_eps.items():
try:
eps[name] = entry_point(self.doitoml)
except MissingDependencyError as err:
logs.append((group, name, str(err)))
except Exception as err: # pragma: no cover
message = f"{group} {name} unexpectedly failed to load {err}"
raise EntryPointError(message) from err
for log in sorted(logs): # pragma: no cover
self.doitoml.log.info("%s %s is missing a dependency: %s", *log)
return dict(sorted(eps.items(), key=self.rank_key))
[docs]
def rank_key(self, key_ep: Tuple[str, Any]) -> Tuple[Union[int, float], str]:
"""Return a sort key based on the ``entry_point``'s ``rank`` and key."""
key, ep = key_ep
return (getattr(ep, "rank", 100), key.lower())