Modern Flask 3.x patterns — app factory, blueprints, middleware.
This module demonstrates idiomatic Flask for building JSON APIs
- Application factory —
create_app() builds a fresh app each time,
making tests reproducible and avoiding global state.
- Blueprints —
api_bp groups related routes under /api.
- Error handlers — custom
ValidationError + 404 both return JSON.
- Middleware — before/after request hooks for logging & headers.
ASI relevance
Most ASI backend services expose REST or GraphQL APIs. Understanding
Flask's request lifecycle helps with debugging, testing, and extending
any WSGI-based Python service.
References
https://flask.palletsprojects.com/en/stable/patterns/appfactories/
https://flask.palletsprojects.com/en/stable/blueprints/
https://flask.palletsprojects.com/en/stable/testing/
Item
dataclass
A catalog item with an integer id, a name, and a price.
Source code in src/concepts/modern_flask.py
| @dataclass
class Item:
"""A catalog item with an integer id, a name, and a price."""
id: int
name: str
price: float
|
ItemCreate
dataclass
Payload for creating a new Item (no id yet).
Source code in src/concepts/modern_flask.py
| @dataclass
class ItemCreate:
"""Payload for creating a new Item (no id yet)."""
name: str
price: float
|
ValidationError
Bases: Exception
Raised when request data fails validation.
Carries a human-readable message and an HTTP status_code (default 400).
The app-level error handler converts this to a JSON response.
Source code in src/concepts/modern_flask.py
| class ValidationError(Exception):
"""Raised when request data fails validation.
Carries a human-readable *message* and an HTTP *status_code* (default 400).
The app-level error handler converts this to a JSON response.
"""
def __init__(self, message: str, status_code: int = 400) -> None:
super().__init__(message)
self.message = message
self.status_code = status_code
|
health
health() -> tuple[Response, int]
Simple health-check endpoint.
Source code in src/concepts/modern_flask.py
| @api_bp.route("/health")
def health() -> tuple[Response, int]:
"""Simple health-check endpoint."""
return jsonify({"status": "healthy"}), 200
|
list_items
list_items() -> tuple[Response, int]
Return all items as a JSON list.
Source code in src/concepts/modern_flask.py
| @api_bp.route("/items")
def list_items() -> tuple[Response, int]:
"""Return all items as a JSON list."""
items_list = [
{"id": item.id, "name": item.name, "price": item.price}
for item in _items.values()
]
return jsonify(items_list), 200
|
get_item
get_item(item_id: int) -> tuple[Response, int]
Return a single item or 404.
Source code in src/concepts/modern_flask.py
| @api_bp.route("/items/<int:item_id>")
def get_item(item_id: int) -> tuple[Response, int]:
"""Return a single item or 404."""
item = _items.get(item_id)
if item is None:
return jsonify({"error": "not found"}), 404
return jsonify({"id": item.id, "name": item.name, "price": item.price}), 200
|
create_item
create_item() -> tuple[Response, int]
Create an item from the JSON request body.
Expects {"name": "...", "price": ...}. Returns 201 on success.
Source code in src/concepts/modern_flask.py
| @api_bp.route("/items", methods=["POST"])
def create_item() -> tuple[Response, int]:
"""Create an item from the JSON request body.
Expects ``{"name": "...", "price": ...}``. Returns 201 on success.
"""
global _next_id
body: Any = request.get_json(silent=True)
if body is None:
raise ValidationError("request body must be JSON")
name = body.get("name")
price = body.get("price")
if not name or not isinstance(name, str):
raise ValidationError("'name' is required and must be a string")
if price is None or not isinstance(price, (int, float)):
raise ValidationError("'price' is required and must be a number")
new_item = Item(id=_next_id, name=name, price=float(price))
_items[_next_id] = new_item
_next_id += 1
return (
jsonify({"id": new_item.id, "name": new_item.name, "price": new_item.price}),
201,
)
|
log_request
Log every incoming request's method and path.
Source code in src/concepts/modern_flask.py
| @api_bp.before_request
def log_request() -> None:
"""Log every incoming request's method and path."""
from flask import current_app
current_app.logger.debug("%s %s", request.method, request.path)
|
add_request_id
add_request_id(response: Response) -> Response
Attach a unique X-Request-ID header to every response.
Source code in src/concepts/modern_flask.py
| @api_bp.after_request
def add_request_id(response: Response) -> Response:
"""Attach a unique X-Request-ID header to every response."""
response.headers["X-Request-ID"] = str(uuid.uuid4())
return response
|
create_app
create_app(testing: bool = False) -> Flask
Build and configure the Flask application.
When testing is True the app runs in testing mode and the in-memory
store is reset to a known state.
Source code in src/concepts/modern_flask.py
| def create_app(testing: bool = False) -> Flask:
"""Build and configure the Flask application.
When *testing* is True the app runs in testing mode and the in-memory
store is reset to a known state.
"""
app = Flask(__name__)
app.testing = testing
if testing:
_reset_store()
# --- Register the API blueprint ---
app.register_blueprint(api_bp)
# --- Error handlers (registered on the app, not the blueprint) ---
@app.errorhandler(ValidationError)
def handle_validation_error(exc: ValidationError) -> tuple[Response, int]:
return jsonify({"error": exc.message}), exc.status_code
@app.errorhandler(404)
def handle_not_found(_exc: Exception) -> tuple[Response, int]:
return jsonify({"error": "not found"}), 404
return app
|