app
Flask application factory for AIFT.
Provides the create_app() factory function that initialises the Flask
application, loads configuration from config.yaml, sets the upload size
limit, registers all HTTP route blueprints, and configures CSRF protection.
A Python version guard runs at import time so that downstream code can assume a supported interpreter.
Attributes:
- CSRF_HEADER: Name of the HTTP header used to transmit the CSRF token.
- CSRF_SAFE_METHODS: HTTP methods exempt from CSRF validation (read-only methods that do not modify server state).
1"""Flask application factory for AIFT. 2 3Provides the :func:`create_app` factory function that initialises the Flask 4application, loads configuration from ``config.yaml``, sets the upload size 5limit, registers all HTTP route blueprints, and configures CSRF protection. 6 7A Python version guard runs at import time so that downstream code can 8assume a supported interpreter. 9 10Attributes: 11 CSRF_HEADER: Name of the HTTP header used to transmit the CSRF token. 12 CSRF_SAFE_METHODS: HTTP methods exempt from CSRF validation (read-only 13 methods that do not modify server state). 14""" 15 16from __future__ import annotations 17 18import secrets 19from pathlib import Path 20 21from runtime_compat import assert_supported_python_version 22 23assert_supported_python_version() 24 25from flask import Flask, jsonify, request 26 27from .config import PROJECT_ROOT, load_config 28from .routes import register_routes 29 30__all__ = [ 31 "create_app", 32 "ai_providers", 33 "analyzer", 34 "audit", 35 "case_logging", 36 "chat", 37 "config", 38 "hasher", 39 "parser", 40 "reporter", 41 "routes", 42 "version", 43] 44 45CSRF_HEADER = "X-CSRF-Token" 46CSRF_SAFE_METHODS = frozenset({"GET", "HEAD", "OPTIONS"}) 47 48 49def create_app(config_path: str | None = None) -> Flask: 50 """Create and configure the Flask application instance. 51 52 Loads AIFT configuration (merging defaults, YAML, and environment 53 variables), stores it in ``app.config["AIFT_CONFIG"]``, configures the 54 maximum upload size, generates a per-process CSRF token, installs CSRF 55 validation middleware, and registers all HTTP routes. 56 57 Args: 58 config_path: Optional path to a YAML configuration file. When 59 *None*, the default ``config.yaml`` in the project root is used. 60 61 Returns: 62 A fully configured :class:`~flask.Flask` application instance. 63 """ 64 app = Flask(__name__, template_folder="../templates", static_folder="../static") 65 aift_config = load_config(config_path) 66 # Store the resolved absolute path so all downstream code uses it consistently. 67 resolved_config_path = str(Path(config_path).resolve()) if config_path is not None else str(PROJECT_ROOT / "config.yaml") 68 app.config["AIFT_CONFIG"] = aift_config 69 app.config["AIFT_CONFIG_PATH"] = resolved_config_path 70 71 # Generate a per-process CSRF token for protecting state-changing requests. 72 app.config["CSRF_TOKEN"] = secrets.token_hex(32) 73 74 _register_csrf_protection(app) 75 register_routes(app) 76 77 return app 78 79 80def _register_csrf_protection(app: Flask) -> None: 81 """Install a ``before_request`` hook that validates the CSRF token. 82 83 All requests whose method is not in :data:`CSRF_SAFE_METHODS` must 84 include a valid ``X-CSRF-Token`` header matching the token stored in 85 ``app.config["CSRF_TOKEN"]``. Requests to the CSRF token endpoint 86 itself (``/api/csrf-token``) are exempt so the frontend can obtain the 87 token. 88 89 Args: 90 app: The Flask application to attach the hook to. 91 """ 92 93 @app.before_request 94 def _enforce_csrf() -> tuple | None: 95 """Reject state-changing requests that lack a valid CSRF token. 96 97 Returns: 98 A 403 JSON error response tuple when validation fails, or 99 ``None`` to allow the request to proceed. 100 """ 101 if request.method in CSRF_SAFE_METHODS: 102 return None 103 if request.path == "/api/csrf-token": 104 return None 105 token = request.headers.get(CSRF_HEADER, "") 106 if not secrets.compare_digest(token, app.config["CSRF_TOKEN"]): 107 return jsonify({"error": "CSRF token missing or invalid."}), 403 108 return None 109 110 @app.get("/api/csrf-token") 111 def _get_csrf_token() -> tuple: 112 """Return the CSRF token so the frontend can include it in requests. 113 114 Returns: 115 A JSON response containing the CSRF token with a 200 status. 116 """ 117 return jsonify({"csrf_token": app.config["CSRF_TOKEN"]}), 200
def
create_app(config_path: str | None = None) -> flask.app.Flask:
50def create_app(config_path: str | None = None) -> Flask: 51 """Create and configure the Flask application instance. 52 53 Loads AIFT configuration (merging defaults, YAML, and environment 54 variables), stores it in ``app.config["AIFT_CONFIG"]``, configures the 55 maximum upload size, generates a per-process CSRF token, installs CSRF 56 validation middleware, and registers all HTTP routes. 57 58 Args: 59 config_path: Optional path to a YAML configuration file. When 60 *None*, the default ``config.yaml`` in the project root is used. 61 62 Returns: 63 A fully configured :class:`~flask.Flask` application instance. 64 """ 65 app = Flask(__name__, template_folder="../templates", static_folder="../static") 66 aift_config = load_config(config_path) 67 # Store the resolved absolute path so all downstream code uses it consistently. 68 resolved_config_path = str(Path(config_path).resolve()) if config_path is not None else str(PROJECT_ROOT / "config.yaml") 69 app.config["AIFT_CONFIG"] = aift_config 70 app.config["AIFT_CONFIG_PATH"] = resolved_config_path 71 72 # Generate a per-process CSRF token for protecting state-changing requests. 73 app.config["CSRF_TOKEN"] = secrets.token_hex(32) 74 75 _register_csrf_protection(app) 76 register_routes(app) 77 78 return app
Create and configure the Flask application instance.
Loads AIFT configuration (merging defaults, YAML, and environment
variables), stores it in app.config["AIFT_CONFIG"], configures the
maximum upload size, generates a per-process CSRF token, installs CSRF
validation middleware, and registers all HTTP routes.
Arguments:
- config_path: Optional path to a YAML configuration file. When
None, the default
config.yamlin the project root is used.
Returns:
A fully configured
~flask.Flaskapplication instance.