The compatibility page is clear about services and runtime APIs, and it names what is planned (D1, Workflows, Queues) and what is not (KV, R2, Cache API, cron triggers). Python Workers appear in neither list, so I can't tell whether this is deferred or deliberately out of scope.
Asking because the compat page's own scope rule seems to point toward "in scope": Python Workers aren't a separate Cloudflare primitive the way KV or R2 are. They're the same Durable Objects and module-Worker surface celld already implements, reached from a different language. A Python DO gets the same SQLite storage, alarms, and hibernatable WebSockets.
What happens today
I have Python Durable Objects that run on workerd, deployed with pywrangler. A minimal config:
name = "example"
main = "entry.py"
compatibility_date = "2025-11-17"
compatibility_flags = ["python_workers"]
[[durable_objects.bindings]]
name = "COORDINATOR"
class_name = "ProductionCoordinator"
[[migrations]]
tag = "v1"
new_sqlite_classes = ["ProductionCoordinator"]
Every key here is on celld's accepted list, and python_workers is accepted-without-effect as an unmodeled flag. But celld deploy bundles with esbuild, so a .py entry point can't produce a module.
This is the "loud failure" the compat page asks for in the config layer and, as far as I can tell, not in the entry-point layer — the config validates, then esbuild is what complains. If Python is out of scope, rejecting python_workers (or a non-JS main) by name at deploy time would match how the other unsupported keys behave.
Why it looks harder than #130
#130 asks for Rust, which compiles to Wasm, and celld already has V8's Wasm "without restrictions". Python isn't that shape. workerd ships the Pyodide runtime, the shim that maps a .py module onto fetch/alarm/webSocketMessage, and the glue that presents env bindings, DO stubs, and Request/Response as Python objects. That's a real layer, not a bundling change.
It also looks awkward against the differential-testing promise in docs/testing.md: "identical bytes on workerd and celld" is a stronger claim to hold when a pinned Pyodide version sits between the source and the runtime.
So I'm not filing this as a feature request. I'd like to know which of these it is:
- In scope, not yet built.
- Out of scope — the Pyodide layer is Cloudflare platform surface, not Durable Objects surface.
- Undecided, and gated on demand.
Happy to test against real Python DO workloads if it's ever built, and happy to send a docs PR recording the answer on the compat page either way — an explicit "not planned" entry would have answered this without an issue.
Drafted with Claude, which read the compat page more carefully than I did. I checked it against the source; anything wrong here is still mine. 😉
The compatibility page is clear about services and runtime APIs, and it names what is planned (D1, Workflows, Queues) and what is not (KV, R2, Cache API, cron triggers). Python Workers appear in neither list, so I can't tell whether this is deferred or deliberately out of scope.
Asking because the compat page's own scope rule seems to point toward "in scope": Python Workers aren't a separate Cloudflare primitive the way KV or R2 are. They're the same Durable Objects and module-Worker surface celld already implements, reached from a different language. A Python DO gets the same SQLite storage, alarms, and hibernatable WebSockets.
What happens today
I have Python Durable Objects that run on workerd, deployed with
pywrangler. A minimal config:Every key here is on celld's accepted list, and
python_workersis accepted-without-effect as an unmodeled flag. Butcelld deploybundles with esbuild, so a.pyentry point can't produce a module.This is the "loud failure" the compat page asks for in the config layer and, as far as I can tell, not in the entry-point layer — the config validates, then esbuild is what complains. If Python is out of scope, rejecting
python_workers(or a non-JSmain) by name at deploy time would match how the other unsupported keys behave.Why it looks harder than #130
#130 asks for Rust, which compiles to Wasm, and celld already has V8's Wasm "without restrictions". Python isn't that shape. workerd ships the Pyodide runtime, the shim that maps a
.pymodule ontofetch/alarm/webSocketMessage, and the glue that presentsenvbindings, DO stubs, andRequest/Responseas Python objects. That's a real layer, not a bundling change.It also looks awkward against the differential-testing promise in docs/testing.md: "identical bytes on workerd and celld" is a stronger claim to hold when a pinned Pyodide version sits between the source and the runtime.
So I'm not filing this as a feature request. I'd like to know which of these it is:
Happy to test against real Python DO workloads if it's ever built, and happy to send a docs PR recording the answer on the compat page either way — an explicit "not planned" entry would have answered this without an issue.
Drafted with Claude, which read the compat page more carefully than I did. I checked it against the source; anything wrong here is still mine. 😉