Plugins API Reference
Every public symbol in pyxle.plugins, including the access helpers introduced alongside the plugin system.
Importing:
from pyxle.plugins import (
PyxlePlugin,
PluginContext,
PluginSpec,
plugin,
active_context,
set_active_context,
load_plugins,
run_startup,
run_shutdown,
PluginError,
PluginResolutionError,
PluginServiceError,
)For the user-facing guide, see Plugins. This page is the reference.
PyxlePlugin
Abstract base class plugins subclass to become discoverable. Nothing is abstract on the Python level — every hook has a no-op default, so a minimal subclass is:
class Plugin(PyxlePlugin):
name = "pyxle-hello"
version = "0.1.0"
async def on_startup(self, ctx: PluginContext) -> None:
ctx.register("hello.service", HelloService())Class attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
name |
str |
"unnamed" |
User-facing plugin name. Used in error messages and for service namespacing. |
version |
str |
"0.0.0" |
Informational. Pyxle doesn't enforce versioning today. |
Instance attributes
| Attribute | Type | Description |
|---|---|---|
self.settings |
Mapping[str, Any] |
Populated by the loader after construction. Matches the settings object from the entry in pyxle.config.json::plugins. Empty dict when the user didn't provide one. |
Methods
async on_startup(ctx: PluginContext) -> None
Called once at ASGI lifespan startup. Typical work: open long-lived connections, run migrations, register services. Raising here aborts ASGI boot (Starlette refuses to serve traffic), which is the right posture for "pyxle-db can't reach the database" scenarios.
async def on_startup(self, ctx: PluginContext) -> None:
self._client = await create_http_client()
ctx.register("telemetry.client", self._client)async on_shutdown(ctx: PluginContext) -> None
Called once at graceful shutdown. Runs in reverse plugin-declaration order so earlier plugins' services are still available when later plugins tear down. Raising here is logged but doesn't abort shutdown of remaining plugins — best-effort by design.
middleware() -> Sequence[tuple[str, Mapping[str, Any]]]
Returns middleware specs to append to the host app's stack. Each entry is (import_string, options):
def middleware(self) -> Sequence[tuple[str, Mapping[str, Any]]]:
return [
("pyxle_requestid.middleware:RequestIdMiddleware", {"header": "x-request-id"}),
]Default: empty tuple.
See the Middleware contribution section of the guide for stack-position details.
PluginContext
Per-process lifecycle context shared across plugins. Plugins register named services here; loaders / actions retrieve them via request.app.state.pyxle_plugins or the module-level `plugin(name)` helper.
Constructor
PluginContext(*, settings: Any = None)settings is typically the host app's resolved DevServerSettings. Exposed on `self.settings` so plugins that need the host's project root, debug flag, etc. can read it.
Methods
register(name: str, service: Any) -> None
Register a service under name. Raises PluginServiceError if the name is already registered or empty. Use `replace` to deliberately overwrite.
ctx.register("db.database", database)replace(name: str, service: Any) -> None
Overwrite an existing registration (or register a new one). Never raises on an existing key. Use for host apps that want to substitute plugin services in tests or for plugins that supersede each other intentionally.
get(name: str, default: Any = None) -> Any
Return the service or default if absent. Non-raising.
telemetry = ctx.get("telemetry.client") # None if no telemetry plugin installedrequire(name: str) -> Any
Return the service or raise PluginServiceError. The error message lists every registered name so typos are obvious from the traceback alone.
auth = ctx.require("auth.service")has(name: str) -> bool
Cheap membership check without side effects.
names() -> tuple[str, ...]
Snapshot of registered service keys, sorted. Useful in logs and admin pages.
Properties
settings
The host app's DevServerSettings (or whatever was passed to the constructor). Plugins access ctx.settings.project_root, ctx.settings.debug, etc.
plugin(name, default=MISSING)
Django-style short form for active_context().require(name) / active_context().get(name, default).
from pyxle.plugins import plugin
@server
async def load(request):
auth = plugin("auth.service") # raises if missing
telemetry = plugin("telemetry", None) # returns None if absentResolves via the module-level active context that the devserver installs at ASGI startup. For use inside an ASGI request (loader, action, API endpoint). For use outside an ASGI context (tests, scripts), install a context manually via `set_active_context`.
Raises PluginServiceError if:
- No active context has been installed (typical cause: calling from outside an ASGI request).
- The service
nameis not registered and nodefaultwas provided.
Prefer plugin-provided helpers (like get_auth_service()) when they exist — they type-annotate their return values, which the generic helper cannot.
active_context() -> PluginContext
Return the currently-installed module-level context. Raises PluginServiceError if none has been installed.
Most app code uses `plugin(name)` instead of touching the context directly. Reach for active_context() when you need the full PluginContext object (e.g. to iterate names() for a diagnostics page).
set_active_context(ctx: PluginContext | None) -> None
Install or clear the module-level context. Pyxle's devserver calls this inside the ASGI lifespan; library code shouldn't normally need to. Tests use it to stand up a clean registry outside the devserver:
from pyxle.plugins import PluginContext, set_active_context
ctx = PluginContext()
ctx.register("my.service", FakeService())
set_active_context(ctx)
try:
# ... code under test that calls plugin(...) or a plugin-provided helper
finally:
set_active_context(None)Passing None clears the active context, returning subsequent plugin(...) calls to the "no active context" error state.
Kept as an explicit function (not a module-level variable) so a future migration to contextvars.ContextVar is a drop-in replacement.
PluginSpec
Resolved plugin entry from pyxle.config.json. Frozen dataclass.
Fields
| Field | Type | Default | Description |
|---|---|---|---|
name |
str |
— | User-facing name (e.g. "pyxle-auth"). |
module |
str |
derived | Python module containing the plugin — default derived from name by replacing - with _ and appending .plugin. |
attribute |
str |
"plugin" |
Attribute on module that holds the PyxlePlugin class or instance. |
settings |
Mapping[str, Any] |
{} |
Per-app configuration dict forwarded to the plugin. |
Class methods
PluginSpec.from_config_entry(entry, *, source="pyxle.config.json") -> PluginSpec
Parse one item from a plugins config list. Accepts either a bare string or an object:
PluginSpec.from_config_entry("pyxle-auth")
# PluginSpec(name='pyxle-auth', module='pyxle_auth.plugin', attribute='plugin', settings={})
PluginSpec.from_config_entry({
"name": "pyxle-auth",
"settings": {"cookieDomain": ".pyxle.app"},
})Raises PluginError for malformed entries (empty string, missing name, wrong types).
load_plugins(specs) -> tuple[PyxlePlugin, ...]
Resolve each PluginSpec to a PyxlePlugin instance. The host app's settings are propagated onto instance.settings so the plugin's hooks can read them.
specs = [PluginSpec.from_config_entry(entry) for entry in config.plugins]
plugins = load_plugins(specs)Failure modes:
PluginResolutionError— module import failed, attribute missing, or attribute is a class that doesn't subclassPyxlePlugin.
async run_startup(plugins, ctx) -> None
Call on_startup on each plugin in declaration order.
await run_startup(plugins, ctx)Failures propagate wrapped in PluginError with the underlying cause chained — ASGI startup aborts cleanly.
async run_shutdown(plugins, ctx) -> None
Call on_shutdown on each plugin in reverse declaration order. Best-effort: failures are logged but don't interrupt the rest of the teardown.
await run_shutdown(plugins, ctx)Errors
PluginError
Base class for every plugin-system failure. Catch this in generic error boundaries; catch the more specific subclasses below when you want to branch on the failure mode.
PluginResolutionError
Subclass of PluginError. Raised by load_plugins when:
- The declared module can't be imported (package missing, import-time crash).
- The declared attribute is missing.
- The declared attribute is a class that doesn't subclass
PyxlePlugin. - The declared attribute is neither a
PyxlePluginsubclass nor an instance.
PluginServiceError
Subclass of PluginError. Raised by:
PluginContext.registeron empty or duplicate names.PluginContext.requireon missing names.plugin(name)when no active context is installed, or when the name is missing and no default was provided.
See also
- Plugins guide — authoring walkthrough + consuming patterns.
- Configuration reference —
pyxle.config.json::pluginsschema. - pyxle-db — first-party SQLite plugin.
- pyxle-auth — first-party auth plugin.