Ir para o conteúdo

Feature flags

Ligue e desligue funcionalidades sem redeploy: rollouts graduais, kill-switches, beta por trás de um flag. O SDK traz um serviço FeatureFlags sobre backends plugáveis (env estático, Redis em runtime, ou os dois em camadas) e uma dependência FastAPI que protege rotas.

Começo rápido

from tempest_fastapi_sdk import FeatureFlags, MemoryFeatureFlagBackend

flags = FeatureFlags(MemoryFeatureFlagBackend({"new_checkout": True}))

if await flags.is_enabled("new_checkout"):
    ...                                                       # caminho novo

is_enabled(name) devolve o valor do flag, ou o default do serviço (False) quando o flag não existe. Passe default=True por chamada para inverter isso pontualmente. Toggle com enable / disable / set, e liste tudo com all().

Backends

Backend Quando usar
MemoryFeatureFlagBackend(initial=...) Testes e dev (em processo).
EnvFeatureFlagBackend(prefix="FEATURE_") Config estáticanew_checkoutFEATURE_NEW_CHECKOUT. Read-only (set levanta).
RedisFeatureFlagBackend(redis_client, key="feature_flags") Toggle em runtime, compartilhado entre réplicas (um hash Redis).
CompositeFeatureFlagBackend([redis, env]) Camadas: o override do Redis vence o default do env.

Valores aceitos como verdadeiros

1, true, yes, on, t, y (case-insensitive) viram True; qualquer outra coisa é False. Vale para env e Redis.

Produção: Redis sobre env

O padrão recomendado usa o env como default estático (versionado no deploy) e o Redis como override de runtime (o time liga/desliga sem subir release):

from redis.asyncio import Redis

from tempest_fastapi_sdk import (
    CompositeFeatureFlagBackend,
    EnvFeatureFlagBackend,
    FeatureFlags,
    RedisFeatureFlagBackend,
)


def build_flags(redis: Redis) -> FeatureFlags:
    """Monta o serviço de flags com override Redis sobre default de env.

    Args:
        redis (Redis): Cliente async de Redis conectado.

    Returns:
        FeatureFlags: O serviço pronto para injetar.
    """
    backend = CompositeFeatureFlagBackend(
        [
            RedisFeatureFlagBackend(redis, key="feature_flags"),  # runtime
            EnvFeatureFlagBackend(prefix="FEATURE_"),             # default
        ]
    )
    return FeatureFlags(backend)

Protegendo uma rota

make_flag_dependency(flags, name) devolve uma dependência async que deixa a rota passar só quando o flag está ligado. Caso contrário levanta AppException no envelope do SDK — 404 por padrão, para a feature simplesmente "não existir":

from fastapi import APIRouter, Depends

from tempest_fastapi_sdk import make_flag_dependency

from src.api.dependencies.resources import get_flags

router = APIRouter()
flags = get_flags()


@router.get(
    "/checkout/v2",
    dependencies=[Depends(make_flag_dependency(flags, "new_checkout"))],
)
async def checkout_v2() -> dict[str, bool]:
    """Só responde quando ``new_checkout`` está ligado."""
    return {"ok": True}

Para um kill-switch de algo legado, inverta o gate com enabled=False (a rota só responde enquanto o flag está desligado):

@router.get(
    "/legacy",
    dependencies=[
        Depends(make_flag_dependency(flags, "legacy_disabled", enabled=False)),
    ],
)
async def legacy() -> dict[str, bool]:
    """Para de responder no instante em que ``legacy_disabled`` é ligado."""
    return {"ok": True}

status_code, detail e code são configuráveis — use status_code=403 quando quiser sinalizar "existe mas é proibido" em vez de esconder com 404.

Recapitulando

  • FeatureFlags(backend, default=False)is_enabled / enable / disable / set / all.
  • Backends: Memory (dev), Env (estático read-only), Redis (runtime), Composite (camadas).
  • make_flag_dependency(flags, name, enabled=True, status_code=404) protege rotas.
  • Override Redis sobre default de env é o padrão de produção — toggle sem redeploy.