Strip a Python web application to nothing and it is a function taking a
dictionary and returning bytes. This one has no imports beyond the standard
library and answers two requests correctly:
GET /stock?code=PL-14gives200 OK,{'Content-Type': 'application/json', 'Content-Length': '32'}and a
body of{"code": "PL-14", "inStock": 3}GET /nopegives404 Not Foundandno such path
The function receives environ, a dictionary holdingPATH_INFO and QUERY_STRING among about thirty other
keys, and calls start_response(status, headers) before returning an
iterable of bytes. That contract is WSGI, it has not changed since 2003, and
every synchronous Python framework you have used is a program that eventually
calls something with this shape.
The four things a framework actually adds
Routing, so /stock/PL-14 reaches a function
with PL-14 as an argument rather than a string you slice by hand.
Parsing, so a form body or a JSON body arrives as a dict with
the content type already checked. Templating or serialising, so
the response is built rather than concatenated, which is also where the escaping
that stops a cross-site scripting bug lives. And the error path,
so an exception becomes a 500 with a traceback in the log rather than a
connection that closes.
Everything else a framework offers is optional and most of it is the fifth
thing: an object relational mapper, an admin, a migration system, users and
sessions. That is the real division, because it is the one that decides which
framework, rather than a preference about syntax.
Choosing, on the one axis that matters
Django brings the database layer, the admin and the auth system as one
decision, so a project that needs users, a schema that will change, and a screen
for staff to edit rows starts three weeks ahead. Flask and FastAPI bring the
first four things and leave the fifth to you, which is right when the data lives
somewhere else already or when the service answers other programs rather than
people.
FastAPI's own addition is that the function signature is the specification:
annotate the parameters and it validates the request and generates the API
documentation from the same lines. That is genuinely new work rather than a
different spelling, and it is why the choice between it and Flask is not
arbitrary. What either of them is talking to underneath is
the layer between your
objects and the tables.
What to run in 2026
ASGI is the interface to build on now, free and the standard, and it replaced
WSGI for anything holding a connection open: WSGI's contract has one request in
and one response out with nowhere for a websocket or a long poll to live. The
fifteen lines above are WSGI because it is the smaller thing to read, and the
ASGI version is the same shape with async and a receive channel.
uvicorn is what runs it, free and open source, and as of 2026 it is the usual
answer for Django, FastAPI and anything else ASGI. It replaced running a
framework's own development server in production, which every framework tells
you not to do on the page you skipped. The thing to understand rather than
memorise is that the server and the framework are separate programs, which is
why the traceback in the log sometimes belongs to one and sometimes to the
other.
Where this stalls people
The stall is a framework learned as a set of decorators, so its magic is
opaque and every unusual requirement becomes a search for the incantation.
Nothing above is magic: a request is a dictionary, a response is a status, some
headers and bytes, and a route is a lookup from a path to a function. A person
holding that model can read the framework's own source when the documentation
runs out, which is what the unusual requirement always needs.
The exercise worth doing once: write the fifteen lines, then add routing to
them, and stop. Twenty minutes, and every decorator afterwards is recognisable
as a thing you already built. The lifecycle those bytes travel through on the
way to a browser is
request in, response
out, and everything between, the environment it has to run inside is
pip, venv, and
keeping projects apart, and the language underneath is
readable, everywhere, and the usual
first language.