CodeVetter · Repo Unpack · Behavior traces
pallets/flask
Three flows dominate: application construction, WSGI request dispatch, and CLI invocation. Construction is `Flask.__init__` → `App.__init__` → `Scaffold.__init__`, ending with an auto-registered static route that holds only a weakref to the app. Request dispatch is `__call__` → `wsgi_app` → `AppContext.push` (session open, then routing) → `full_dispatch_request` → `preprocess_request` → `dispatch_request` → `finalize_request` → `process_response` (session save) → `ctx.pop` (teardowns). CLI invocation is `main` → `FlaskGroup.make_context` (sets `FLASK_RUN_FROM_CLI`) → eager `--env-file`/`--app` callbacks → `ScriptInfo.load_app` → command body with an app context pushed via `ctx.with_resource`.
- Startup: `Flask.__init__` calls `super().__init__` (App → Scaffold), creates `self.cli = cli.AppGroup()` named after the app, then — if `has_static_folder` — asserts `bool(static_host) == host_matching` and registers `f"{static_url_path}/<path:filename>"` with a `weakref.ref(self)` closure to avoid an app↔view reference cycle (issue #3761).
- Startup: `App.__init__` resolves `instance_path` (rejecting relative paths), builds `config` via `make_config`, instantiates `aborter`, `json_provider_class(self)`, the empty `blueprints`/`extensions` dicts, `url_map_class(host_matching=...)`, and sets `_got_first_request = False`.
- Request: `Flask.__call__` → `wsgi_app(environ, start_response)` creates the context with `self.request_context(environ)`, pushes it, dispatches, and in `finally` restores it for the debugger via `environ["werkzeug.debug.preserve_context"]` before `ctx.pop(error)`.
- Request: `AppContext.push` increments `_push_count`, returns early if already pushed, sets `_cv_app`, sends `appcontext_pushed`, then opens the session via `_get_session()` *before* calling `match_request()` — ordering that lets custom URL converters read the session.
- Request: `full_dispatch_request` sets `_got_first_request = True` (freezing setup methods), sends `request_started`, runs `preprocess_request`, short-circuits to `finalize_request` if a `before_request` returned non-None, otherwise calls `dispatch_request`, catching exceptions into `handle_user_exception`.
- Request: `preprocess_request` runs `url_value_preprocessors` for `(None, *reversed(req.blueprints))` first, then `before_request_funcs` in the same order — app-level before blueprint-level, outermost blueprint first.
- Request: `dispatch_request` raises any stored `routing_exception` through `raise_routing_exception`, returns `make_default_options_response` for automatic OPTIONS, and otherwise calls `ensure_sync(self.view_functions[rule.endpoint])(**view_args)`.
- Request: `process_response` runs `ctx._after_request_functions` (from `after_this_request`) first, then app+blueprint `after_request_funcs` in reverse registration order over `chain(request.blueprints, (None,))`, then saves the session.
- Teardown: `AppContext.pop` decrements `_push_count`, returns while still nested, then under `_CollectErrors` runs `do_teardown_request`, `request.close()`, `do_teardown_appcontext`, resets the ContextVar, sends `appcontext_popped`, and finally re-raises collected errors as a `BaseExceptionGroup` on Python 3.11+.
- Error path: `handle_exception` sends `got_request_exception`, re-raises when `PROPAGATE_EXCEPTIONS` (defaulting to `testing or debug`) is truthy, otherwise logs, wraps in `InternalServerError(original_exception=e)`, looks up a 500 handler, and finalizes with `from_error_handler=True` so response-processing failures are logged rather than raised.
- Error handler lookup in `_find_error_handler` tries (specific code, then None) × (each blueprint name, then None), walking `exc_class.__mro__` at each step — so a blueprint handler for a specific code beats an app handler for that code, which beats class-based handlers.
- CLI: `FlaskGroup.make_context` sets `os.environ["FLASK_RUN_FROM_CLI"] = "true"` so a stray module-level `app.run()` becomes a no-op that prints a red warning instead of blocking the command.
- CLI: `FlaskGroup.get_command` loads `flask.commands` entry points, prefers built-ins, then calls `ScriptInfo.load_app()` and pushes an app context with `ctx.with_resource(app.app_context())` before delegating to `app.cli.get_command` — which is why `@with_appcontext` is unnecessary for app-registered commands.
- CLI: `ScriptInfo.load_app` splits the import path on `r":(?![\\/])"` (so Windows drive letters survive), calls `prepare_import` to insert the package root into `sys.path[0]`, then `locate_app`, and finally sets `app.debug = get_debug_flag()` when `set_debug_flag` is on.
- Streaming: `stream_with_context` builds a generator, advances it once with `next()` to capture and push the current `AppContext` (which bumps `_push_count`), yields the user's chunks inside `with ctx:`, and closes a wrapped WSGI iterator in `finally`.