WebOb is a self-contained Python package that wraps the WSGI environment and response contract behind convenient, high-level objects. Instead of manipulating the raw environ dictionary and hand-crafting start-resposne tuples, you interact with rich Request and Response classes that expose headers, form data, cookies, status codes, and more as plain Python attributes.
Why WebOb?
- HTTP semantics are mapped to native data structures (dicts, lists, bytes).
- Battle-testeed code hides the quirky corners of PEP 3333.
- Zero open issues; regressions are fixed quickly.
- Full statement and branch coverage in the test suite.
- No external dependencies beyond the standard library.
- Runs on Python 3.7+.
From Raw WSGI to WebOb
A minimal WSGI callable looks like this:
from wsgiref.simple_server import make_server
def bare_wsgi(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/plain')])
lines = ['Hello from raw WSGI']
for k, v in sorted(environ.items()):
lines.append(f'{k}: {v}')
return ['\n'.join(lines).encode('utf-8')]
if __name__ == '__main__':
port = 8000
httpd = make_server('', port, bare_wsgi)
print(f'Listening on http://0.0.0.0:{port}')
httpd.serve_forever()
Query it with curl http://localhost:8000 and you’ll see the headers and CGI variables dumped back.
Replacing Boilerplate with Request and Response
WebOb’s objects absorb the repetitive parts:
from wsgiref.simple_server import make_server
from webob import Request, Response
def webob_app(environ, start_response):
req = Request(environ)
payload = ['Hello from WebOb']
payload.extend(f'{k}: {v}' for k, v in sorted(req.environ.items()))
resp = Response(text='\n'.join(payload), content_type='text/plain')
return resp(environ, start_response)
if __name__ == '__main__':
make_server('', 8000, webob_app).serve_forever()
The output is identical to the raw version, but the code is shorter and clearer.
Using the wsgify Decorator
The webob.dec.wsgify decorator hides the environ/start_response signature entirely. Your functon receives a Request and returns a Response (or something convertible to one).
from wsgiref.simple_server import make_server
from webob import Request, Response
from webob.dec import wsgify
@wsgify
def decorated_app(req: Request) -> Response:
body = ['Hello from wsgify']
body += [f'{k}: {v}' for k, v in sorted(req.environ.items())]
return Response('\n'.join(body), content_type='text/plain')
if __name__ == '__main__':
make_server('', 8000, decorated_app).serve_forever()
Again, curl http://localhost:8000 yields the same result, yet the application layer is now a single, testable function.