exlab_wizard.plugins.base#

Plugin contract base class. Backend Spec §6.1.

Defines the public surface that every plugin author subclasses:

  • Plugin – the abstract contract: required methods (can_handle, transform), optional lifecycle hooks (pre_transform_all, post_transform_all, describe_changes, on_plugin_failure, validate_variables), and the class attributes the host’s registry cross-checks against manifest.yml (name, version, supported_extensions, api_version, required_variables, optional_variables).

  • PluginContext – the frozen dataclass the host hands the plugin on every per-file call (variable map, destination root, answers file, run identity, dry-run flag, and the structured log shim).

  • FileChange – the per-mutation report shape used by Plugin.describe_changes() for dry-run previews.

  • PluginError and PluginInputRequired – re-exports of the canonical hierarchy from exlab_wizard.errors. Plugin authors import the names from this module for convenience; the host catches them by their canonical class so the two views point at the same exception object.

Note (v0.7): the legacy transform_readme hook is intentionally omitted. README mutation is post-plugin and non-pluggable – see Backend Spec §6.1.5 (“What plugins must not touch”) and §10.8.

Classes

FileChange(path, kind, summary[, detail])

A single mutation a plugin would make.

Plugin()

Abstract base class for all ExLab-Wizard plugins.

PluginContext(variables, dst_root, ...)

Read-only context handed to every plugin lifecycle hook.

class exlab_wizard.plugins.base.FileChange(path, kind, summary, detail=<factory>)[source]#

Bases: object

A single mutation a plugin would make. Used by describe_changes.

Backend Spec §6.1.3.

path#

Absolute path under the rendered destination directory.

kind#

One of "modify", "create", "rename", "delete".

summary#

One-line human-readable description for UI display.

detail#

Optional structured payload for richer dry-run previews (e.g. {"writes": [{"cell": "B7", "value": "asmith"}]}).

Parameters:
detail: dict[str, Any]#
kind: str#
path: Path#
summary: str#
class exlab_wizard.plugins.base.Plugin[source]#

Bases: ABC

Abstract base class for all ExLab-Wizard plugins.

Backend Spec §6.1.3.

Lifecycle (one instance per creation session, all in the worker subprocess):

  • __init__() – cheap construction; no I/O.

  • validate_variables(variables) – called once at registration in a short-lived validation worker.

  • pre_transform_all(ctx) – called once before the file loop.

  • For each matched file:

    • can_handle(file, variables) – cheap predicate.

    • describe_changes(file, ctx) – only invoked in dry-run mode.

    • transform(file, ctx) – the actual mutation.

  • post_transform_all(ctx) – called once after the file loop.

  • on_plugin_failure(exc, ctx) – called only if any other hook raised.

api_version: ClassVar[str] = '1'#
abstractmethod can_handle(file_path, variables)[source]#

Secondary filter, called after the extension match.

Cheap and side-effect-free. Returning False means this file is skipped for this plugin only – other plugins still get a chance.

Parameters:
Return type:

bool

describe_changes(file_path, ctx)[source]#

Return the dry-run preview for what transform would do.

Default returns a single "modify" FileChange with no detail; plugins should override when the user-facing preview matters (e.g. listing the cells that would be written).

Parameters:
Return type:

list[FileChange]

name: ClassVar[str]#
on_plugin_failure(exc, ctx)[source]#

Called if any prior hook raised. Default: no-op.

Use to roll back partial state (delete a half-written sidecar file, restore a backup, close a leaked handle). The exception that caused the failure is passed in; the plugin MUST NOT re-raise. Returning normally means cleanup succeeded; raising means the cleanup itself failed and will be logged separately.

Parameters:
Return type:

None

optional_variables: ClassVar[list[str]] = []#
post_transform_all(ctx)[source]#

Called once after the file loop. Default: no-op.

Symmetric to pre_transform_all() – close handles, flush buffers, etc. Not called if pre_transform_all itself raised.

Parameters:

ctx (PluginContext)

Return type:

None

pre_transform_all(ctx)[source]#

Called once before the file loop. Default: no-op.

Use for batch setup that should be paid once per session (e.g. opening a workbook, opening a DB connection). State stored on self is preserved through the loop because the worker holds one instance for the whole session.

Parameters:

ctx (PluginContext)

Return type:

None

required_variables: ClassVar[list[str]] = []#
supported_extensions: ClassVar[list[str]]#
abstractmethod transform(file_path, ctx)[source]#

Mutate file_path in place.

On unrecoverable failure raise PluginError with a human-readable message. On a discovered need for additional input, raise PluginInputRequired. Return value is ignored.

Parameters:
Return type:

None

validate_variables(variables)[source]#

Return a list of error strings; empty list means “valid”.

Default implementation reports any of self.required_variables that are missing from variables or are present but empty. Plugin authors override only to add bespoke checks (date-format, equipment-allowlist, etc.); the override should call super() first to preserve the missing-required check.

Backend Spec §6.1.3 (variable validation in worker, not host).

Parameters:

variables (dict[str, Any])

Return type:

list[str]

version: ClassVar[str]#
class exlab_wizard.plugins.base.PluginContext(variables, dst_root, answers_file, template_name, template_version, run_kind, equipment_id, project, dry_run, log)[source]#

Bases: object

Read-only context handed to every plugin lifecycle hook.

Constructed once per creation session by the host and passed in across the IPC boundary. Plugins read from it but MUST NOT mutate it (the dataclass is frozen as a defensive measure – mutation attempts raise dataclasses.FrozenInstanceError).

Backend Spec §6.1.4.

Parameters:
answers_file: Path#
dry_run: bool#
dst_root: Path#
equipment_id: str#
log: PluginLogger#
project: str#
run_kind: str#
template_name: str#
template_version: str#
variables: dict[str, Any]#
exception exlab_wizard.plugins.base.PluginError[source]#

Bases: ExLabError

Raised by plugin workers to signal expected failure. Backend Spec §6.

exception exlab_wizard.plugins.base.PluginInputRequired(fields, reason)[source]#

Bases: ExLabError

Raised by plugin workers to escalate for additional input. Backend Spec §6.4.

Parameters:
property reason: str#