A plugin loader executes a selected module and checks its exported interface before making it available to application dispatch.
Python plugin project: load owned code and publish only an accepted interface
Operation contract
The fixture writes two owned modules into a temporary directory and loads each through importlib’s file specification. It places the module in sys.modules for initialization and removes that entry when loading or validation fails. A successful module must expose a callable receipt_label that passes one declared smoke case. Only that accepted callable enters the local registry.
Failure and ownership boundary
The smoke case is not proof for every input, and module side effects are not rolled back by removing a cache entry. This loader must receive trusted, application-owned code paths; it is not an isolation boundary for uploads or arbitrary executable text. The fixture releases its module entries when finished. Python import failures: module removal does not roll back every side effect, Python first-class functions: dispatch through an explicit operation registry and Python Protocol: structural intent without runtime validation explain the remaining boundaries.
Working program
import importlib.util
from pathlib import Path
import sys
import tempfile
def load_owned_label(path, name):
specification = importlib.util.spec_from_file_location(name, path)
if specification is None or specification.loader is None:
raise ValueError("module specification unavailable")
module = importlib.util.module_from_spec(specification)
if name in sys.modules: raise ValueError("module name already retained")
sys.modules[name] = module
try:
specification.loader.exec_module(module)
operation = getattr(module, "receipt_label", None)
if not callable(operation) or operation(41) != "R-0041":
raise ValueError("plugin interface rejected")
return operation
except BaseException:
sys.modules.pop(name, None)
raise
with tempfile.TemporaryDirectory() as directory:
owned = Path(directory)
accepted = owned / "accepted.py"; rejected = owned / "rejected.py"
accepted.write_text('def receipt_label(number):\n return f"R-{number:04d}"\n', encoding="utf-8")
rejected.write_text('receipt_label = 125\n', encoding="utf-8")
registry = {}
try:
registry["minor"] = load_owned_label(accepted, "aitrove_owned_label")
print(registry["minor"](41))
try: registry["invalid"] = load_owned_label(rejected, "aitrove_rejected_label")
except ValueError: print("rejected registry absent:", "invalid" not in registry)
print("failed module absent:", "aitrove_rejected_label" not in sys.modules)
finally:
sys.modules.pop("aitrove_owned_label", None)Output
R-0041
rejected registry absent: True
failed module absent: TrueCosts and limits
Initialization executes the module’s own work. The tiny owned sources here have bounded smoke checks; a file path or an import API does not impose execution time, memory or side-effect limits. Published callables retain their module-global references even after cache removal.
Common Mistakes
- Import cache cleanup does not undo external side effects.
- Do not label a trusted-code loader as an upload sandbox.
Connected lessons
Python import failures: module removal does not roll back every side effect, Python first-class functions: dispatch through an explicit operation registry, Python Protocol: structural intent without runtime validation.
Trace the related workflow
Python entry points: inspect a trusted distribution before loading its plugin.
