A native Python extension wrapping the cpex-core Rust runtime via PyO3.
Provides the canonical CPEX plugin lifecycle with await-based async APIs.
- Python ≥ 3.10
- Rust toolchain (
rustup) - maturin (
pip install maturin)
# From the bindings/python directory:
cd bindings/python
python -m venv .venv
source .venv/bin/activate
pip install maturin pytest pytest-asyncio
maturin developimport asyncio
from cpex import PluginManager
async def main():
mgr = PluginManager("plugins/config.yaml")
await mgr.initialize()
result = await mgr.invoke_hook(
"cmf.tool_pre_invoke",
{
"message": {
"role": "user",
"content": [{"type": "text", "text": "Hello"}],
}
},
)
if not result.continue_processing:
print("Denied:", result.violation)
else:
print("Allowed")
# Always shut down to drain fire-and-forget tasks.
await mgr.shutdown()
asyncio.run(main())Synchronous constructor. Reads the YAML config file, registers bundled APL
factories, and loads the config. Raises ValueError on missing file,
IO error, or config parse failure.
Initialize all registered plugins. Must be called before invoke_hook.
Shut down all plugins and drain fire-and-forget background tasks. Call this before exit if you need fire-and-forget side effects to complete.
Invoke a hook by name. Returns a PipelineResult.
hook_name— e.g."cmf.tool_pre_invoke". Any hook name is accepted;cmf.*hooks use typedMessagePayload, others useGenericPayload.payload— JSON-compatibledict(str keys, depth ≤ 128).extensions— optionaldictof CPEX extensions fields.context_table— optionaldictfor stateful plugins.
Raises:
ValueError— payload conversion failure or config error.RuntimeError— plugin execution error.TimeoutError— wall-clock timeout exceeded (60 s default).
Policy denials do not raise — check result.continue_processing.
| Attribute | Type | Description |
|---|---|---|
continue_processing |
bool |
False when a plugin denied |
violation |
dict | None |
Populated on deny |
errors |
list[dict] |
Per-plugin errors (on_error: ignore/disable) |
modified_payload |
dict | None |
Payload after transform-phase modifications |
modified_extensions |
dict | None |
Extensions after modifications |
metadata |
dict | None |
Optional telemetry metadata |
context_table |
dict |
Per-plugin state |
Plugins configured with mode: fire_and_forget run asynchronously.
Their side effects are not guaranteed until await manager.shutdown()
completes. In tests always await shutdown() before asserting
fire-and-forget side effects:
await mgr.invoke_hook(...)
await mgr.shutdown() # drain before asserting audit log
assert audit_log_written()This package and the legacy ./cpex/ package share the import name cpex.
They must not be installed in the same virtualenv. Always use a
dedicated venv for the Rust-backed package.
The tokio runtime thread count is controlled by:
CPEX_PY_WORKER_THREADS=4 python my_script.pyDefaults to tokio's num_cpus when unset.
- Identity/delegation hooks (
identity_resolve,token_delegate): routed throughGenericPayloadin v1 — token fields are preserved in the raw dict but are not cryptographically validated. Typed constructors and token injection are planned for v2. - Cedarling PDP: available behind the
cedarlingCargo feature (--features cedarling), off by default.
cd bindings/python
maturin develop
pytest tests/ -vSee MIGRATION.md for a mapping from the legacy
pure-Python cpex framework to this Rust-backed package.