Source code for httk.core._plugins
#
# The high-throughput toolkit (httk)
# Copyright (C) 2012-2024 the httk AUTHORS
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from collections.abc import Callable
from dataclasses import dataclass
from importlib import import_module
from typing import Any
CallableRef = str | Callable[..., Any]
[docs]
def resolve_callable(ref: CallableRef) -> Callable[..., Any]:
"""
Resolve a callable reference.
Accepts:
- a callable object
- a string of form "module.submodule:callable_name"
"""
if callable(ref):
return ref
if not isinstance(ref, str):
raise TypeError(f"Expected callable or str reference, got {type(ref)!r}")
module_name, _, attr = ref.partition(":")
if not module_name or not attr:
raise ValueError(f"Invalid reference {ref!r}; expected 'module:callable'.")
obj = getattr(import_module(module_name), attr)
if not callable(obj):
raise TypeError(f"Resolved {ref!r} to non-callable object {obj!r}")
return obj
@dataclass(frozen=True)
class PluginSpec:
"""
A minimal plugin spec.
- `key`: selection key (e.g., a file extension like ".cif", or a format name like "cif")
- `handler`: callable or "module:callable" reference (lazy)
"""
key: str
handler: CallableRef
name: str | None = None # optional display name
[docs]
class PluginRegistry:
"""
Registry mapping keys -> plugin specs.
Intended use:
- readers: key is file extension ".cif"
- savers: key is file extension ".cif" or format name
- show/visualization: key is format/backend name
"""
def __init__(self) -> None:
self._by_key: dict[str, PluginSpec] = {}
[docs]
def register(self, *, key: str, handler: CallableRef, name: str | None = None) -> None:
k = key
self._by_key[k] = PluginSpec(key=k, handler=handler, name=name)
[docs]
def keys(self) -> list[str]:
return sorted(self._by_key.keys())
[docs]
def items(self):
return self._by_key.items()
[docs]
def get(self, key: str) -> PluginSpec | None:
return self._by_key.get(key)
[docs]
def require(self, key: str) -> PluginSpec:
spec = self.get(key)
if spec is None:
known = ", ".join(self.keys()) or "(none)"
raise ValueError(f"No plugin registered for {key!r}. Known: {known}")
return spec
[docs]
def dispatch(self, key: str, *args: Any, **kwargs: Any) -> Any:
spec = self.require(key)
fn = resolve_callable(spec.handler)
return fn(*args, **kwargs)