The architecture of Flask, x-rayed
Flask reads like a book — 24 components for a framework that has run a meaningful fraction of the web. The most interesting structural decision is recent: the sansio/ package, which splits the framework's pure logic from anything that touches I/O, is modern Flask quietly restructuring itself for a world beyond WSGI.
archsteer xray v0.4.1 against pallets/flask@36e4a82 (src/flask/) on 2026-07-05. Read-only static analysis — no code executed, no architecture rules declared. Structure, not judgment.Module dependency graph
Top 14 modules by connectivity; an arrow means "imports from".
graph LR
m0["__init__"]
m1["app"]
m2["blueprints"]
m3["cli"]
m4["ctx"]
m5["debughelpers"]
m6["globals"]
m7["helpers"]
m8["json"]
m9["sansio"]
m10["sessions"]
m11["templating"]
m12["typing"]
m13["wrappers"]
m0 --> m1
m0 --> m2
m0 --> m4
m0 --> m6
m0 --> m7
m0 --> m8
m0 --> m11
m0 --> m13
m1 --> m0
m1 --> m3
m1 --> m4
m1 --> m5
m1 --> m6
m1 --> m7
m1 --> m9
m1 --> m10
m1 --> m11
m1 --> m12
m1 --> m13
m2 --> m3
m2 --> m6
m2 --> m7
m2 --> m9
m2 --> m12
m2 --> m13
m3 --> m0
m3 --> m1
m3 --> m6
m3 --> m7
m3 --> m12
m4 --> m0
m4 --> m1
m4 --> m6
m4 --> m7
m4 --> m10
m4 --> m12
m4 --> m13
m5 --> m2
m5 --> m6
m5 --> m9
m5 --> m12
m5 --> m13
m6 --> m1
m6 --> m4
m6 --> m10
m6 --> m12
m6 --> m13
m7 --> m0
m7 --> m6
m7 --> m12
m7 --> m13
m8 --> m6
m8 --> m9
m8 --> m12
m8 --> m13
m9 --> m0
m9 --> m4
m9 --> m7
m9 --> m8
m9 --> m11
m9 --> m12
m10 --> m1
m10 --> m8
m10 --> m12
m10 --> m13
m11 --> m4
m11 --> m5
m11 --> m6
m11 --> m7
m11 --> m9
m11 --> m12
m13 --> m5
m13 --> m6
m13 --> m7
m13 --> m8
m13 --> m12Components by module
Most connected components
Top 20 of 24 by exported API surface and dependency count.
| Component | Module | Key exports |
|---|---|---|
app.py | app | _make_timedelta, remove_ctx, add_ctx, Flask |
cli.py | cli | NoAppException, find_best_app, _called_with_wrong_args, find_app_by_string |
sansio/app.py | sansio | _make_timedelta, App |
helpers.py | helpers | get_debug_flag, get_load_dotenv, stream_with_context, stream_with_context |
__init__.py | __init__ | — |
ctx.py | ctx | _AppCtxGlobals, after_this_request, copy_current_request_context, has_request_context |
templating.py | templating | _default_template_ctx_processor, Environment, DispatchingJinjaLoader, _render |
sansio/scaffold.py | sansio | setupmethod, Scaffold, _endpoint_from_view_func, _find_package_path |
json/tag.py | json | JSONTag, TagDict, PassDict, TagTuple |
testing.py | testing | EnvironBuilder, _get_werkzeug_version, FlaskClient, FlaskCliRunner |
sessions.py | sessions | SessionMixin, SecureCookieSession, NullSession, SessionInterface |
debughelpers.py | debughelpers | UnexpectedUnicodeError, DebugFilesKeyError, FormDataRoutingRedirect, attach_enctype_error_multidict |
config.py | config | ConfigAttribute, Config |
json/provider.py | json | JSONProvider, _default, DefaultJSONProvider |
sansio/blueprints.py | sansio | BlueprintSetupState, Blueprint |
wrappers.py | wrappers | Request, Response |
blueprints.py | blueprints | Blueprint |
json/__init__.py | json | dumps, dump, loads, load |
globals.py | globals | __getattr__ |
logging.py | logging | wsgi_errors_stream, has_level_handler, create_logger |
Declared runtime dependencies
What the structure teaches
The sansio split is the headline
app.py and sansio/app.py mirror each other: the sansio ("sans I/O") variant holds routing tables, config, and blueprint logic with zero request/response handling, and the WSGI layer subclasses it. This is how a 15-year-old framework prepares for async without breaking anyone.
Flask is a curator, not an implementor
Six runtime dependencies — werkzeug, jinja2, click, itsdangerous, blinker, markupsafe — do the actual work of WSGI, templating, CLI, signing, and signals. Flask's own 24 components are mostly the developer-experience glue: the app object, blueprints, config, and context locals.
app.py is still the god object, on purpose
The Flask class is the single most connected component, importing from nearly every module. Flask has always chosen one obvious entry point over layered indirection — the x-ray shows that trade-off has survived every refactor.
X-ray your own repo
This page is unedited archsteer xray output plus commentary. The same map of your codebase takes one command, runs locally, and never sends code anywhere: pip install archsteer && archsteer xray
FAQ
How is Flask structured internally?
Around one central Flask application class (app.py) with focused satellite modules: blueprints, config, templating, sessions, and CLI. Since Flask 2.2 the core logic lives in a sansio/ package (pure logic, no I/O) that the WSGI layer subclasses — 24 components in total, resting on six runtime dependencies led by Werkzeug and Jinja2.
What is Flask's sansio package?
sansio ("sans I/O") contains the parts of the app and blueprint objects that don't touch the network: URL rule registration, config handling, error handler lookup. Keeping them I/O-free means alternative I/O layers (like async implementations) can reuse the same core without forking the framework.
How was this architecture map generated?
By running `archsteer xray` (an open-source, MIT-licensed CLI) against the src/flask directory of the public GitHub repo — read-only static analysis, no code executed. Reproduce it on your own repo with `pip install archsteer && archsteer xray`.
More x-rays
The architecture of FastAPI, x-rayed
An auto-generated architecture map of FastAPI: 48 components across 28 modules, the module…
The architecture of HTTPX, x-rayed
An auto-generated architecture map of HTTPX: 23 underscore-private components, the pluggab…
The architecture of Starlette, x-rayed
An auto-generated architecture map of Starlette: 34 components, the middleware stack, and …
The architecture of Pydantic, x-rayed
An auto-generated architecture map of Pydantic: 104 components, the _internal core, the bu…