mrkeyoor.com_
Sun 20 Sept 08:25 UTC
PyPIWeb Backendupdated 19 Sept 2026

flask review

Flask 3.1.3 is a WSGI web framework built around Werkzeug routing and requests, Jinja templates, Click commands, signed cookies, and application or request contexts. It gives you routes and lifecycle hooks without choosing an ORM, validation library, authentication system, or project layout. The current patch changes session access tracking so key-only operations such as `in` and `len()` mark the session as accessed, addressing GHSA-68rp-wp8r-4726. The 3.1 line also added trusted-host checks, multipart limits, per-request body limits, partitioned cookies, and secret-key fallbacks. Our Python 3.12 import succeeded in 0.63 seconds and the distribution shipped `py.typed`.

Verdict

Our Flask 3.1.3 install finished in 0.4 seconds, occupied 3 MB across 7 packages, imported in 0.63 seconds, and had 0 pip-audit findings. It remains a good fit for conventional WSGI applications that want to pick their own database and validation pieces; async-first APIs or batteries-included business systems should start elsewhere.

We installed it

Lab card: what happened when we installed flaskScreenshot of flask documentation
Install✓ · 0.4s7 packages on disk · 3 MB
Importimport flask in 0.63s · pure Python · py.typed · requires Python >=3.9
Known vulns0(pip-audit)

Answers from our run

Does flask install cleanly?

Yes. In a fresh container with an empty cache, pip install flask finished in 0.4s, leaving 7 packages and 3 MB on disk. pip-audit reported no known vulnerabilities.

What does flask need to run?

Python >=3.9, and nothing compiled: it is pure Python. In our run import flask succeeded in 0.63s, and the package ships py.typed for type checkers.

flask or django: which should you use?

django: Choose it when ORM, admin, forms, authentication, migrations, and a prescribed application layout should arrive together. Our Flask 3.1.3 install finished in 0.4 seconds, occupied 3 MB across 7 packages, imported in 0.63 seconds, and had 0 pip-audit findings.

When should you not use flask?

WebSockets, high fan-out streaming, or long-lived async connections define the product. Flask async views still occupy one WSGI worker; Quart, Starlette, or FastAPI fit an ASGI service better.

API stability4/5Routes, blueprints, application factories, request and application contexts, Jinja integration, the test client, and extension initialization remain established Flask patterns in 3.1. Version 3 removed a long list of previously deprecated 2.x attributes, hooks, stack objects, and JSON customization points, so an old application cannot assume drop-in compatibility. The 3.1 additions are configuration-level features such as trusted hosts, upload limits, partitioned cookies, and signing-key fallbacks rather than a new programming model.
Docs5/5The Pallets documentation separates quickstart material from configuration, application factories, blueprints, testing, error handling, async caveats, deployment servers, proxy handling, security, extension development, and a line-by-line changelog. Versioned URLs reduce the chance of copying a 2.x example into a 3.1 project. The docs repeatedly state that the development server is unsuitable for production and explain the one-worker cost of async views, two omissions that framework quickstarts often leave unclear.
Maintenance5/5The Pallets repository is unarchived, was pushed on 2026-08-16, and reports only 3 open issues and pull requests alongside 72,137 stars. Flask 3.1.3 shipped in February 2026 to correct session access tracking and address GHSA-68rp-wp8r-4726; 3.1.2 and 3.1.1 also carried focused test-client, streaming, key-rotation, CLI, and typing fixes. Maintenance is coordinated with Werkzeug, Jinja, ItsDangerous, Click, MarkupSafe, and Blinker rather than hidden in one monolith.
Ecosystem5/5PyPI Stats counted 45,513,320 downloads in the latest week, and Flask has 72,137 GitHub stars. WSGI servers, extensions, hosting platforms, tutorials, and integrations have supported its application and blueprint model for years. That choice also creates compatibility work: authentication, ORM, validation, caching, CSRF, and admin features usually come from separate packages whose Flask 3 support must be checked individually. The base distribution itself stayed at 7 installed packages and 3 MB in our test.

Discussed on

  1. hnFlask 2.0.0 has been merged into master150 points
  2. hnFlask 2 Coming soon with Async Support7 points
  3. hnFlask fails after itsdangerous module’s update5 points

Use it if

  • A Python service needs conventional HTTP routes, templates, signed sessions, and CLI commands while the team wants to choose its own database and validation layers.
  • The deployment stack already speaks WSGI through Gunicorn, uWSGI, mod_wsgi, or another production server.
  • A small application should start in one file but retain a documented path to factories, blueprints, extensions, and tests.
  • Synchronous request handlers dominate, with occasional awaited I/O that does not require thousands of long-lived connections.
Skip it if

Setup reality

We installed Flask 3.1.3 in a clean Python 3.12 Bookworm container. The operation took 0.4 seconds and left 7 packages using 3 MB. pip-audit found 0 known vulnerabilities. Package metadata lists 9 direct dependencies, requires Python 3.9 or newer, and does not state a license there, although GitHub identifies BSD-3-Clause. It is pure Python and includes py.typed. import flask worked in 0.63 seconds in our no-cache sandbox.

A route can run with no credentials, but sessions require a high-entropy SECRET_KEY. Signed cookie contents are readable by the browser, so store identifiers rather than secrets. Flask 3.1 supports SECRET_KEY_FALLBACKS for key rotation, though extensions must implement compatible fallback behavior. Environment loading through the CLI needs the dotenv extra. Factory-based applications should load configuration before registering extensions and blueprints.

