""" app/tenancy/middleware.py ------------------------- Wires tenant resolution into the Flask request lifecycle. `init_tenancy(app)` registers a single app-level before_request handler that: * always clears g.tenant / g.tenant_engine (so downstream code can rely on them) * does NOTHING further when MULTI_TENANT_ENABLED is False → today's behaviour * bypasses static + configured exempt paths (health checks) * otherwise resolves the Host header to a tenant and selects its engine * returns a 404 page for an unknown / unverified / suspended host Flask-SQLAlchemy already removes the scoped session on app-context teardown, so each request rebinds via RoutingSession.get_bind against the fresh g.tenant_engine — no teardown handler is needed here. """ from flask import g, request, current_app, Response from app.tenancy.resolver import resolve_tenant from app.tenancy.engine_cache import get_tenant_engine _UNKNOWN_TENANT_PAGE = ( "" "" "Workspace not found" "
" "

Workspace not found

" "

This address isn’t linked to an active JQC workspace.

" "

Check the URL, or contact your administrator.

" "
" ) def _is_exempt(path): if path.startswith('/static/'): return True for prefix in current_app.config.get('MULTI_TENANT_EXEMPT_PATHS', []): if prefix and path.startswith(prefix): return True return False def init_tenancy(app): @app.before_request def _resolve_tenant(): # Default state — referenced safely by downstream code regardless of mode. g.tenant = None g.tenant_engine = None if not current_app.config.get('MULTI_TENANT_ENABLED', False): return # inert: default database serves everything (single-tenant) if _is_exempt(request.path): return host = (request.host or '').split(':')[0].strip().lower() tenant = resolve_tenant(host) if tenant is None: return Response(_UNKNOWN_TENANT_PAGE, status=404, mimetype='text/html') g.tenant = tenant g.tenant_engine = get_tenant_engine(tenant)