-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpackage_loader.py
More file actions
59 lines (49 loc) · 2.28 KB
/
package_loader.py
File metadata and controls
59 lines (49 loc) · 2.28 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
"""Dynamic plugin registration into an :class:`ActionRegistry`.
``PackageLoader`` imports an external package by name and registers every
top-level function / class / builtin under the key ``"<package>_<member>"``.
"""
from __future__ import annotations
from importlib import import_module
from importlib.util import find_spec
from inspect import getmembers, isbuiltin, isclass, isfunction
from types import ModuleType
from automation_file.core.action_registry import ActionRegistry
from automation_file.logging_config import file_automation_logger
class PackageLoader:
"""Load packages lazily and register their public callables."""
def __init__(self, registry: ActionRegistry) -> None:
self.registry: ActionRegistry = registry
self._cache: dict[str, ModuleType] = {}
def load(self, package: str) -> ModuleType | None:
"""Import ``package`` once and return the module (cached)."""
cached = self._cache.get(package)
if cached is not None:
return cached
spec = find_spec(package)
if spec is None:
file_automation_logger.error("PackageLoader: cannot find %s", package)
return None
try:
# `package` is a trusted caller-supplied name (see PackageLoader docstring and
# the CLAUDE.md security note on plugin loading); it is not untrusted input.
name = spec.name
module = import_module(name) # nosemgrep
except ImportError as error:
file_automation_logger.error("PackageLoader import error: %r", error)
return None
self._cache[package] = module
return module
def add_package_to_executor(self, package: str) -> int:
"""Register every function / class / builtin from ``package``.
Returns the number of commands that were registered.
"""
module = self.load(package)
if module is None:
return 0
count = 0
for predicate in (isfunction, isbuiltin, isclass):
for member_name, member in getmembers(module, predicate):
self.registry.register(f"{package}_{member_name}", member)
count += 1
file_automation_logger.info("PackageLoader: registered %d members from %s", count, package)
return count