"""Feature flags: what can be switched, and what is switched right now. Ship-dark, not a kill switch. Flags let work merge and deploy without becoming visible; they are expected to flip about monthly, by a person, deliberately. Nothing here does percentage rollouts or user targeting — the site has no user identity to target. Unleash holds the state. It does not hold the list. REGISTRY below is that list, and it exists for three reasons: the SDK evaluates an unknown flag to False, so without a registry that is an *undeclared* False, indistinguishable from a typo; /api/flags needs a key set to return when Unleash is unreachable; and a flag in the UI but not in the registry is orphaned and should be visibly so rather than quietly authoritative. Every flag defaults to False. There is no per-flag default, because a flag that defaults on is a kill switch, and this is not one. """ from __future__ import annotations import logging from dataclasses import dataclass from datetime import date from .config import settings logger = logging.getLogger(__name__) # A flag is temporary scaffolding. See test_a_flag_older_than_the_limit. MAX_FLAG_AGE_DAYS = 90 @dataclass(frozen=True) class Flag: # One string: the registry key, the Unleash flag name, and the JSON key in # /api/flags. snake_case, matching the API's existing convention. No case # transformation anywhere, so there is no mapping layer to get wrong. name: str description: str # one line: what turning this on reveals added: date # for the staleness tripwire REGISTRY: dict[str, Flag] = { f.name: f for f in ( Flag( name="admission_distance", description=( "The last-distance-offered figure on the Admissions tile and " "the 'How far away are you?' section on school pages." ), added=date(2026, 8, 23), ), ) } _client = None def init() -> None: """Start the Unleash client, or log why flags are all off. Called once from the app lifespan. Never raises: a flag system that can stop the API from booting is worse than one that is switched off. """ global _client if not settings.unleash_url or not settings.unleash_api_token: logger.warning( "Unleash is not configured (UNLEASH_URL / UNLEASH_API_TOKEN); " "every feature flag evaluates to False.") return try: from UnleashClient import UnleashClient _client = UnleashClient( url=settings.unleash_url, app_name=settings.unleash_app_name, custom_headers={"Authorization": settings.unleash_api_token}, cache_directory=settings.unleash_cache_directory, refresh_interval=15, ) _client.initialize_client() logger.info("Unleash client initialised against %s", settings.unleash_url) except Exception: # Fail closed and keep serving. The SDK also evaluates everything False # until its first successful sync, so this is the same direction. _client = None logger.exception("Unleash client failed to start; flags are all False.") def is_enabled(name: str) -> bool: """Whether `name` is on. False for anything unknown, unreachable or broken.""" if name not in REGISTRY: logger.error( "undeclared feature flag %r was evaluated; returning False. " "Add it to backend/flags.py REGISTRY or fix the name.", name) return False if _client is None: return False try: return bool(_client.is_enabled( name, fallback_function=lambda feature_name, context: False)) except Exception: logger.exception("flag %r failed to evaluate; returning False", name) return False def all_flags() -> dict[str, bool]: """Every declared flag and its current value. Serves /api/flags.""" return {name: is_enabled(name) for name in REGISTRY}