Skip to content

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.

ConstraintDetails
No filesystem persistenceThe WASM linear memory is reset per request. Use KV/R2/D1 for storage.
No network socketsStandard socket module is unavailable. Use pymode.tcp and pymode.http instead.
No threadsthreading is disabled (--disable-threads). Use pymode.parallel for real parallelism via child DOs.
No subprocessos.system, subprocess, os.fork are not available in WASI.
No signalsSignal handling is emulated (-lwasi-emulated-signal), limited functionality.
30s CPU limitPer-request CPU time is capped at 30 seconds (CF Workers limit).
128MB memoryPer-DO memory limit. Large data processing should use pymode.parallel to distribute across DOs.
10MB bundle sizeTotal compressed worker size. PyMode itself uses ~2-3MB gzipped, leaving ~7MB for your code and dependencies.

PyMode bundles a subset of CPython’s stdlib as string constants in the worker. These are the modules available in the WASM runtime:

CategoryModules
Encodingsencodings, encodings.aliases, encodings.utf_8, encodings.ascii, encodings.latin_1
JSONjson
Regexre
Collectionscollections, collections.abc, functools, operator
Typesenum, types, typing, abc
Corewarnings, contextlib, dataclasses, copy, copyreg, weakref, _weakrefset
Textstring, textwrap
Cryptobase64, hashlib, hmac, secrets
Numbersrandom, bisect, heapq, numbers, fractions, decimal
Date/Timedatetime, calendar
Pathfnmatch, glob, pathlib
URLurllib, urllib.parse, ipaddress
Importimportlib, importlib.abc, importlib.machinery, importlib.util
Serializationpickle, _compat_pickle, struct
I/Oio, _pyio
Misckeyword, 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

ModuleImplementationNotes
binasciiPure Python (lib/polyfills/binascii.py)Replaces the C extension. Covers hex, base64, crc32, uu encoding.
socketPure Python (lib/polyfills/socket.py)Routes through pymode.tcp host imports. Provides constants, getaddrinfo, create_connection.
threadingPure Python (lib/polyfills/threading.py)No-op locks within a DO. Thread.start() spawns child DOs via pymode.parallel for real parallelism.
loggingPure Python (lib/polyfills/logging/)Threading-free logging that writes to stderr. Full API: getLogger, basicConfig, handlers.
multiprocessingPure Python (lib/polyfills/multiprocessing/)cpu_count() returns 1, exception classes. Use pymode.parallel for real parallelism.
sslPure Python (lib/polyfills/ssl.py)TLS termination handled by CF’s edge — Python code doesn’t need raw TLS.
zlibPure Python (lib/polyfills/zlib.py)Minimal compression polyfill for packages that import zlib at module level.

These stdlib modules require OS features that WASI doesn’t provide:

ModuleReason
socketPolyfilled — routes through pymode.tcp host imports.
sslPolyfilled — TLS termination handled by CF’s edge.
subprocessNo process spawning in WASI.
multiprocessingPolyfilled — cpu_count() returns 1. Use pymode.parallel for real parallelism via child DOs.
threadingPolyfilled — no-op locks within a DO. Thread.start() spawns child DOs via pymode.parallel.
sqlite3C extension not compiled for WASM. Use D1 binding instead.
ctypesNo dynamic linking for arbitrary shared libraries.
tkinterNo GUI in Workers.
cursesNo terminal in Workers.
readlineNo interactive terminal.
mmapNot available in WASI.
fcntl, termios, grp, pwdUnix-specific, not in WASI.
select, selectorsPolyfilled — selectors bundled in stdlib.
signalEmulated only, limited functionality.
resourceNo resource limits API in WASI.

These modules have been added to the stdlib bundle since initial release:

ModuleAdded for
html, html.parser, html.entitiesbeautifulsoup4, web scraping
email (full package)requests, urllib3, http.client
http, http.client, http.cookiejarrequests, httpx, urllib3
xml, xml.sax, xml.etree, xml.parserslangchain, defusedxml
asyncio (full package)pydantic, typing_extensions
argparsenumpy, click
localehttpx, distro
tempfile, shutilmany packages
importlib.metadata, importlib.resourcespackage metadata discovery
concurrent.futurestenacity, langsmith
zoneinfopydantic datetime
tomllibpydantic, project configs
configparservarious packages
sysconfigpydantic, setuptools
unittestpyparsing

Requesting a module: If you need a stdlib module that’s not bundled, open an issue or add it to BOOT_FILES in scripts/generate-stdlib-fs.py.

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, typing module

Not supported:

  • async/await at the handler level (use Asyncify for I/O instead)
  • asyncio event loop (no I/O multiplexing in WASI)

These packages have conformance tests running in the actual workerd runtime:

PackageTypeTestsNotes
pydanticRust ext (compiled to WASM)4 passingpython-pydantic-core.wasm variant
fastapiPure Python (ASGI)1 passingRequest validation, typed APIs
langchain-corePure Python5 passingMessages, documents, utils, serialization
langgraphPure Python5 passingStateGraph, compile, invoke, conditional edges
instructorPure Python1 passingStructured LLM output via pydantic
openai SDKPure Python1 passingClient types importable
numpyC ext (compiled to WASM)8 passingpython-numpy.wasm variant, arrays, FFT, random
jinja2Pure Python3 passingTemplates, loops, filters, inheritance
requestsPure Python1 passingSession building, request construction
httpxPure Python1 passingRequest building, URL parsing
beautifulsoup4Pure Python2 passingHTML parsing, CSS selectors
pyyamlPure Python fallback2 passingYAML parse/dump
clickPure Python1 passingCLI command creation
attrsPure Python1 passingValidators, asdict
starlettePure Python1 passingASGI routing

Total: 261 tests across all test suites.

These packages have native extensions replaced with pure-Python polyfills:

PackageOriginalPolyfill
uuid_utilsRust (_uuid_utils)stdlib uuid + time + os.urandom
xxhashC (_xxhash)hashlib.sha256 as stand-in
ormsgpackRust (ormsgpack)Wraps pure-Python msgpack
jiterRust (jiter.jiter)stdlib json
markupsafeC (_speedups)Pure Python fallback (built-in)
multiprocessingC (process model)Polyfill: cpu_count()=1, exception classes

Architecture: Each Package Gets Its Own DO

Section titled “Architecture: Each Package Gets Its Own DO”

Memory constraints (128MB Worker / 256MB DO) apply per DO, not globally. Heavy packages run in dedicated DOs communicating via Worker RPC:

Orchestrator DO Package DOs (each 256MB)
┌────────────┐ ┌─────────────┐
│ Your app │───RPC───▶│ numpy DO │
│ routing │ └─────────────┘
│ logic │───RPC───▶│ pandas DO │
│ │ └─────────────┘
└────────────┘───RPC───▶│ langchain │
│ DO │
└─────────────┘

This means even pandas (~100MB) can work — it just needs its own DO. The threading.Thread polyfill already spawns child DOs via pymode.parallel.

PackageReason
tensorflowNeeds GPU, ~500MB, C++/CUDA runtime
torchNeeds GPU, ~2GB, C++/CUDA runtime
scipyNeeds BLAS/LAPACK (Fortran), complex C

For ML inference, use Workers AI (env.AI.run()) instead.