Skip to content

Python APIs for GIL and Free-Threaded CPython

dexpot

SYNCHRONOUS PYTHON APIS

Synchronous APIs. GIL or free-threaded.

Keep plain synchronous handlers while Dexpot adapts bounded admission and scheduling to standard GIL or free-threaded CPython.

Dexpot lockup: synchronous Python APIs adapt execution to the interpreter.

GET STARTED

Install Dexpot

pip install "dexpot[cli]"Quick start guide

EXPLORE THE MECHANISM

Try bounded CRUD behavior.

Explore the existing playground’s bounded item operations, response outcomes, and recorded loopback evidence.

Open the playground

Browser-local preview. JavaScript computes bounded results in this tab; it does not execute Python or send requests. Recorded loopback timings are separate evidence, not this preview’s runtime.

Dexpot keeps the route handler as an ordinary synchronous Python function. This is the same minimal application shipped in examples/minimal.py:

from msgspec import Struct
from dexpot import Dex
app = Dex()
class ItemOut(Struct):
id: int
name: str
price: float
@app.get("/items/{item_id}", response=ItemOut)
def get_item(item_id: int) -> ItemOut:
"""Return one typed item."""
return ItemOut(id=item_id, name=f"item-{item_id}", price=float(item_id))

Save it as examples/minimal.py, install the CLI extra, and start the public server command from the repository root:

Terminal window
pip install "dexpot[cli]"
PYTHONPATH=examples dexpot serve minimal:app --host 127.0.0.1 --port 8000

In another terminal, call the actual HTTP route:

Terminal window
curl -i http://127.0.0.1:8000/items/7

The example responds with the typed item for that path. The homepage preview remains browser-local; this command starts the real Python server.

Dexpot keeps application code synchronous while changing admission and scheduling around the interpreter that runs it. The original execution diagram is a conceptual overview, not a literal request sequence.

Runtime Serving model Overload behavior
Free-threaded CPython (GIL disabled) One process; each admitted connection owns a thread A process-wide active-connection cap sheds excess work before thread creation
CPython with the GIL enabled A bounded pool of connection-owning threads A bounded queue sheds excess work with 503
CPython with the GIL enabled and workers POSIX SO_REUSEPORT processes, each with its own bounded pool Each worker sheds independently
Conceptual overview of Dexpot's execution and scheduling architecture; the arrows are not a literal request sequence.
Conceptual overview of Dexpot’s execution architecture; not a literal request sequence. Open full size.

The new supporting diagram explains v0.7.1 startup and connection ownership without replacing the execution overview above. Dexpot selects its serving mode when serve() starts, after application imports, using the actual GIL state. The application handler syntax does not change.

Dexpot worker lifecycle: serve-time GIL selection, macOS FT owner-scoped scheduling, completed-response CPU fairness and capacity reclaimed after owner exit.
Connection close and worker exit are separate events: the FT capacity slot is reclaimed only after the owner has exited and been joined. Read the complete text explanation or open full size.

On macOS with the GIL disabled, workers can use an owner-scoped scheduling policy and offer other workers a turn after at least 1 ms of accumulated CPU work, at a completed continuing-response boundary. Waiting does not use that budget; application handlers are not preempted. Successful policy changes are restored before worker exit. Linux and GIL-enabled execution retain their existing paths; the main/acceptor thread policy is unchanged.

In v0.7.0, response=ItemOut also checks the returned data and projects it onto the declared public fields before sending success bytes. Malformed or non-finite output returns a sanitized 500. Leave response=None for generic JSON, or use RawResponse on an unchecked route for bytes. The response-contract illustration shows the public-field and rejection paths.

Response contract illustration: checked public-field projection, generic encoding for response=None, raw bytes, rejection of malformed or non-finite values, and factory/default caveats.
Checked responses project declared public fields; generic and raw response paths remain distinct, and unsupported schemas are limited. Open full size.
  • Plain synchronous handlers without an event loop or coroutine bridge.
  • Compiled endpoint plans for route matching, argument binding, path conversion, and msgspec codecs.
  • Interpreter-adaptive scheduling selected when serve() starts, after application imports, from the actual GIL state.
  • A bounded HTTP core for parsing, routing, scheduling, draining, and response writes.
  • An optional parser-only Rust extension without moving sockets, routing, handlers, or scheduling out of Python.

Dexpot is part of ModePot, an open-source family of modern frameworks, tools, engines, and games.