Limitations
PyMode runs upstream CPython 3.13 compiled to wasm32-wasi inside Cloudflare Workers Durable Objects. This works well for most Python code, but the WASM/WASI/DO environment has inherent constraints.
Platform Constraints
Section titled “Platform Constraints”| Constraint | Details |
|---|---|
| No filesystem persistence | The WASM linear memory is reset per request. Use KV/R2/D1 for storage. |
| No network sockets | Standard socket module is unavailable. Use pymode.tcp and pymode.http instead. |
| No threads | threading is disabled (--disable-threads). Use pymode.parallel for real parallelism via child DOs. |
| No subprocess | os.system, subprocess, os.fork are not available in WASI. |
| No signals | Signal handling is emulated (-lwasi-emulated-signal), limited functionality. |
| 30s CPU limit | Per-request CPU time is capped at 30 seconds (CF Workers limit). |
| 128MB memory | Per-DO memory limit. Large data processing should use pymode.parallel to distribute across DOs. |
| 10MB bundle size | CF Workers’ paid-tier limit on compressed deploy. A typical pymode deploy produces a 6-8 MB gzipped wasm depending on which packages are AOT-preimported; the remainder is your project sources + site-packages. Heavy native deps (numpy/pandas) consume more of the budget. |
Bundled Stdlib Modules
Section titled “Bundled Stdlib Modules”PyMode bundles a subset of CPython’s stdlib as string constants in the worker. These are the modules available in the WASM runtime:
Always Available (bundled in stdlib-fs)
Section titled “Always Available (bundled in stdlib-fs)”| Category | Modules |
|---|---|
| Encodings | encodings, encodings.aliases, encodings.utf_8, encodings.ascii, encodings.latin_1 |
| JSON | json |
| Regex | re |
| Collections | collections, collections.abc, functools, operator |
| Types | enum, types, typing, abc |
| Core | warnings, contextlib, dataclasses, copy, copyreg, weakref, _weakrefset |
| Text | string, textwrap |
| Crypto | base64, hashlib, hmac, secrets |
| Numbers | random, bisect, heapq, numbers, fractions, decimal |
| Date/Time | datetime, calendar |
| Path | fnmatch, glob, pathlib |
| URL | urllib, urllib.parse, ipaddress |
| Import | importlib, importlib.abc, importlib.machinery, importlib.util |
| Serialization | pickle, _compat_pickle, struct |
| I/O | io, _pyio |
| Misc | keyword, reprlib, traceback, linecache, tokenize, token, csv, pprint |
Built-in C Modules (compiled into python.wasm)
Section titled “Built-in C Modules (compiled into python.wasm)”These are C extension modules linked directly into the WASM binary:
array, _asyncio, _bisect, _contextvars, _csv, _heapq, _json, _pickle, _random, _struct, _zoneinfo, math, cmath, _statistics, _datetime, _decimal, _md5, _sha1, _sha2, _sha3, _blake2, _codecs, _collections, errno, _io, itertools, _sre, _thread, time, _typing, _weakref, _abc, _functools, _locale, _operator, _stat, _symtable, unicodedata, pyexpat, _elementtree
Polyfilled Modules
Section titled “Polyfilled Modules”| Module | Implementation | Notes |
|---|---|---|
binascii | Pure Python (lib/polyfills/binascii.py) | Replaces the C extension. Covers hex, base64, crc32, uu encoding. |
socket | Pure Python (lib/polyfills/socket.py) | Routes through pymode.tcp host imports. Provides constants, getaddrinfo, create_connection. |
threading | Pure Python (lib/polyfills/threading.py) | No-op locks within a DO. Thread.start() spawns child DOs via pymode.parallel for real parallelism. |
logging | Pure Python (lib/polyfills/logging/) | Threading-free logging that writes to stderr. Full API: getLogger, basicConfig, handlers. |
multiprocessing | Pure Python (lib/polyfills/multiprocessing/) | cpu_count() returns 1, exception classes. Use pymode.parallel for real parallelism. |
ssl | Pure Python (lib/polyfills/ssl.py) | TLS termination handled by CF’s edge — Python code doesn’t need raw TLS. |
zlib | Pure Python (lib/polyfills/zlib.py) | Minimal compression polyfill for packages that import zlib at module level. |
Not Available
Section titled “Not Available”These stdlib modules require OS features that WASI doesn’t provide:
| Module | Reason |
|---|---|
socket | Polyfilled — routes through pymode.tcp host imports. |
ssl | Polyfilled — TLS termination handled by CF’s edge. |
subprocess | No process spawning in WASI. |
multiprocessing | Polyfilled — cpu_count() returns 1. Use pymode.parallel for real parallelism via child DOs. |
threading | Polyfilled — no-op locks within a DO. Thread.start() spawns child DOs via pymode.parallel. |
sqlite3 | C extension not compiled for WASM. Use D1 binding instead. |
ctypes | No dynamic linking for arbitrary shared libraries. |
tkinter | No GUI in Workers. |
curses | No terminal in Workers. |
readline | No interactive terminal. |
mmap | Not available in WASI. |
fcntl, termios, grp, pwd | Unix-specific, not in WASI. |
select, selectors | Polyfilled — selectors bundled in stdlib. |
signal | Emulated only, limited functionality. |
resource | No resource limits API in WASI. |
Now Bundled (previously missing)
Section titled “Now Bundled (previously missing)”These modules have been added to the stdlib bundle since initial release:
| Module | Added for |
|---|---|
html, html.parser, html.entities | beautifulsoup4, web scraping |
email (full package) | requests, urllib3, http.client |
http, http.client, http.cookiejar | requests, httpx, urllib3 |
xml, xml.sax, xml.etree, xml.parsers | langchain, defusedxml |
asyncio (full package) | pydantic, typing_extensions |
argparse | numpy, click |
locale | httpx, distro |
tempfile, shutil | many packages |
importlib.metadata, importlib.resources | package metadata discovery |
concurrent.futures | tenacity, langsmith |
zoneinfo | pydantic datetime |
tomllib | pydantic, project configs |
configparser | various packages |
sysconfig | pydantic, setuptools |
unittest | pyparsing |
Requesting a module: If you need a stdlib module that’s not bundled, open an issue or add it to the bundle list in
scripts/generate-stdlib-fs.ts.
Python Language Support
Section titled “Python Language Support”Full CPython 3.13 language support including:
- All syntax features (match/case, walrus operator, f-strings, etc.)
- Generators, async generators, coroutines
- Decorators, metaclasses, descriptors
- Exception groups,
ExceptionGroup - Type hints,
typingmodule
Not supported:
async/awaitat the handler level (use Asyncify for I/O instead)asyncioevent loop (no I/O multiplexing in WASI)
Package Support
Section titled “Package Support”C-extension packages: current state
Section titled “C-extension packages: current state”pymode deploy auto-detects when your project depends on a C-extension
package and, if a variant wasm exists, uses that variant directly (fast
deploy — no AOT rebuild). The numpy variant is wired and verified
end-to-end as of this writing.
| Package | How it’s served | Verified deployable today |
|---|---|---|
| numpy | python-numpy.wasm variant | ✅ (linalg stubbed) |
| Pillow | python-pillow.wasm variant | ✅ |
| pydantic_core | python-pydantic-core.wasm variant | ✅ |
| pyyaml | pure-Python fallback (uv pip) | ✅ |
| multidict / yarl / frozenlist / propcache | pure-Python fallback (uv pip) | likely (same pattern as pyyaml) |
| msgpack | pure-Python fallback (uv pip) | likely |
| markupsafe | statically linked | yes |
| regex / xxhash | statically linked | yes |
| pandas | needs numpy + Cython compile pass | no |
| scipy | needs Fortran cross-compile | no |
| scikit-learn | depends on scipy | no |
| lxml | needs libxml2 + libxslt cross-compile | no |
| cryptography | needs OpenSSL cross-compile | no |
| orjson / ujson | Rust/C-only (no pure-Python fallback) | no |
| cffi | runtime C-compile model incompatible with WASM | no |
numpy caveats: the variant ships _multiarray_umath (array core +
random + FFT) but does not ship _umath_linalg. Linear algebra
functions in numpy.linalg are stubbed and raise ImportError if
called. Everything else (arrays, math, FFT, random) works.
For projects needing pandas / scipy / scikit-learn / lxml / cryptography, Cloudflare’s official Python Workers ships pre-built wheels for those via Pyodide — that’s the working answer today. Closing pymode’s catalog gap is a multi-month engineering project; we do it package-by-package as needed.
Tested & Working
Section titled “Tested & Working”These packages have conformance tests running in the actual workerd runtime:
| Package | Type | Tests | Notes |
|---|---|---|---|
| pydantic | Rust ext (compiled to WASM) | 4 passing | python-pydantic-core.wasm variant |
| fastapi | Pure Python (ASGI) | 1 passing | Request validation, typed APIs |
| langchain-core | Pure Python | 5 passing | Messages, documents, utils, serialization |
| langgraph | Pure Python | 5 passing | StateGraph, compile, invoke, conditional edges |
| instructor | Pure Python | 1 passing | Structured LLM output via pydantic |
| openai SDK | Pure Python | 1 passing | Client types importable |
| numpy | C ext (compiled to WASM) | 8 passing | python-numpy.wasm variant, arrays, FFT, random |
| jinja2 | Pure Python | 3 passing | Templates, loops, filters, inheritance |
| requests | Pure Python | 1 passing | Session building, request construction |
| httpx | Pure Python | 1 passing | Request building, URL parsing |
| beautifulsoup4 | Pure Python | 2 passing | HTML parsing, CSS selectors |
| pyyaml | Pure Python fallback | 2 passing | YAML parse/dump |
| click | Pure Python | 1 passing | CLI command creation |
| attrs | Pure Python | 1 passing | Validators, asdict |
| starlette | Pure Python | 1 passing | ASGI routing |
The vitest suite covers these against the actual workerd runtime. Run
npm test to see the current count and pass/fail status for your
local environment.
Polyfilled C/Rust Extensions
Section titled “Polyfilled C/Rust Extensions”These packages have native extensions replaced with pure-Python polyfills:
| Package | Original | Polyfill |
|---|---|---|
uuid_utils | Rust (_uuid_utils) | stdlib uuid + time + os.urandom |
xxhash | C (_xxhash) | hashlib.sha256 as stand-in |
ormsgpack | Rust (ormsgpack) | Wraps pure-Python msgpack |
jiter | Rust (jiter.jiter) | stdlib json |
markupsafe | C (_speedups) | Pure Python fallback (built-in) |
multiprocessing | C (process model) | Polyfill: cpu_count()=1, exception classes |
Memory budget
Section titled “Memory budget”A single Durable Object has a 128 MB memory budget. Most pure-Python packages run comfortably; CPython itself plus a typical handler costs ~20-40 MB depending on imports. Packages that allocate large arrays (pandas, numpy) reach the limit faster.
The pymode.parallel API spawns child DOs for compute parallelism —
each child has its own memory budget. This pattern can in principle
fan out work that’s too large for one DO, but isn’t automatic; you
opt in by calling spawn() / gather() from your handler.
Won’t Work in WASM (any DO)
Section titled “Won’t Work in WASM (any DO)”| Package | Reason |
|---|---|
| tensorflow | Needs GPU, ~500MB, C++/CUDA runtime |
| torch | Needs GPU, ~2GB, C++/CUDA runtime |
| scipy | Needs BLAS/LAPACK (Fortran), complex C |
For ML inference, use Workers AI (env.AI.run()) instead.