pycordis
Distribution pycordis-sdk, import name pycordis.
Write Cordis plugins in Python. Python plugins run inside the original Node Cordis runtime: every Python plugin is a real Cordis fiber with its own effects, listeners, services and disposal, and Cordis HMR hot-reloads Python modules.
uv pip install pycordis-sdk
import asyncio, pycordis
def greeter(ctx, config):
ctx.on('greet', lambda name: print(f"{config['greeting']}, {name}"))
return lambda: print('greeter disposed')
async def main():
async with pycordis.App() as app:
await app.ctx.plugin(greeter, {'greeting': 'Hello'})
app.ctx.emit('greet', 'Cordis')
asyncio.run(main())
Requirements: Python ≥ 3.12 and Node.js ≥ 22.15 on PATH (or PYCORDIS_NODE=/path/to/node). Linux and macOS. pycordis has no Python dependencies. The wheel bundles the Cordis runtime used by DeepSeek Harness: cordis 4.1, the Loader, Include, Group, Timer, HMR and logger-console plugins, with their dependencies and license notices. To use a DeepSeek Harness checkout's built vendor/ packages instead, pass App(vendor_dir=...) or set PYCORDIS_VENDOR_DIR.
Model
App spawns a Node process that hosts one root Cordis Context. The two runtimes exchange newline-delimited JSON frames over a pair of pipes.
- Synchronous, re-entrant calls.
- Every cross-runtime call blocks its caller until the other side answers. While waiting, each side serves the other side's nested calls.
- This keeps Cordis's synchronous APIs exact:
emit,bail,waterfall, accessors,Service.checkand Standard Schema validation. - Node serves every request while it waits. Python serves only requests nested in its own pending call. Other Node-originated calls, such as timers or plugin activation, run once Python's event loop is idle. Python code therefore keeps JS-like run-to-completion between
awaits.
- Async values. A Python
async defcalled from JS starts eagerly, like a JS async function, and returns a Promise. A JS Promise becomes an awaitable. - Reference semantics.
- Only immutable values are copied:
None/null, bools, numbers, strings, bytes and symbols. - Every mutable value crosses as a live proxy of the object in its home runtime. A Python dict provided as a service, an event payload mutated by a waterfall listener, or a JS config object is therefore one shared object.
- Sending a proxy back to its home runtime returns the original object, so
isholds. pycordis.js.copy(value)andpycordis.to_py(js_value)make explicit snapshots.
- Only immutable values are copied:
- Threading. Bridge calls must run on the
Appevent-loop thread. From other threads, useapp.call_soon_threadsafe. - Lifetime. A proxy releases its remote object when it is garbage-collected. Reference cycles that cross the bridge are not collected.
API
Python names mirror the Cordis API. Methods use snake_case, with camelCase aliases where the JS name differs.
| Cordis (JS) | pycordis |
|---|---|
ctx.extend/isolate/intercept, ctx.root, ctx.baseUrl |
same; also ctx.base_url |
Context.effect/filter/isolate/intercept, Context.is |
class attributes (symbols); Context.is_(value) |
ctx.get/set/provide/accessor/mixin |
same; accessor(name, get=fn(ctx, receiver, error), set=fn(ctx, value, receiver, error)) |
ctx.foo (service access) |
ctx.foo, ctx['foo'], ctx.foo = value; Cordis inject rules apply |
ctx.effect(execute, label) |
same; execute may return a disposer, None, a coroutine, a generator or an async generator of disposers; returns a Disposer whose call result is always awaitable |
ctx.plugin/inject |
same; a plugin is a function, a class, an object with apply, or a module with apply; metadata comes from attributes (name, Config, inject, provide, intercept) or @pycordis.plugin(...) |
ctx.emit/parallel/serial/bail/waterfall, ctx.on/once |
same; parallel and serial return awaitables; options are True or {'prepend': …, 'global': …} |
Fiber |
Fiber with FiberState (IntEnum), await fiber, fiber.await_(), raw_config, effective_config, resolve_config, update, restart, and get_effects() returning EffectMeta |
Service (init, check, invoke, extend, tracker, resolveConfig) |
pycordis.Service; members __cordis_init__, __cordis_check__, __cordis_invoke__, …, or decorators such as @Service.init |
Config (Standard Schema) |
pycordis.schema builders, or JS Schemastery schemas |
CordisError, ValidationError, AggregateError |
pycordis.CordisError, ValidationError, JsAggregateError (an ExceptionGroup); other JS errors raise JsError |
| any JS module | await app.js.import_(spec), where spec is a bundled package name, a node: builtin, a path or a data URL |
from pycordis import Service, schema
class Counter(Service):
Config = schema.object({'start': schema.number().default(0)})
def __init__(self, ctx, config):
super().__init__(ctx, 'counter')
self.value = config['start']
def increment(self):
self.value += 1
return self.value
def consumer(ctx):
print(ctx.counter.increment())
consumer.inject = ['counter']
Services read through a context are traced, as in Cordis. Inside a method of a Service reached as ctx.svc, self.ctx is the caller's context, so effects the service registers belong to the caller's fiber.
Reading a missing JS property raises AttributeError. Use getattr(obj, key, None) when the property may be absent.
Configuration files and HMR
async with pycordis.App() as app:
await app.load_config('cordis.yml') # Cordis Loader + Include
await app.enable_hmr(root=['.'], debounce=100) # Cordis HMR
# cordis.yml
- id: counter
name: ./plugins/counter.py
config: { start: 10 }
- id: consumer
name: python:my_package.consumer # a module on sys.path
inject: [counter]
!!js expressions in config can read Python services.
Each Python module joins Node's module graph as a synthetic ES module. Its static imports mirror the module's local Python imports. The Cordis HMR therefore handles Python plugins unchanged:
- watching and debounce
- accept/decline analysis
- plugin swaps that keep the raw config
- rollback with a code frame on syntax or execution errors
- Include refresh
hmr/changeandhmr/reload
A changed Python module that the host imported directly, not through Loader, calls ctx.loader.exit(). This is the counterpart of HMR externals.
Reload semantics are those of Cordis HMR: a plugin whose provided service still has live dependents cannot be re-provided by a swap. Pure-JS plugins behave the same way.
License
MIT. The bundled Cordis runtime and its dependencies are MIT or ISC; their notices ship in pycordis/_node/runtime/THIRD_PARTY_LICENSES.txt.
Metadata
Release files for pycordis-sdk 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| pycordis_sdk-1.0.0.tar.gz | 151.3 kB | Without provenance | Details for pycordis_sdk-1.0.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| pycordis_sdk-1.0.0-py3-none-any.whl | Python 3 | none | any | Details for pycordis_sdk-1.0.0-py3-none-any.whl |
Total release size: 307.1 kB
Release files / pycordis_sdk-1.0.0.tar.gz
| Download URL | pycordis_sdk-1.0.0.tar.gz |
|---|---|
| Size | 151.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
87de23a58f7741098b523f6827ea3749dbf79bfed859879877acbc24fa749012
|
|
BLAKE2b-256 checksum How to use checksums |
ffada3aeb871dba7a3196e460027494f4101216086da0b551b6931ab60e8f548
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.9.8
|
Release files / pycordis_sdk-1.0.0-py3-none-any.whl
| Download URL | pycordis_sdk-1.0.0-py3-none-any.whl |
|---|---|
| Size | 155.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
55dc6519576324f4bac6db1441a0162a7febc88a56438bf3c118a97d24f81518
|
|
BLAKE2b-256 checksum How to use checksums |
e1c8d66dfb08406a674d61313af370c82e4d2937aaa7187bfdbe58c9e3a2375b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.9.8
|
Security for pycordis-sdk 1.0.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.