Why Build a Framework? WSGI & the Real Python Web Server Interface
Building a Web Framework
Chapter 1 ยท Why Build a Framework? WSGI & the Real Python Web Server Interface
Web Framework Internals studied how five real frameworks each answer four real questions — routing, templating, data access, middleware — by comparing their real syntax and quoting their real documentation. This course answers the identical four questions a genuinely different way: by building a real, small, working Python web framework from scratch, one capability at a time, ending with a real application running on top of it. Before any of that, though, there's a more basic real question to answer first — how does a Python function even become "a web application" that a real browser can talk to at all?
compiler1/compiler2, browserengine1/browserengine2, dbengine1/dbengine2, and oskernel1/oskernel2 projects already used.
The Pipeline This Course Builds
| Capability | What it does | Built in |
|---|---|---|
| Routing | A real path-matching engine with dynamic parameters, mirroring Web Framework Internals' own Chapter 2 | Chapter 2 |
| Request/response objects | A real, ergonomic wrapper around the raw WSGI interface this chapter builds | Chapter 3 |
| Templating | A real template engine — parsing, compiling, rendering — mirroring Chapter 4 | Chapter 4 |
| Middleware | A real onion-model pipeline, mirroring Chapter 8 | Chapter 5 |
| Sessions & cookies | Real state carried across genuinely stateless HTTP requests | Chapter 6 |
| A minimal ORM | Mapping real Python objects to real SQL, mirroring Chapter 6 | Chapter 7 |
| Error handling & dev server | A real development server with live reload | Chapter 8 |
| Static files & a CLI | Serving real files directly, and a real command-line tool for the framework itself | Chapter 9 |
The Real Interface Every Synchronous Python Framework Sits On
A Python program doesn't talk to the internet directly — a real web server (Apache, nginx, Gunicorn) handles the actual TCP connections and HTTP parsing, then needs some real, standardized way to hand a parsed request off to Python application code and get a response back. That standard is WSGI (the Web Server Gateway Interface, defined in PEP 3333) — and it's genuinely simple: a WSGI application is just a callable taking two arguments. PEP 3333's own real, minimal example:
This isn't a sketch — it's real, runnable code. Python's own standard library ships a genuine, working WSGI server (wsgiref) specifically for testing code exactly like this:
urllib.request) talking to a genuine, real HTTP server, produced entirely by a 6-line function following one, real, standardized interface. Every real Python web framework — Django, Flask, every one of Web Framework Internals' own routers built in Chapter 2 — ultimately reduces to something shaped like simple_app above, however many layers of routing, templating, and middleware sit in front of it.
The Real environ Dict: What a Server Actually Hands You
environ is a real, plain Python dict — per PEP 3333's own specification, a set of CGI-style variables describing the incoming request. Capturing it from a real request shows exactly what's really in there:
PATH_INFO and QUERY_STRING, verified here as real, plain strings sitting in a real dict, are exactly what Chapter 2's own router will parse to match a route, and exactly what a real request object (Chapter 3) will wrap into something more ergonomic than reading dict keys by hand. Nothing about routing or request objects is magic — it's all reachable, real data already sitting in environ the moment a request arrives.
start_response: A Callback, Not a Return Value
Web Framework Internals verified Rack's own real interface — a middleware and a full application both implement call(env), returning a real [status, headers, body] triplet. WSGI takes a genuinely different real approach: start_response is a callback, called from inside the application, before the body is returned. Getting the order wrong is a real, documented contract violation — and Python's own reference implementation doesn't fail silently:
wsgiref doesn't just document that start_response must be called first — it checks, at runtime, and raises a real AssertionError the instant the rule is broken, converting it into a genuine HTTP 500 rather than sending a malformed response or hanging. The identical real discipline applies to the body itself: returning a plain Python str instead of a list of real bytes objects — a genuinely easy mistake — is caught the same way, verified directly: AssertionError: write() argument must be a bytes instance. PEP 3333's own real requirement, "must return an iterable yielding zero or more bytestrings," isn't just a suggestion in the spec's own prose — it's an actively-checked runtime contract in Python's own reference server.
Real Frameworks Sit on the Identical Interface
Django's own real, default deployment file, wsgi.py, is a genuine, direct instance of exactly this pattern:
application, the module-level name a real WSGI server looks for, is a genuine, callable WSGI application — underneath Django's own real router, middleware stack, and ORM (all covered across Web Framework Internals' own Chapters 2–7), it still reduces to the identical (environ, start_response) shape simple_app used at the very top of this chapter.
WSGI's Real Limits, and Why This Course Still Builds on It
WSGI's own real successor, ASGI, exists for a real, documented reason — per ASGI's own official documentation, it's "a spiritual successor to WSGI, intended to provide a standard interface between async-capable Python web servers, frameworks, and applications." The same real documentation names the specific real gap directly: "WSGI applications are a single, synchronous callable that takes a request and returns a response; this doesn't allow for long-lived connections, like you get with long-poll HTTP or WebSocket connections."
Where This Course Is Headed
Chapter 2 builds a real routing engine on top of the environ dict verified in this chapter — real path matching and dynamic parameters, following the exact same real approach Web Framework Internals' own Chapter 2 built and verified.
Hands-On Exercises
Write a real WSGI application that reads PATH_INFO from environ and echoes it back in the response body (e.g. "You requested: /users/7"), and verify it against three genuinely different real paths using urllib.request against a real wsgiref server.
๐ View solutionWrite a real WSGI application that calls start_response correctly but returns a plain Python str instead of a list of bytes, run it against a real wsgiref server, and report the exact real exception raised server-side and the real HTTP status the client receives.
๐ View solutionUsing this chapter's own real start_response callback model and Web Framework Internals' own quoted Rack call(env) -> [status, headers, body] return-triplet model, explain in your own words which of the two interfaces makes a "forgot to call start_response" bug easier to catch automatically, and why.
๐ View solutionChapter 1 Quick Reference
- WSGI โ the real, standardized interface (PEP 3333) between a Python web server and Python application code: a callable taking (environ, start_response)
- environ โ a real, plain dict of request data; verified real keys include REQUEST_METHOD, PATH_INFO, QUERY_STRING, SERVER_PROTOCOL
- start_response โ a real callback, not a return value; verified to raise a real AssertionError if called too late or never called at all
- The body โ must be real bytes, not str; verified as an actively-enforced runtime contract, not just documentation
- Real frameworks confirm this โ Django's own real wsgi.py/get_wsgi_application() reduces to the identical callable shape
- WSGI vs. ASGI โ WSGI is a single, synchronous callable; ASGI is its real, documented async successor, needed for WebSockets/long-lived connections โ this course deliberately stays on WSGI
- Next chapter: A routing engine from scratch โ path matching & dynamic parameters