Plugins¶
Privyx loads plugins from local paths you configure — there is no
third-party entry-point mechanism. Discovery is opt-in (nothing loads unless
you list a path) and works by subclass: a plugin module defines a concrete
subclass of a Privyx component base class, and Privyx registers it under its
name.
What Can Be Plugged In¶
| Family | Base class | Methods to implement | Selected by |
|---|---|---|---|
| Detector | privyx.privacy.detector.base.BaseDetector |
detect_sync |
detector.type |
| Policy | privyx.privacy.policy.base.BasePolicy |
_decide_sync |
policy.type |
| Operator | privyx.privacy.operator.base.BaseOperator |
pseudonymize, deanonymize |
operator.type |
| Anchor | privyx.privacy.anchor.base.BaseAnchor |
anchor, deanchor |
anchor.type |
| Vault | privyx.vault.base.BaseVault |
create, get, save, delete (opt. connect/close, and list_sessions for privyx session list/prune) |
vault.type |
| Provider | privyx.providers.base.BaseProvider |
send, stream, close |
provider.type |
The Contract¶
- Subclass one of the base classes above with a concrete implementation.
- Set a class-level
name— the value a configtypeselects. If omitted, it is derived from the class name with the family suffix stripped (LicensePlateDetector→licenseplate); settingnameexplicitly is recommended. - Optionally add
@classmethod from_config(cls, config: dict) -> Selfto construct from the component's config section. Without it, Privyx calls the no-argument constructorcls(). The section may carry options of your own (detector: {type: license_plate, region: EU}passesregion); Privyx rejects such unknown keys only under a built-in type, where they are typos. - Optionally define module-level
on_startup()/on_shutdown()(sync or async) for lifecycle work (see below).
A plugin never shadows a built-in: built-in types (regex, pseudonym,
hmac, memory, ...) always win, and a plugin is consulted only when the
configured type is not a built-in.
Enabling¶
List a directory or a single .py file under plugins.paths. Each directory
is walked recursively; files whose names start with _ are skipped.
plugins:
enabled: true # set false to skip loading even when paths are set
paths:
- ./plugins # the conventional in-repo location
- /opt/privyx/ext
detector:
type: license_plate # a name a plugin registered
Paths may also be set via PRIVYX_PLUGIN_PATHS (comma-separated).
privyx doctor reports what each family loaded, so you can confirm a plugin was
discovered before serving.
Writing a Detector¶
from privyx.core.context import Context
from privyx.core.result import Detection
from privyx.privacy.detector.base import BaseDetector
class LicensePlateDetector(BaseDetector):
name = "license_plate"
def detect_sync(self, text: str, context: Context) -> Detection:
d = Detection()
# ... find spans, d.add(start, end, "LICENSE_PLATE", text[start:end])
return d
A ready-to-run copy lives in plugins/detectors/license_plate.py. A second
example, plugins/detectors/iban.py, validates a checksum and takes an
option; the tutorial A detector plugin
builds it step by step.
A detector that calls a paid or remote service can report what it did with
context.counters["my_calls"] += 1. The counts land in the audit trail as
session.transform detector_counts and on /metrics, so they must be counts,
never text. Set detection.cacheable = False on a degraded result (a fallback)
so the detection cache does not keep it in place of a real scan.
Writing an Operator¶
from privyx.core.result import TransformResult
from privyx.privacy.operator.base import BaseOperator
class MyOperator(BaseOperator):
name = "myop"
async def pseudonymize(self, text, detection, session, context) -> TransformResult:
...
async def deanonymize(self, text, session, context) -> TransformResult:
...
Operators that emit tokens or anchor values receive the token codec and anchor
through their config; read them in from_config if needed.
Lifecycle Hooks¶
A plugin module may define module-level hooks that run around the serving lifecycle:
async def on_startup() -> None:
... # open a connection, warm a cache
def on_shutdown() -> None:
... # release resources
Startup hooks run before the server accepts traffic; a failing startup hook aborts the process. Shutdown hooks run during teardown and are best-effort (errors are logged, not raised).
How Discovery Works¶
privyx.plugins.loader.load_plugins(settings) clears the plugin registry, then
imports every module under plugins.paths (under a synthetic module name, so
plugins never touch sys.path). For each module it registers every concrete
Base* subclass defined in that module, keyed by name, into
privyx.plugins.registry.PLUGINS. Two plugins claiming the same name in the
same family, a missing path, or a module that fails to import all raise
ConfigError at startup rather than failing on the first request.