class Loader:
"""Backend loader class."""
BACKEND_NAME_REGEX: ClassVar[re.Pattern] = re.compile(
r"cssfinder(_|-)backend(_|-)[a-z0-9_\-]+",
re.IGNORECASE,
)
def __init__(self) -> None:
self.reload()
def reload(self) -> None:
"""Load all backends available in python environment.
This is automatically called by constructor to load all backends. You can use it
to refresh list of loaded backends.
"""
self.backends: dict[tuple[str, Precision], Type[BackendBase]] = {}
for dist in importlib.import_module("pkg_resources").working_set:
if self.BACKEND_NAME_REGEX.match(dist.project_name) is None:
continue
module_name = dist.project_name.replace("-", "_")
module = importlib.import_module(module_name)
module_meta = metadata.metadata(module_name)
export_backend = getattr(module, "export_backend", None)
if export_backend is None:
continue
backends = export_backend()
if not isinstance(backends, dict):
logging.critical(
"Backend %r unsupported export format %r, expected <class 'dict'>.",
module_name,
type(backends),
)
continue
self._extend_backend_index(module_name, module_meta, backends)
def _extend_backend_index(
self,
module_name: str,
module_meta: metadata.PackageMetadata, # type: ignore[name-defined]
backends: Any,
) -> None:
for key, value in backends.items():
if not isinstance(key, tuple) or len(key) != LEN_KEY_TUPLE:
logging.critical(
"Backend %r -> %r unsupported key format, expected key to be "
"tuple[str, Precision], got %r.",
module_name,
value,
key,
)
continue
name, precision = key
if not isinstance(name, str):
logging.critical(
"Backend %r -> %r unsupported key format, expected key to be "
"tuple[str, Precision], got %r.",
module_name,
value,
key,
)
continue
if not isinstance(precision, Precision):
logging.critical(
"Backend %r -> %r unsupported key format, expected key to be "
"tuple[str, Precision], got %r.",
module_name,
value,
key,
)
continue
if len(getattr(value, "author", "")) == 0:
value.author = module_meta["Author"]
self.backends[(name.casefold(), precision)] = value
@classmethod
@lru_cache(maxsize=1)
def new(cls) -> Self:
"""Get instance of Loader."""
return cls()
def get_backend(self, name: str, precision: Precision) -> Type[BackendBase]:
"""Query set of available backends with provided properties and return backend
class if there is one meeting expectations.
"""
try:
return self.backends[(name.casefold(), precision)]
except KeyError as exc:
msg = (
f"There is no backend with name={name!r} and precision="
f"{precision.name!r} currently installed."
)
raise BackendNotAvailableError(msg) from exc
def get_rich_table(self) -> Table:
"""Create rich Table object containing information about available backends."""
table = Table(title="Available backends", show_lines=True)
table.add_column("Name", justify="right", no_wrap=True, style="deep_sky_blue1")
table.add_column("Precision", justify="center", no_wrap=True)
table.add_column("Author", justify="center", no_wrap=False)
table.add_column("Source", justify="left", no_wrap=False)
table.add_column("Description", justify="left", no_wrap=False)
for key, value in self.backends.items():
try:
(name, precision), cls = key, value
table.add_row(
name,
precision.name,
getattr(cls, "author", ""),
f"{cls.__module__}.{cls.__qualname__}",
getattr(cls, "description", ""),
)
except (TypeError, ValueError): # noqa: PERF203
logging.warning("Failed to display information about backed %r", value)
return table