Metadata-Version: 2.4
Name: mitra-feature-flags-sdk-python
Version: 1.0.0
Summary: Feature flag SDK for Mitra Python services
Project-URL: Homepage, https://github.com/mitralab-dev/mitra-feature-flags-sdk-python
Project-URL: Repository, https://github.com/mitralab-dev/mitra-feature-flags-sdk-python
Project-URL: Issues, https://github.com/mitralab-dev/mitra-feature-flags-sdk-python/issues
Author: Mitra Platform
Classifier: License :: Other/Proprietary License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: httpx<1,>=0.28.1
Provides-Extra: test
Requires-Dist: build<2,>=1.2; extra == 'test'
Requires-Dist: mypy<2,>=1.15; extra == 'test'
Requires-Dist: pytest-asyncio<1,>=0.25; extra == 'test'
Requires-Dist: pytest-cov<7,>=6; extra == 'test'
Requires-Dist: pytest<9,>=8.3; extra == 'test'
Requires-Dist: ruff<1,>=0.11; extra == 'test'
Requires-Dist: twine<7,>=6; extra == 'test'
Description-Content-Type: text/markdown

# Mitra Feature Flags SDK Python

SDK assíncrono para carregar feature flags em serviços Python da Mitra e avaliá-las localmente. O SDK mantém um snapshot imutável em memória, preserva o último valor válido em falhas e usa o fallback declarado no código quando uma flag não existe.

## Instalação

```bash
pip install mitra-feature-flags-sdk-python
```

## Uso

```python
from mitra_feature_flags import (
    BooleanFlag,
    FeatureFlagsClient,
    FeatureFlagsConfig,
    StringListFlag,
)

EMBEDDED_IDE = BooleanFlag("EMBEDDED_IDE", False)
TENANTS_IN_ROLLOUT = StringListFlag("TENANTS_IN_ROLLOUT")

feature_flags = FeatureFlagsClient(
    FeatureFlagsConfig(
        service_name="mitra-sandbox",
        environment="DEV",
        internal_secret=settings.internal_secret,
    )
)

feature_flags.start()

if feature_flags.is_enabled(EMBEDDED_IDE):
    ...

if feature_flags.contains(TENANTS_IN_ROLLOUT, tenant_id):
    ...

await feature_flags.close()
```

`start()` é síncrono: agenda o refresh em segundo plano e retorna sem bloquear o startup. `is_enabled()`, `get_string_list()` e `contains()` leem somente memória e nunca fazem I/O.

## Configuração

| Campo | Obrigatório | Default | Descrição |
|---|---|---|---|
| `service_name` | sim | sem default | Nome do serviço em lower kebab case, com até 100 caracteres. |
| `environment` | sim | sem default | Ambiente da flag, normalizado para upper snake case, com até 100 caracteres. |
| `internal_secret` | sim | sem default | Segredo usado no header interno. Nunca aparece no `repr` da configuração. |
| `base_url` | não | `http://mitra-feature-flags.mitra.local:8080` | URL do control plane. |
| `refresh_interval_seconds` | não | `60.0` | Intervalo base entre atualizações. |
| `refresh_jitter_seconds` | não | `10.0` | Variação aleatória adicional. Aceita zero. |
| `connect_timeout_seconds` | não | `0.2` | Timeout para abrir a conexão HTTP. |
| `read_timeout_seconds` | não | `0.5` | Timeout para receber a resposta HTTP. |

## Lifecycle com FastAPI

O SDK não depende de FastAPI, mas encaixa no lifecycle nativo da aplicação:

```python
from contextlib import asynccontextmanager

from fastapi import FastAPI


@asynccontextmanager
async def lifespan(_app: FastAPI):
    feature_flags.start()
    yield
    await feature_flags.close()


app = FastAPI(lifespan=lifespan)
```

## Contrato

- Nomes de flags usam `UPPER_SNAKE_CASE` e têm até 100 caracteres.
- Valores aceitos são boolean ou lista de strings.
- O snapshot é filtrado pelo `service_name` e `environment` enviados ao control plane.
- `X-Internal-Secret` nunca é logado.
- `ETag` evita baixar snapshots sem alteração.
- Payload inválido ou serviço indisponível preserva o último snapshot válido.

## Desenvolvimento

```bash
ruff check .
mypy
pytest
python -m build
python -m twine check dist/*
```
