pymidil
The Python SDK for Midil Observatory — every event, accounted for.
Midil is an observability control plane for event-driven systems: it traces each
message from producer to consumer, keeps the dead-letter ledger, and gives your
team the levers — pause, throttle, drain — in one console. pymidil is how your
services plug into it: instrument the consumers you already run, or build on the
SDK's event runtime and get telemetry, idempotency, and retry/DLQ semantics
built in.
How it fits together
graph LR
subgraph Your services
P["Producer<br/>(TelemetryProducerHook)"]
C1["pymidil consumer<br/>(SQSConsumer + subscriber)"]
C2["Existing consumer<br/>(ConsumerObserver — any broker)"]
end
B[(Broker<br/>SQS · Kafka · …)]
subgraph Midil Observatory
API["Observatory API<br/>(X-Api-Key)"]
Console["Console — traces · DLQ ledger ·<br/>incidents · idempotency · consumer control"]
end
P --> B --> C1 & C2
P & C1 & C2 -- telemetry --> API --> Console
Console -- "pause / throttle / drain" --> API -- control state --> C1 & C2
Every observation lands in your organization's isolated tenant, routed by the
API key. From the console you get the end-to-end trace graph (one OrderPaid
fanning into four branches, the red leg obvious), the dead-letter ledger grouped
by failure class, incident investigation, idempotency analysis — and live
control over the consumers themselves. Replay of dead-lettered events is on the
roadmap (the console marks it "soon"; nothing in the SDK pretends otherwise).
Install
pip install pymidil
Modular — install only what your service needs:
| Extra | Installs | Use when you need |
|---|---|---|
pymidil[auth] |
httpx, pyjwt | HTTP telemetry sink, Cognito auth, JWT verification |
pymidil[web] |
fastapi, starlette, uvicorn | REST APIs, middleware, pagination |
pymidil[aws] |
aioboto3 | SQS producers/consumers, EventBridge scheduling |
pymidil[redis] |
redis | Redis-backed event streaming |
pymidil[mongodb] |
pymongo | MongoDB cursor pagination |
pymidil[cli] |
click, rich, cookiecutter | Project scaffolding and service launcher |
pymidil[full] |
everything | — |
Requires Python 3.12+.
Observe a consumer you already run
Zero refactor: keep your broker client, your loop, your handler. Wrap each delivery in an observation and Midil gets the outcome, the timing, and the trace lineage — and you get console control back.
from pymidil.event.observability import ConsumerObserver
observe = ConsumerObserver(
observatory_url="https://api.midil.io",
api_key="mo_…", # issued in the console (org settings → API keys)
consumer="orders-worker", # the name the Observatory shows — and controls
broker="kafka", # a label; any broker works
)
async for record in kafka_consumer: # your existing loop
if not (await observe.control.get()).state.should_pull:
continue # paused from the console
async with observe(record.key, "OrderPlaced", headers=record.headers):
await handle_order(record) # your code, untouched
Per delivery, those lines buy: a telemetry envelope (success / retrying /
failed / dlq) identical to what a pymidil-managed consumer emits, wall-clock
processing time, W3C trace continuity from the message headers — the lineage
graph's cross-service edges — and pause/throttle/drain honored straight from
the console. ProducerObserver is the emit-side counterpart.
Or build on the event runtime
Subscribers are classes with a lifecycle, not callbacks. Retry-vs-dead-letter is decided by the exception you raise, idempotency is a policy you attach, and telemetry is a hook — each concern snaps on independently.
from pymidil.event import (
EventSubscriber, SQSConsumer, SQSConsumerEventConfig, TelemetryDispatchHook,
)
from pymidil.event.exceptions import RetryableEventError
from pymidil.event.idempotency import IdempotencyPolicy, InMemoryIdempotencyStore
from pymidil.event.observability.sinks.http import HttpTelemetrySink
class OrderSubscriber(EventSubscriber):
async def handle(self, event) -> None:
if upstream_busy():
raise RetryableEventError("throttled") # → redelivered with backoff
await process(event) # any other error → DLQ
consumer = SQSConsumer(SQSConsumerEventConfig(type="sqs", queue_url=..., dlq_url=...))
consumer.add_hook(TelemetryDispatchHook(
HttpTelemetrySink("https://api.midil.io", api_key="mo_…"),
source_service="orders-svc",
broker="sqs",
))
consumer.use_idempotency(IdempotencyPolicy(InMemoryIdempotencyStore(), key_fn=...))
consumer.subscribe(OrderSubscriber())
The sqs_fanout example is the full tour: one
OrderPaid fanning out into four services on LocalStack SQS, a deliberately
flaky branch dead-lettering ~30% of the time, and the whole shape visible in
the Observatory's trace graph.
Authentication
Services authenticate to the Observatory with an API key (mo_…), created
by an organization admin in the console and passed as api_key= (sent as
X-Api-Key). The key selects your organization — telemetry can only land in
your own tenant — and is scoped to exactly two capabilities: writing telemetry
and reading control state. A leaked key can never touch the console or
management surfaces.
The supporting toolkit
The SDK also ships the service plumbing the event runtime grew out of — usable on their own:
| Module | What it gives you |
|---|---|
pymidil.auth |
Cognito client-credentials auth (outbound) and JWT verification (inbound), behind pluggable interfaces |
pymidil.client |
HTTPX-based client with retry, backoff, and auth-header injection |
pymidil.web |
MidilAPI (FastAPI subclass), auth middleware, cursor/offset pagination, JSON:API serialization |
pymidil.event.scheduler |
One-off and future-dated events via AWS EventBridge |
pymidil.logger |
Structured logging with sensitive-data masking |
midil CLI |
midil init (scaffold a service), midil launch, midil version |
Examples
Runnable examples live in examples/ — see the
examples README. Start with:
event/sqs_fanout/— the end-to-end tour: fan-out topology, retries, DLQ, idempotency, telemetry, console control.event/kafka_observer.py— zero-refactor observation of an existing aiokafka consumer.
Design principles
- Async-first. Every component is built for
asyncio— no sync wrappers. - Observation is data, control is explicit. Telemetry describes what happened; your consumer only changes behavior where you poll control state.
- One semantic core. An observed consumer and a pymidil-managed one emit the same envelopes — the Observatory can't tell the difference, by design.
- Interface-driven. Sinks, control sources, subscribers, auth providers, retry strategies — all swappable abstract bases.
- Opt-in by default. Nothing is installed you didn't ask for.
License
Release files for pymidil 0.2.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 | |
|---|---|---|---|
| pymidil-0.2.0.tar.gz | 93.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pymidil-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 234.1 kB
Release files / pymidil-0.2.0.tar.gz
| Download URL | pymidil-0.2.0.tar.gz |
|---|---|
| Size | 93.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b3f7a2caa12c65e433b91a3f45c6cd96bde0ad171824099b1ed36f6c2bb0b6b0
|
|
BLAKE2b-256 checksum How to use checksums |
4911c4e70737c2054faf9c8da8a3d1ca323ae8abff20e6a48f474cfa844d58e7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.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 Jul 23, 2026.
Transparency logRelease files / pymidil-0.2.0-py3-none-any.whl
| Download URL | pymidil-0.2.0-py3-none-any.whl |
|---|---|
| Size | 141.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
47c4e8167c91b1fc292e245eaf541e3dfe432f1b620172f8b853f8dd3026ed2a
|
|
BLAKE2b-256 checksum How to use checksums |
7e1d15220437c70b1a86f510ed4a71e975949b0d7cd15268ae07521bdbcb150e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.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 Jul 23, 2026.
Transparency log