coglet-python
PyO3 bindings that bridge the Rust coglet library to Python. This crate implements
the PredictHandler trait by wrapping Python predictor classes.
Overview
┌─────────────────────────────────────────────────────────────────────────────┐
│ coglet-python │
│ │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ lib.rs │ │
│ │ Python module: serve(), active(), _run_worker(), _is_cancelable() │ │
│ └──────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌───────────────────────┼───────────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌─────────────────────┐ ┌─────────────────────┐ ┌──────────────────┐ │
│ │ worker_bridge.rs │ │ predictor.rs │ │ log_writer.rs │ │
│ │ PredictHandler │ │ PythonPredictor │ │ SlotLogWriter │ │
│ │ impl for Python │ │ load/setup/predict │ │ ContextVar │ │
│ └─────────────────────┘ └─────────────────────┘ └──────────────────┘ │
│ │ │ │ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────────┐ ┌─────────────────────┐ ┌──────────────────┐ │
│ │ input.rs │ │ output.rs │ │ audit.rs │ │
│ │ Pydantic/ADT │ │ JSON serialization │ │ TeeWriter │ │
│ │ input processing │ │ make_encodeable │ │ stream protect │ │
│ └─────────────────────┘ └─────────────────────┘ └──────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ cancel.rs │ │
│ │ SIGUSR1 handling, CancelableGuard, KeyboardInterrupt injection │ │
│ └──────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Directory Structure
coglet-python/
├── Cargo.toml
├── coglet.pyi # Type stubs for Python IDE support
└── src/
├── lib.rs # Python module definition, serve/active/_run_worker
├── predictor.rs # PythonPredictor: wraps Python Predictor class
├── worker_bridge.rs # PythonPredictHandler: implements PredictHandler
├── input.rs # Input processing (Pydantic validation, ADT)
├── output.rs # Output processing (make_encodeable, upload_files)
├── log_writer.rs # SlotLogWriter, ContextVar routing, SetupLogSender
├── audit.rs # Audit hook, TeeWriter for stream protection
└── cancel.rs # Cancellation: SIGUSR1, CancelableGuard
Critical Concepts
The active() Flag
import coglet
if coglet.server.active:
# Running inside worker subprocess
# stdout/stderr are captured, print goes to slot routing
else:
# Running in parent or standalone
# Normal stdout/stderr behavior
Set to True at the start of _run_worker(). Used by user code and cog internals
to detect worker context.
Single Async Event Loop
Async predictors run on a single Python asyncio event loop, created at worker startup. All slots share this loop.
Worker Subprocess
┌─────────────────────────────────────────────────────────────┐
│ Tokio Runtime (Rust) │
│ └─ run_worker event loop │
│ └─ For each SlotRequest::Predict: │
│ └─ tokio::spawn prediction task │
│ └─ Python::attach (acquire GIL) │
│ └─ asyncio.run_coroutine_threadsafe() │
│ └─ Predictor.predict() coroutine │
└─────────────────────────────────────────────────────────────┘
asyncio event loop (Python)
┌─────────────────────────────────────────────────────────────┐
│ Single event loop, started once at worker init │
│ - concurrent.futures.Future per async prediction │
│ - ContextVar propagates prediction_id to spawned tasks │
│ - Cancellation via future.cancel() │
└─────────────────────────────────────────────────────────────┘
Why single loop?
- Python asyncio has one event loop per thread
- We use
run_coroutine_threadsafeto submit from Rust/Tokio - Multiple slots can have concurrent predictions (up to
max_concurrency)
Prediction Execution
Sync Predictors:
SlotRequest::Predict arrives
│
├─▶ Python::attach (acquire GIL)
├─▶ set_sync_prediction_id(id) # For log routing
├─▶ predictor.predict(input) # Blocking call
├─▶ set_sync_prediction_id(None)
└─▶ Return PredictResult
Async Predictors:
SlotRequest::Predict arrives
│
├─▶ Python::attach (acquire GIL)
├─▶ Create wrapped coroutine:
│ async def _ctx_wrapper(coro, prediction_id, contextvar):
│ contextvar.set(prediction_id) # Set in this task's context
│ return await coro
│
├─▶ asyncio.run_coroutine_threadsafe(wrapper, loop)
├─▶ py.detach() (release GIL)
├─▶ future.result() (block Rust task, Python runs)
└─▶ Return PredictResult
STDOUT/STDERR Routing
All output from user code must be captured and routed through the slot socket.
Architecture:
sys.stdout = SlotLogWriter(stdout)
sys.stderr = SlotLogWriter(stderr)
SlotLogWriter.write(data)
│
├─▶ Get current prediction_id from:
│ 1. SYNC_PREDICTION_ID static (for sync predictors)
│ 2. ContextVar (for async predictors/spawned tasks)
│
├─▶ Look up SlotSender in PREDICTION_REGISTRY
│
└─▶ Route:
Found sender → slot_sender.send_log(source, data)
No sender → Check setup sender (during setup)
Neither → Log as orphan to stderr
Line Buffering:
SlotLogWriter buffers writes until a newline. This coalesces Python's print()
which does separate writes for content and \n.
Audit Hook Protection
User code might replace sys.stdout:
sys.stdout = open("mylog.txt", "w")
We can't prevent this, but we can intercept it with a Python audit hook.
Strategy: TeeWriter
User replaces sys.stdout
│
├─▶ Audit hook fires on object.__setattr__(sys, "stdout", value)
│
├─▶ Check: is value already SlotLogWriter? → Allow (it's us)
│
├─▶ Check: is value already TeeWriter? → Allow (already wrapped)
│
├─▶ Create TeeWriter(inner=SlotLogWriter, user_stream=value)
│
└─▶ Schedule: sys.stdout = tee (via Timer to avoid recursion)
TeeWriter.write(data)
│
├─▶ inner.write(data) # Our SlotLogWriter (routing works)
└─▶ user_stream.write(data) # User's stream (their code works)
Result: Both our log routing AND the user's stream receive the data.
Cancellation
Sync Predictors:
Parent: ControlRequest::Cancel { slot }
│
├─▶ Worker: handler.cancel(slot)
│ └─▶ Set CANCEL_REQUESTED flag for slot
│
├─▶ Worker: send SIGUSR1 to self
│
└─▶ Signal handler: raise KeyboardInterrupt (if in cancelable region)
Prediction code:
with CancelableGuard(): # Sets CANCELABLE=true
predictor.predict() # Can be interrupted
# CANCELABLE=false on exit
Async Predictors:
Parent: ControlRequest::Cancel { slot }
│
└─▶ Worker: handler.cancel(slot)
│
├─▶ Get future from slot state
└─▶ future.cancel()
│
└─▶ Python raises asyncio.CancelledError
Setup Log Routing
During setup (before any prediction), logs go through the control channel:
worker_bridge.setup()
│
├─▶ register_setup_sender(tx) # Control channel sender
│
├─▶ predictor.load() + predictor.setup()
│ │
│ └─▶ print("Loading model...")
│ │
│ └─▶ SlotLogWriter.write()
│ │
│ ├─▶ No prediction_id (not in prediction)
│ └─▶ get_setup_sender() → ControlResponse::Log
│
└─▶ unregister_setup_sender()
Behaviors
Worker Startup:
set_active()- Mark as worker subprocessinit_tracing()- Configure logging (stderr, COG_LOG_LEVEL env)install_slot_log_writers()- Replace sys.stdout/stderrinstall_audit_hook()- Protect streamsinstall_signal_handler()- SIGUSR1 for cancellation- Read Init message from stdin
- Connect to slot sockets
handler.setup()- Load and initialize predictor- Send Ready message
- Enter event loop
Shutdown:
- ControlRequest::Shutdown → Send ShuttingDown, exit
- stdin closes (parent died) → Exit immediately
- All slots poisoned → Exit
Error Handling:
- SetupError::Load - Failed to import/instantiate predictor
- SetupError::Setup - setup() raised exception
- PredictionError - Prediction failed, slot stays healthy
- Slot write error → Slot poisoned (no more predictions on that slot)
Metadata
Release files for coglet 0.23.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distributions (wheels)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| coglet-0.23.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl | CPython 3.10 | abi3 | Linux glibc 2.17+ x86-64 | Details |
| coglet-0.23.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl | CPython 3.10 | abi3 | Linux glibc 2.17+ ARM64 | Details |
| coglet-0.23.0-cp310-abi3-macosx_11_0_arm64.whl | CPython 3.10 | abi3 | macOS 11.0+ ARM64 | Details |
Total release size: 20.2 MB
Release files / coglet-0.23.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | coglet-0.23.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 6.8 MB |
| Tags | CPython 3.10 Linux glibc 2.17+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
dc4c87a1214317cc08cd62854b97643b1347437c05a09da3e52d8b2d21a1dac3
|
|
BLAKE2b-256 checksum How to use checksums |
0cb4f0bc22b8cb5dafa38208277eb76bcbf4b1453dbf45086b1bfd6af498ac61
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.
Transparency logRelease files / coglet-0.23.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
| Download URL | coglet-0.23.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl |
|---|---|
| Size | 6.5 MB |
| Tags | CPython 3.10 Linux glibc 2.17+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
c25668cc2543b2284f5feba97bcc72b551980d6b6325272dccfe34d74acd2fe9
|
|
BLAKE2b-256 checksum How to use checksums |
04bb62cf4197862f6c701737588d6bfa942cac5609bbd4268448b93051147e6b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.
Transparency logRelease files / coglet-0.23.0-cp310-abi3-macosx_11_0_arm64.whl
| Download URL | coglet-0.23.0-cp310-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 7.0 MB |
| Tags | CPython 3.10 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
42570df8e0cea095e155a415cc433d2557ed3b6b83079a354e2493ac6f373c85
|
|
BLAKE2b-256 checksum How to use checksums |
f5a129e034e65408ccfaf5ed09bb3de32fc6752bb537d6781fd14d42d73f5676
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.
Transparency log