The built-in server is for local development. Put a production WSGI server behind a trusted reverse proxy and apply ProxyFix only for the exact proxy hops you operate; accepting forged forwarded headers changes generated URLs and security decisions. Set TRUSTED_HOSTS, MAX_CONTENT_LENGTH, MAX_FORM_MEMORY_SIZE, and MAX_FORM_PARTS where untrusted traffic reaches the app. Flask does not add CSRF protection, authentication, database pooling, or rate limiting by itself.

Installing flask[async] lets an async view await I/O, but each request still consumes one WSGI worker and background tasks spawned by the view are cancelled when its event loop stops. Use a task queue for work that must outlive the response. Request and application globals are context-local, not shared caches. Version 3.1.3 changes which cookie-session reads set Vary: Cookie, so edge caching should never treat a session-touching response as universally reusable.

Patterns

Define a small Flask app create-app

from flask import Flask

app = Flask(__name__)

@app.get("/")
def home():
    return {"service": "ready"}

Use `flask --app app run` for local work. Pallets does not support the development server as a production process.

Build applications through a factory create-app-factory

from flask import Flask

def create_app(test_config=None):
    app = Flask(__name__, instance_relative_config=True)
    app.config.from_prefixed_env()
    if test_config:
        app.config.update(test_config)
    from .api import bp
    app.register_blueprint(bp)
    return app

Load configuration before registering extensions and blueprints. Tests can call the factory with isolated settings.

Group routes under a prefix register-blueprint

from flask import Blueprint

bp = Blueprint("orders", __name__, url_prefix="/orders")

@bp.get("/<int:order_id>")
def get_order(order_id):
    return {"id": order_id}

A blueprint prefixes endpoint names too, so this view is addressed as `orders.get_order` in `url_for()`.

Validate a JSON request read-json

from flask import request

@app.post("/orders")
def create_order():
    body = request.get_json()
    if not isinstance(body.get("quantity"), int):
        return {"error": "quantity must be an integer"}, 400
    return {"id": save_order(body)}, 201

`get_json()` expects a JSON content type. Flask supplies parsing, while schema validation remains application code or an extension.

Harden a signed cookie session configure-session

import os
from flask import session

app.config.update(
    SECRET_KEY=os.environ["FLASK_SECRET_KEY"],
    SESSION_COOKIE_SECURE=True,
    SESSION_COOKIE_HTTPONLY=True,
    SESSION_COOKIE_SAMESITE="Lax",
)

@app.post("/login")
def login():
    session.clear()
    session["user_id"] = 42
    return {"ok": True}

The signature prevents undetected changes, but it does not encrypt cookie data. Keep secrets and bulky objects out of the session.

Accept a previous signing key rotate-secret-key

app.config.update(
    SECRET_KEY=os.environ["FLASK_SECRET_KEY"],
    SECRET_KEY_FALLBACKS=[os.environ["FLASK_OLD_SECRET_KEY"]],
)

Flask 3.1 can unsign cookies with fallback keys. Remove an old key after its session lifetime has expired.

Bound multipart request cost limit-uploads

app.config.update(
    MAX_CONTENT_LENGTH=8 * 1024 * 1024,
    MAX_FORM_MEMORY_SIZE=256 * 1024,
    MAX_FORM_PARTS=50,
)

Flask 3.1 separates the total body limit, in-memory form field limit, and multipart part count.

Return JSON for HTTP exceptions handle-http-errors

from werkzeug.exceptions import HTTPException

@app.errorhandler(HTTPException)
def http_error(error):
    return {
        "error": error.name,
        "detail": error.description,
    }, error.code

Return the exception status code. A bare dictionary from an error handler would otherwise become a successful response.

Release a context-local database handle close-request-resource

from flask import g

def get_db():
    if "db" not in g:
        g.db = connect_database()
    return g.db

@app.teardown_appcontext
def close_db(error):
    db = g.pop("db", None)
    if db is not None:
        db.close()

`g` belongs to the active application context. It is suitable for one request's handle, not cross-request caching.

Keep context during a streamed response stream-response

from flask import Response, stream_with_context

@app.get("/export")
def export():
    @stream_with_context
    def rows():
        yield "id,name\n"
        for row in load_rows():
            yield f"{row.id},{row.name}\n"
    return Response(rows(), mimetype="text/csv")

The ordinary request context ends when the view returns. `stream_with_context` extends it through iterator consumption.

Call the app without a socket test-route

def test_create_order(app):
    client = app.test_client()
    response = client.post("/orders", json={"quantity": 2})
    assert response.status_code == 201
    assert response.json["id"]

The test client invokes WSGI directly. Create a fresh factory instance when a test changes configuration or extension state.

Await I/O in one view write-async-view

@app.get("/upstream")
async def upstream():
    result = await fetch_remote_record()
    return result

Install the `async` extra. One async request still occupies one WSGI worker, and child tasks do not survive the response.

Alternatives

PackageRegistryPick it when
djangoPyPIChoose it when ORM, admin, forms, authentication, migrations, and a prescribed application layout should arrive together.
fastapiPyPIChoose it for typed request models, generated OpenAPI, dependency injection, and an ASGI runtime.
starlettePyPIChoose it for a smaller ASGI toolkit with WebSockets and async middleware but without FastAPI's validation layer.
quartPyPIChoose it when Flask-like APIs are useful but the server must be async-native and support WebSockets.

More web backend guides

urllib3 · requests · ws · anyio · httpx · undici · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.