"""Feature-flag gate helpers. The four compliance flags in ``app.config.Settings`` (``PORTFOLIO_AI_ENABLED``, ``PORTFOLIO_SYNC_ENABLED``, ``TICKER_UNIVERSE_AGGREGATE_ENABLED``, ``SUBSCRIPTIONS_ENABLED``) gate code paths that stay in the tree but are inactive by default. Routes / dependencies use ``require_flag()`` to 404 a whole endpoint when its flag is off — making the surface indistinguishable from a non-existent route. """ from __future__ import annotations from fastapi import HTTPException, status from app.config import get_settings def flag_enabled(flag_name: str) -> bool: """Read a boolean flag from Settings. Unknown flags raise — typos here would silently disable features otherwise.""" settings = get_settings() if not hasattr(settings, flag_name): raise AttributeError(f"unknown feature flag: {flag_name}") return bool(getattr(settings, flag_name)) def require_flag(flag_name: str): """FastAPI dependency factory: 404 the route if the flag is off. Usage:: @router.post("/analyze", dependencies=[Depends(require_flag("PORTFOLIO_AI_ENABLED"))]) 404 (not 503) is deliberate: a paused feature should be indistinguishable from a missing route to clients and crawlers.""" async def _gate() -> None: if not flag_enabled(flag_name): raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="not found", ) return _gate