Overview
Relevant Files
src/flask/init.pysrc/flask/app.pysrc/flask/ctx.pysrc/flask/blueprints.pypyproject.tomldocs/design.rst
Flask is a lightweight WSGI web application framework designed for building web applications in Python. It emphasizes simplicity and flexibility, allowing developers to start small and scale up to complex applications. Flask began as a wrapper around Werkzeug and Jinja, and has become one of the most popular Python web frameworks.
Core Philosophy
Flask follows the principle of explicit is better than implicit. Rather than enforcing a specific project structure or dependencies, Flask provides suggestions and lets developers choose their own tools and libraries. This flexibility is enabled by a clean, minimal core that can be extended through community-provided extensions.
Architecture Overview
Loading diagram...
Key Components
Flask Application Object - The central Flask class is a WSGI application that acts as a registry for routes, templates, configuration, and more. Each application is an explicit instance, enabling multiple applications in the same process and facilitating testing.
Routing System - Flask uses Werkzeug's routing engine, which automatically orders routes by complexity. This allows routes to be declared in any order and still work correctly, essential for decorator-based routing across multiple modules.
Contexts - Flask uses application and request contexts to manage state. The AppContext contains app-level data and request information, pushed at the start of each request and CLI command, then popped at the end with teardown functions executed.
Blueprints - Blueprints are reusable collections of routes and app-related functions that can be registered on a Flask application later. They enable modular application design without requiring the app object upfront.
Templating - Flask uses Jinja as its template engine. Rather than providing a pluggable interface, Flask commits to one engine for consistency and simplicity, with templates loaded relative to the application module.
Dependencies
Flask depends on five core libraries:
- Werkzeug - WSGI utilities and routing
- Jinja - Template rendering
- Click - CLI framework
- itsdangerous - Secure signing and serialization
- Blinker - Signal support
- MarkupSafe - Safe string handling
Optional dependencies include asgiref for async support and python-dotenv for environment configuration.
Project Structure
The codebase is organized into:
src/flask/- Main framework codesrc/flask/sansio/- WSGI-agnostic core logictests/- Comprehensive test suitedocs/- Sphinx documentationexamples/- Tutorial and example applications
Architecture & Core Components
Relevant Files
src/flask/app.pysrc/flask/sansio/app.pysrc/flask/sansio/scaffold.pysrc/flask/ctx.pysrc/flask/globals.py
Flask's architecture is built on a layered design that separates concerns between protocol-agnostic logic and WSGI-specific implementation. Understanding the core components and their interactions is essential for extending or debugging Flask applications.
Class Hierarchy
The architecture follows a clear inheritance pattern:
- Scaffold - Base class providing common functionality for both Flask and Blueprint. Manages route registration, error handlers, and request/response hooks.
- App (sansio) - Protocol-agnostic application logic. Handles configuration, URL routing, and view function management without WSGI dependencies.
- Flask - WSGI-specific implementation that extends App. Implements the actual WSGI interface and request handling.
This separation allows Flask to support multiple protocols while keeping core logic reusable.
Request Lifecycle
When a WSGI server calls Flask, the request flows through these stages:
- Context Creation -
Flask.request_context()creates anAppContextfrom the WSGI environ - Context Push - Context is pushed, making
current_app,request,g, andsessionavailable - Request Dispatch -
full_dispatch_request()runs before/after hooks, matches routes, and calls the view function - Response Finalization - Response is processed through after-request hooks
- Context Pop - Context is popped, running teardown functions to clean up resources
def wsgi_app(self, environ, start_response):
ctx = self.request_context(environ)
try:
ctx.push()
response = self.full_dispatch_request(ctx)
finally:
ctx.pop(error)
Context System
The AppContext is the central state container for both app-level and request-level data:
current_app- Proxy to the active Flask application instanceg- Request-scoped namespace for storing arbitrary data (backed by_AppCtxGlobals)request- Proxy to the current request objectsession- Proxy to the session data
Contexts use Python's contextvars module for thread-safe, async-safe storage. The LocalProxy wrapper provides transparent access to context-local data.
Route and Handler Registration
Scaffold maintains several internal dictionaries for managing application behavior:
view_functions- Maps endpoint names to view callableserror_handler_spec- Hierarchical error handlers by scope and exception typebefore_request_funcs- Pre-request hooks by scopeafter_request_funcs- Post-request hooks by scopeteardown_request_funcs- Cleanup functions run even on errors
These structures support both app-level and blueprint-level handlers through scope keys.
Dispatch Flow
Loading diagram...
The dispatch process handles exceptions at each stage, ensuring teardown functions always execute for proper resource cleanup.
Routing & Views
Relevant Files
src/flask/app.pysrc/flask/views.pysrc/flask/blueprints.pysrc/flask/sansio/blueprints.pysrc/flask/sansio/scaffold.py
Flask routing maps incoming HTTP requests to view functions through a three-layer system: URL rules, blueprints, and view dispatching. The routing system is built on Werkzeug's powerful routing engine and provides both decorator-based and programmatic registration methods.
URL Rules and Route Registration
Routes are registered using the @app.route() decorator or app.add_url_rule() method. Both approaches create a Rule object in the application's url_map (a Werkzeug Map instance) and associate it with a view function in view_functions dictionary.
@app.route("/hello/<name>")
def hello(name):
return f"Hello, {name}!"
# Equivalent to:
app.add_url_rule("/hello/<name>", "hello", hello)
The endpoint name (used with url_for()) defaults to the function name but can be customized. URL rules support variable converters like <int:id>, <path:filename>, and custom converters registered on app.url_map.converters.
Request Matching and Dispatching
When a request arrives, Flask creates a MapAdapter by binding the url_map to the request environment. The adapter matches the request path against registered rules and extracts URL variables. This happens in create_url_adapter(), which stores the matched rule and view arguments on the request object.
The dispatch_request() method then retrieves the view function from view_functions using the matched endpoint and calls it with the extracted URL variables:
def dispatch_request(self, ctx):
rule = ctx.request.url_rule
view_args = ctx.request.view_args
return self.view_functions[rule.endpoint](**view_args)
Class-Based Views
Flask provides two base classes for class-based views: View and MethodView. The View class requires implementing dispatch_request() and is converted to a view function using as_view():
class Hello(View):
def dispatch_request(self, name):
return f"Hello, {name}!"
app.add_url_rule("/hello/<name>", view_func=Hello.as_view("hello"))
MethodView automatically dispatches to methods matching HTTP verbs (get(), post(), etc.), making it ideal for REST APIs. The init_every_request class attribute controls whether a new instance is created per request (default: True) or reused for efficiency.
Blueprints
Blueprints defer route registration until they are registered on an application. They record routes and callbacks as deferred functions, which are executed when register_blueprint() is called. This enables modular application structure.
bp = Blueprint("admin", __name__, url_prefix="/admin")
@bp.route("/dashboard")
def dashboard():
return "Admin Dashboard"
app.register_blueprint(bp)
During registration, BlueprintSetupState applies the blueprint's url_prefix and subdomain to all routes, and prefixes endpoint names with the blueprint name (e.g., admin.dashboard). Blueprints can be nested and registered multiple times with different names and prefixes.
URL Building
The url_for() function generates URLs by calling url_adapter.build() with an endpoint name and values. It resolves relative endpoints (starting with .) to the current blueprint and handles external URL generation when SERVER_NAME is configured. URL defaults registered via @app.url_defaults() are injected before building.
Loading diagram...
Request & Response Handling
Relevant Files
src/flask/wrappers.pysrc/flask/helpers.pysrc/flask/ctx.pysrc/flask/app.py
Flask's request and response handling is built on a context-based architecture that manages the lifecycle of HTTP requests from arrival to response delivery. Understanding this flow is essential for working with Flask effectively.
Request Objects
The Request class in wrappers.py extends Werkzeug's base request object with Flask-specific features. Key attributes include:
url_rule- The matched URL rule from routingview_args- Dictionary of URL parameters passed to the viewendpoint- The name of the matched endpointblueprint- The blueprint name if the endpoint belongs to onerouting_exception- Any exception that occurred during URL matching
Request objects also support configurable limits via properties like max_content_length, max_form_memory_size, and max_form_parts, which can be set per-request or via Flask config.
Response Objects
The Response class provides Flask's default response wrapper with HTML as the default MIME type. It inherits from Werkzeug's response and adds Flask-specific features like JSON support and cookie size configuration. Responses can be created directly or through helper functions like make_response().
Request-Response Lifecycle
Loading diagram...
The flow begins when a WSGI server calls wsgi_app(), which creates an AppContext containing the request and application state. The context is pushed to make it active, then full_dispatch_request() orchestrates the request handling.
Key Processing Stages
Preprocessing - preprocess_request() runs URL value preprocessors and before-request handlers. If any handler returns a non-None value, it short-circuits to response finalization.
Dispatching - dispatch_request() matches the URL to a view function and calls it with view arguments. The view function returns a value that may be a string, dict, Response object, or tuple.
Finalization - finalize_request() converts the return value to a Response object via make_response(), then runs process_response() to apply after-request handlers and signals.
Making Responses
The make_response() function converts various return types into Response objects:
- Strings and bytes become response bodies
- Dicts and lists are JSON-encoded
- Response objects pass through unchanged
- Tuples are unpacked as
(body, status, headers)
# String response
return "Hello, World!"
# JSON response
return {"message": "Hello"}
# Custom status and headers
return "Not Found", 404, {"X-Custom": "header"}
# Response object with modifications
response = make_response(render_template("index.html"))
response.headers["X-Custom"] = "value"
return response
Context Management
The AppContext class manages both application and request state. It provides access to request, session, and g (application globals). Contexts are pushed at the start of request handling and popped at the end, triggering teardown functions and cleanup signals.
Helper functions like stream_with_context() preserve the request context when using generators, and copy_current_request_context() allows background tasks to access request data.
Templating & Template Rendering
Relevant Files
src/flask/templating.pysrc/flask/helpers.pysrc/flask/signals.pysrc/flask/sansio/app.pysrc/flask/sansio/scaffold.py
Flask uses Jinja2 as its template engine, providing a clean integration layer that handles template loading, rendering, and context management. The system supports both file-based templates and string-based rendering, with optional streaming for large responses.
Core Rendering Functions
Flask provides four main functions for template rendering:
-
render_template(template_name_or_list, **context)- Renders a template file by name with the given context variables. Accepts a single name, aTemplateobject, or a list of names (first existing one is used). -
render_template_string(source, **context)- Renders a template from a source string instead of a file. -
stream_template(template_name_or_list, **context)- Returns an iterator of strings for streaming responses, useful for large templates or real-time data. -
stream_template_string(source, **context)- Streams a template from a source string.
All rendering functions require an active application context and return the rendered output as a string or iterator.
Template Loading Architecture
Loading diagram...
The DispatchingJinjaLoader searches for templates across the application and all registered blueprints. It supports two modes:
- Fast mode (default): Returns the first matching template found.
- Explained mode (
EXPLAIN_TEMPLATE_LOADING=True): Logs all attempted paths for debugging template resolution issues.
Context Processing Pipeline
Before rendering, Flask updates the template context through a multi-stage pipeline:
-
Default context processor injects
request,session, andg(if a request context exists). -
Blueprint-specific processors run for templates rendered from blueprint views.
-
App-wide processors run for all templates.
-
User-provided context takes precedence and is never overridden by processors.
Register context processors using the @app.context_processor decorator:
@app.context_processor
def inject_user():
return {"current_user": get_current_user()}
Rendering Signals
Flask emits two signals during template rendering:
-
before_render_template- Sent before rendering begins, allowing inspection or modification of the template and context. -
template_rendered- Sent after rendering completes, useful for logging or analytics.
from flask import template_rendered
@template_rendered.connect_via(app)
def log_template(sender, template, context, **extra):
print(f"Rendered {template.name}")
Streaming Responses
The stream_template() and stream_template_string() functions return iterators that preserve the request context throughout streaming. This is essential because the context normally ends after the view function returns. Use stream_with_context() to wrap generator functions that need access to request, session, or g.
Template Globals and Filters
Flask automatically injects these into every template:
config- Application configuration objectrequest- Current request object (if request context active)session- Session data (if request context active)g- Request-scoped storage objecturl_for()- URL generation functionget_flashed_messages()- Flash message retrieval
Custom globals, filters, and tests can be registered via decorators:
@app.template_global()
def format_price(value):
return f"${value:.2f}"
@app.template_filter()
def reverse_string(s):
return s[::-1]
Autoescaping
Autoescaping is enabled by default for templates ending in .html, .htm, .xml, .xhtml, or .svg when using render_template(). For render_template_string(), autoescaping applies to all strings. Use the {% autoescape %} tag to override this behavior in templates.
Sessions, Configuration & Security
Relevant Files
src/flask/sessions.pysrc/flask/config.pysrc/flask/json/tag.py
Flask manages user state through signed cookies and provides a flexible configuration system to control security behavior. Sessions are cryptographically signed to prevent tampering, and configuration options control cookie attributes and session lifetime.
Session Architecture
Sessions in Flask are implemented through a pluggable interface. The SessionInterface base class defines two core methods:
open_session(app, request)- Called at the start of each request to load session data from cookiessave_session(app, session, response)- Called at the end of each request to persist session changes
The default implementation, SecureCookieSessionInterface, stores all session data in a signed cookie using the itsdangerous library. This means sessions are stateless on the server—no database is required.
Session Objects
The SessionMixin provides a dictionary-like interface with three tracking attributes:
modified- Set toTruewhen data changes; controls whether the cookie is writtenaccessed- Set toTruewhen data is read or written; used for cache headerspermanent- WhenTrue, the session persists beyond browser closure (usesPERMANENT_SESSION_LIFETIME)
The SecureCookieSession class extends this with automatic tracking via a callback dictionary. When you modify the session, modified is automatically set to True, triggering a cookie write on response.
Signing & Serialization
Session cookies are signed using HMAC-SHA1 by default. The signing process:
- Session data is serialized to JSON using
TaggedJSONSerializer - The JSON is signed with the app's
SECRET_KEYand a salt ("cookie-session") - The signed value is URL-safe base64 encoded and sent as a cookie
The TaggedJSONSerializer extends JSON to support Python types like datetime, UUID, bytes, and Markup by wrapping them with type tags. This allows rich data in sessions without pickle (which has security risks).
Configuration System
The Config class extends dict and provides multiple loading methods:
from_pyfile(filename)- Load from a Python file (only uppercase keys)from_object(obj)- Load from a module or class attributesfrom_file(filename, load)- Load from JSON, TOML, or other formatsfrom_prefixed_env(prefix)- Load from environment variables with a prefixfrom_mapping(mapping)- Load from a dictionary
The ConfigAttribute descriptor allows app properties to forward to config values. For example, app.permanent_session_lifetime reads from config["PERMANENT_SESSION_LIFETIME"].
Security Configuration
Key session security options:
SECRET_KEY- Required for signing; should be a long random stringSECRET_KEY_FALLBACKS- List of old keys for rotation without invalidating sessionsSESSION_COOKIE_SECURE- Only send over HTTPSSESSION_COOKIE_HTTPONLY- Prevent JavaScript access (default:True)SESSION_COOKIE_SAMESITE- Restrict cross-site cookie sending ('Lax'or'Strict')PERMANENT_SESSION_LIFETIME- Expiration time for permanent sessions (default: 31 days)SESSION_REFRESH_EACH_REQUEST- Refresh cookie expiration on each request
If SECRET_KEY is not set, Flask creates a NullSession that allows reads but raises errors on writes, providing helpful error messages.
Custom Session Backends
To implement a custom session backend (e.g., database-backed sessions):
- Subclass
SessionInterface - Implement
open_session()andsave_session() - Return objects implementing
SessionMixin(dict-like withmodified,accessed,permanent) - Assign to
app.session_interface
Multiple concurrent requests with the same session may occur; synchronization is your responsibility.
CLI & Command System
Relevant Files
src/flask/cli.pysrc/flask/app.pysrc/flask/blueprints.py
Flask's CLI system is built on Click, a Python package for creating command-line interfaces. The system provides a structured way to register, discover, and execute commands both at the application and blueprint levels.
Core Architecture
The CLI system revolves around three main components:
- FlaskGroup - The main Click group that serves as the entry point for the
flaskcommand. It handles app discovery, plugin loading, and command resolution. - AppGroup - A Click group subclass that automatically wraps commands with application context, eliminating the need for manual context management.
- ScriptInfo - A helper object that manages Flask app loading, environment variable handling, and debug flag configuration.
Application Discovery
When you run flask commands, Flask needs to locate your application. The discovery process follows this order:
- Explicit
--appoption:flask --app myapp:create_app run FLASK_APPenvironment variable- Auto-discovery: looks for
wsgi.pyorapp.pyin the current directory - App factory functions: searches for
create_app()ormake_app()functions
The ScriptInfo.load_app() method handles this discovery and caches the loaded app instance.
Registering Commands
Commands can be registered at three levels:
Application-level commands:
import click
from flask import Flask
app = Flask(__name__)
@app.cli.command()
@click.argument('name')
def hello(name):
click.echo(f'Hello {name}!')
Blueprint-level commands:
from flask import Blueprint
bp = Blueprint('admin', __name__)
@bp.cli.command()
def init_db():
click.echo('Database initialized')
app.register_blueprint(bp)
Command groups:
from flask.cli import AppGroup
user_cli = AppGroup('user')
@user_cli.command('create')
@click.argument('name')
def create_user(name):
click.echo(f'User {name} created')
app.cli.add_command(user_cli)
Built-in Commands
Flask provides three default commands:
flask run- Starts the development server with hot-reload and debugger supportflask shell- Opens an interactive Python shell with app context activeflask routes- Displays all registered routes with methods and endpoints
Application Context
The @with_appcontext decorator ensures a command runs within the Flask application context, providing access to current_app, g, and other context-local objects:
from flask.cli import with_appcontext
@app.cli.command()
@with_appcontext
def migrate():
# current_app is available here
click.echo(f'Migrating {current_app.name}')
Commands registered via app.cli or blueprint.cli automatically have app context available without the decorator.
Plugin Commands
Flask supports plugin commands via entry points. Extensions can register commands by defining an entry point in their pyproject.toml:
[project.entry-points."flask.commands"]
my-command = "my_extension.commands:cli"
The FlaskGroup._load_plugin_commands() method discovers and loads these at runtime.
Custom CLI Scripts
For advanced use cases, create a custom CLI group:
from flask.cli import FlaskGroup
def create_app():
return Flask(__name__)
@click.group(cls=FlaskGroup, create_app=create_app)
def cli():
"""Custom management script."""
if __name__ == '__main__':
cli()
Define the entry point in pyproject.toml:
[project.scripts]
myapp = "myapp:cli"
Environment Variables
Flask CLI respects several environment variables:
FLASK_APP- Application import pathFLASK_DEBUG- Enable debug modeFLASK_ENV- Application environment (deprecated in favor ofFLASK_DEBUG).envand.flaskenv- Loaded automatically ifpython-dotenvis installed
The load_dotenv() function manages this loading, with precedence: explicit path > .env > .flaskenv.
Signals & Event System
Relevant Files
src/flask/signals.pysrc/flask/ctx.pysrc/flask/app.pysrc/flask/templating.py
Flask uses the Blinker library to implement a lightweight event system. Signals allow you to subscribe to specific lifecycle events without modifying core Flask code. This is useful for logging, metrics, testing, and extensions.
Core Signal Types
Flask provides nine built-in signals organized by lifecycle phase:
Request Lifecycle:
request_started— Sent when the request context is pushed, before any processingrequest_finished— Sent after the response is finalized, before context cleanuprequest_tearing_down— Sent during context cleanup (always called, even on errors)got_request_exception— Sent when an unhandled exception occurs during request processing
Template Rendering:
before_render_template— Sent before a template is rendered (allows modifying context)template_rendered— Sent after a template is successfully rendered
Application Context:
appcontext_pushed— Sent when an app context is pushedappcontext_popped— Sent when an app context is poppedappcontext_tearing_down— Sent during app context cleanup
Other:
message_flashed— Sent when a flash message is added
Subscribing to Signals
Use the .connect() method to subscribe. Always specify a sender (usually the app) to avoid listening to all applications:
from flask import request_started
def log_request(sender, **extra):
sender.logger.debug('Request started')
app = Flask(__name__)
request_started.connect(log_request, app)
Unsubscribe with .disconnect():
request_started.disconnect(log_request, app)
Decorator-Based Subscription
Use @signal.connect_via(app) for cleaner syntax:
from flask import template_rendered
@template_rendered.connect_via(app)
def log_template(sender, template, context, **extra):
print(f'Rendered {template.name}')
Signal Parameters
Each signal passes different parameters to subscribers:
request_started/request_finished— sender onlyrequest_finished— sender, responsegot_request_exception— sender, exceptiontemplate_rendered/before_render_template— sender, template, contextmessage_flashed— sender, message, category*_tearing_down— sender, exc (exception if one occurred)
Implementation Details
Signals are defined in src/flask/signals.py using a Blinker Namespace:
from blinker import Namespace
_signals = Namespace()
request_started = _signals.signal("request-started")
Signals are sent throughout the request lifecycle:
request_started— Sent infull_dispatch_request()before preprocessingrequest_finished— Sent infinalize_request()after response processinggot_request_exception— Sent inhandle_exception()when unhandled exceptions occurappcontext_pushed/appcontext_popped— Sent inAppContext.push()andAppContext.pop()- Template signals — Sent in
_render()and_stream()intemplating.py
Key Differences from Decorators
Signals differ from Flask's decorator-based callbacks (@app.before_request, @app.after_request):
- Signals can be subscribed/unsubscribed dynamically at runtime
- Signals cannot modify the request/response flow (read-only observers)
- Signals are ideal for logging, metrics, auditing, and testing
- Decorators are better for core request processing logic
Context Availability
Context-local proxies (flask.g, flask.request, flask.session) are available between request_started and request_finished. After request_tearing_down, these are no longer accessible.
Testing Utilities & Test Client
Relevant Files
src/flask/testing.pytests/conftest.py
Flask provides a comprehensive testing framework built on top of Werkzeug and Click testing utilities. The testing module includes three main components: EnvironBuilder for constructing test requests, FlaskClient for making HTTP requests without a live server, and FlaskCliRunner for testing CLI commands.
EnvironBuilder: Configuring Test Requests
EnvironBuilder extends Werkzeug's test builder to automatically apply Flask application configuration. It simplifies request construction by reading defaults from the app's config (SERVER_NAME, APPLICATION_ROOT, PREFERRED_URL_SCHEME). This means you can write cleaner test code without manually specifying every detail.
from flask.testing import EnvironBuilder
# EnvironBuilder automatically uses app config
builder = EnvironBuilder(app, path="/api/users", method="POST", json={"name": "Alice"})
request = builder.get_request()
Key features include subdomain support, custom URL schemes, and automatic JSON serialization using the app's configured JSON encoder.
FlaskClient: Making Test Requests
FlaskClient is the primary tool for testing HTTP endpoints. It extends Werkzeug's Client with Flask-specific features like context preservation and session manipulation.
# Create a test client
client = app.test_client()
# Make requests
response = client.get("/")
response = client.post("/api/data", json={"key": "value"})
response = client.put("/api/data/1", data={"name": "updated"})
Context Preservation: Use the client as a context manager to keep request contexts alive after requests complete. This allows you to inspect flask.g, flask.session, and other request-scoped data:
with client:
response = client.get("/")
assert flask.g.user_id == 42 # Access request context data
Session Manipulation: The session_transaction() method lets you modify session data between requests:
with client:
with client.session_transaction() as sess:
sess["user_id"] = 123
response = client.get("/") # Request sees the session data
Environment Customization: Modify client.environ_base to set default headers, remote address, or user agent:
client.environ_base["REMOTE_ADDR"] = "192.168.1.1"
client.environ_base["HTTP_USER_AGENT"] = "Custom Agent"
FlaskCliRunner: Testing CLI Commands
FlaskCliRunner tests Flask CLI commands in isolation. It wraps Click's testing runner and automatically provides the Flask app context.
runner = app.test_cli_runner()
# Invoke commands by name
result = runner.invoke(args=["db", "init"])
assert result.exit_code == 0
assert "Database initialized" in result.output
# Or invoke command objects directly
result = runner.invoke(my_command)
Test Fixtures (conftest.py)
The repository provides standard pytest fixtures for common testing scenarios:
app: A Flask application instance withTESTING=Trueand a test secret keyclient: A test client for the appapp_ctx: An active application contextreq_ctx: An active request contextleak_detector: Automatically fails if app contexts aren't cleaned up
These fixtures ensure consistent test setup and prevent context leaks between tests.
Common Testing Patterns
Testing JSON APIs:
def test_api(client):
response = client.post("/api/items", json={"name": "test"})
assert response.status_code == 201
data = response.get_json()
assert data["id"] is not None
Testing with Sessions:
def test_authenticated_route(client):
with client:
with client.session_transaction() as sess:
sess["user_id"] = 1
response = client.get("/profile")
assert response.status_code == 200
Testing Redirects:
def test_redirect(client):
response = client.get("/old-path", follow_redirects=True)
assert response.status_code == 200
assert b"new content" in response.data
JSON Serialization & Data Handling
Relevant Files
src/flask/json/init.pysrc/flask/json/provider.pysrc/flask/json/tag.py
Flask provides a pluggable JSON serialization system through the JSONProvider architecture. This allows applications to customize how data is converted to and from JSON, supporting both standard types and custom Python objects.
Core Architecture
The JSON system is built on three layers:
-
JSONProvider (Base Class): Defines the interface for JSON operations. Subclasses implement
dumps()andloads()to use different JSON libraries (e.g.,simplejson,orjson). -
DefaultJSONProvider: Flask's built-in provider using Python's standard
jsonlibrary. It extends serialization to handle additional types likedatetime,UUID,Decimal, and dataclasses. -
Module-Level Functions:
flask.json.dumps(),loads(),dump(),load(), andjsonify()delegate to the current app's provider when available.
Serialization Behavior
The DefaultJSONProvider automatically converts:
- Dates:
datetime.dateanddatetime.datetime→ RFC 822 HTTP date strings - UUIDs:
uuid.UUID→ hex string - Decimals:
decimal.Decimal→ string representation - Dataclasses: Converted via
dataclasses.asdict() - Markup Objects: Objects with
__html__()method → their HTML string
from flask import Flask, jsonify
from datetime import datetime
from uuid import uuid4
app = Flask(__name__)
@app.route('/api/data')
def get_data():
return jsonify(
timestamp=datetime.now(),
id=uuid4(),
message="Hello"
)
Configuration
The DefaultJSONProvider exposes three key attributes:
ensure_ascii(default:True): Escape non-ASCII characters. Set toFalsefor better performance with Unicode.sort_keys(default:True): Sort dictionary keys in output. Useful for caching but impacts performance.compact(default:None): Controls formatting.Noneuses debug mode to decide;Falseadds indentation;Trueuses compact separators.
app.json.ensure_ascii = False
app.json.sort_keys = False
app.json.compact = True
Tagged JSON Serialization
For session data and other scenarios requiring lossless serialization of non-standard types, Flask provides TaggedJSONSerializer. It uses a compact tag system to preserve type information:
from flask.json.tag import TaggedJSONSerializer, JSONTag
serializer = TaggedJSONSerializer()
# Serialize and deserialize with type preservation
data = (1, 2, 3) # tuple
json_str = serializer.dumps(data)
restored = serializer.loads(json_str) # Returns tuple, not list
Supported tagged types include tuples, bytes, Markup, UUID, and datetime. Custom types can be added by subclassing JSONTag:
class TagCustom(JSONTag):
key = " c"
def check(self, value):
return isinstance(value, MyClass)
def to_json(self, value):
return {"field": value.field}
def to_python(self, value):
return MyClass(value["field"])
serializer.register(TagCustom)
Custom Providers
To use a different JSON library, subclass JSONProvider and set it on the app:
from flask.json.provider import JSONProvider
class CustomJSONProvider(JSONProvider):
def dumps(self, obj, **kwargs):
# Use your library here
return custom_json_lib.dumps(obj, **kwargs)
def loads(self, s, **kwargs):
return custom_json_lib.loads(s, **kwargs)
app.json_provider_class = CustomJSONProvider
The provider instance is created automatically and accessible via app.json.