Metadata-Version: 2.4
Name: toggleai
Version: 0.1.2
Summary: Official Python SDK for ToggleAI — Feature Flags, Remote Configs, and Logging.
Project-URL: Homepage, https://toggleai.app
Project-URL: Documentation, https://docs.toggleai.app/python-sdk
Project-URL: Repository, https://github.com/toggleai/python-sdk
Author-email: ToggleAI <sdk@toggleai.fun>
License: MIT
Keywords: a/b-testing,feature-flags,remote-config,toggleai
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: httpx>=0.25.0
Provides-Extra: dev
Requires-Dist: hatch>=1.8; extra == 'dev'
Requires-Dist: mypy>=1.7; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest-httpx>=0.28; extra == 'dev'
Requires-Dist: pytest>=7.4; extra == 'dev'
Requires-Dist: ruff>=0.1; extra == 'dev'
Description-Content-Type: text/markdown

# toggleai-sdk (Python)

Official Python SDK for [**ToggleAI**](https://toggleai.fun) — Feature Flags, Remote Configs, Error Monitoring, A/B Testing, and Progressive Delivery Telemetry.

[Website](https://toggleai.fun) • [Documentation](https://docs.toggleai.fun)

## Features

- 🌍 **Async-native & Sync-friendly** — built on `httpx` with full `asyncio` support and sync wrappers for Django / Flask.
- ⚡ **Zero-latency evaluation** — evaluates flags locally in-memory after the initial fetch (sub-ms latency).
- 🔄 **Real-time polling** — automatic background refresh keeps configs fresh.
- 🔐 **API Key Validation** — programmatically verify `client_id` + `secret` credentials via `validate_credentials()`.
- 🎯 **Advanced targeting** — full support for targeting rules, user attributes, and percentage rollouts.
- 🐞 **Structured logging** — batched, async-safe log & error ingestion.
- 🧪 **A/B Testing** — track conversions and record exposures for running experiments.
- 🚀 **Progressive Canary Rollouts** — report health metrics and execution logs during deployment stages.
- 🛡️ **Typed** — full type hints throughout, compatible with `mypy --strict`.

## Installation

```bash
pip install toggleai-sdk
# or with uv / poetry
uv add toggleai-sdk
poetry add toggleai-sdk
```

**Requires Python 3.10+**

## Quick Start

### Async (FastAPI, Starlette, etc.)

```python
from toggleai import ToggleAIClient, EvaluationContext

client = ToggleAIClient(
    client_id="pk_live_xxxxxxxxxxxxxxxx",
    secret="sk_live_xxxxxxxxxxxxxxxx",
)

# Optional: Validate credentials before initialization
auth = await client.validate_credentials()
if auth.valid:
    print(f"Authenticated project: {auth.key.project_id}")

await client.init()

# Boolean flag — evaluated locally (sub-ms)
if client.get_flag("new-checkout"):
    show_new_checkout()

# Typed flag value
color = client.get_flag_value("button-color", default="#000")

# Remote config
timeout = client.get_config("api_timeout_ms", default=5000)

# With user context (for targeting + rollout)
ctx = EvaluationContext(user_id="user_123", attributes={"plan": "premium", "country": "US"})
if client.get_flag("premium-feature", ctx):
    ...

await client.close()
```

### Sync (Django / Flask)

```python
from toggleai import ToggleAIClient

client = ToggleAIClient(client_id="pk_live_xxx", secret="sk_live_xxx")

# Validate credentials (sync)
auth = client.validate_credentials_sync()
print(f"Key valid: {auth.valid}")

client.init_sync()                         # blocks until ready

enabled = client.get_flag("dark-mode")
timeout = client.get_config("api_timeout_ms", default=5000)

client.close_sync()                        # flush logs + teardown
```

## Feature Flags

### Local evaluation (instant, cached)

```python
# Boolean
enabled = client.get_flag("my-flag")

# Typed value (multivariate flag)
color   = client.get_flag_value("button-color", default="blue")
max_qty = client.get_flag_value("max-quantity", default=10)

# Full evaluation result with reason
result = client.evaluate_flag("my-flag", ctx)
print(result.reason)        # "TARGETING_MATCH", "ROLLOUT", "DEFAULT", …
print(result.variation_key) # None or "variation-a"

# Evaluate all flags at once
all_results = client.evaluate_all_flags(ctx)
```

### Server-side evaluation (real-time)

```python
# Single flag
result = await client.evaluate_flag_remote("my-flag", ctx)

# All flags
results = await client.evaluate_all_flags_remote(ctx)
```

## Remote Configs

```python
# Single value (typed by caller)
timeout: int = client.get_config("api_timeout_ms", default=5000)
theme:   dict = client.get_config("theme_colors", default={"primary": "#fff"})

# All configs
all_configs = client.get_all_configs()  # Dict[str, Any]

# Existence check
if client.has_config("feature_rollout_message"):
    ...
```

## Logging & Error Monitoring

```python
logger = client.get_logger()

logger.info("User signed in", context={"userId": "u_123"})
logger.warn("Slow query detected", duration_ms=450.0)
logger.error("Payment failed", context={"orderId": "o_99"})
logger.fatal("Out of memory")

# Capture an exception with stack trace
try:
    await risky_operation()
except Exception as exc:
    logger.capture_error(exc, context={"endpoint": "/checkout"})

# Set a default context applied to all subsequent logs
logger.set_context({"service": "payments", "version": "2.1.0"})

# Manual flush (call before shutdown)
await logger.flush()
```

## A/B Experiment Tracking

### Track a conversion event

```python
from toggleai import TrackConversionOptions

await client.track_conversion(TrackConversionOptions(
    experiment_id="exp_xyz",
    variation_id="var_abc",   # DB UUID from evaluate_flag().variation_key
    metric_key="signup",
    value=1.0,
    user_id="user_42",
))
```

### Auto-experiment event tracking

```python
from toggleai import TrackEventOptions

await client.track(TrackEventOptions(
    metric_key="purchase_completed",
    user_identifier="user_42",
    value=29.99,
))
```

### Record an exposure

```python
from toggleai import ExposureOptions

await client.record_exposure(ExposureOptions(
    experiment_id="exp_xyz",
    variation_id="var_abc",
    user_identifier="user_42",
))
```

## Feature Usage Analytics & Sessions

```python
from toggleai import TrackFeatureInteractionOptions, SessionOptions

# Track feature interaction event
await client.track_feature_interaction(
    "new-dashboard",
    TrackFeatureInteractionOptions(
        event_type="click",
        event_name="analytics_widget",
        user_identifier="user_42",
    ),
)

# Start a user session with auto-flushing queue
session = client.start_session(SessionOptions(user_id="user_42"))
session.track("button_click", flag_key="new-dashboard")
await session.flush_async()
session.close()
```

## Progressive Delivery Telemetry

```python
from toggleai import IngestPipelineHealthMetricOptions, IngestPipelineExecutionLogsOptions

# Ingest health metric
await client.ingest_pipeline_health_metric(
    "ex_123",
    IngestPipelineHealthMetricOptions(metric_name="error_rate", value=1.2),
)

# Ingest execution logs
await client.ingest_pipeline_execution_logs(
    "ex_123",
    IngestPipelineExecutionLogsOptions(message="Database query timed out", level="error"),
)
```

## Flag Lifecycle Manager & CI Scanner CLI

```python
from toggleai import CodeReference

# Push code references programmatically
await client.push_code_references(
    "new-checkout",
    [CodeReference(file="app/views.py", line=42, snippet="if client.get_flag('new-checkout'):")],
)
```

### CI Scanner CLI Tool

You can scan your Python codebase recursively for static references to active feature flags using the official ToggleAI Scanner CLI:

```bash
# Scan Python codebase directory and upload code references to ToggleAI
npx toggleai scan --project=my-app --api-key=pk_live_xxx:sk_live_xxx --dir=./

# Or run in CI/CD (GitHub Actions / GitLab CI)
npx toggleai scan --project=my-app --dir=./
```

## Event Listeners

```python
client = ToggleAIClient(
    client_id="pk_live_xxx",
    secret="sk_live_xxx",
    on_ready=lambda: print("ToggleAI ready!"),
    on_config_update=lambda p: print(f"Config refreshed at {p.generated_at}"),
    on_error=lambda e: print(f"SDK error: {e.code} — {e}"),
)
```

## Default Context

```python
client = ToggleAIClient(
    client_id="...",
    secret="...",
    default_context=EvaluationContext(
        attributes={"server_region": "us-east-1", "app_version": "3.0.0"}
    ),
)
```

The default context is merged with every per-call context (per-call takes precedence).

## Error Handling

```python
from toggleai import ToggleAIError

try:
    await client.init()
except ToggleAIError as exc:
    match exc.code:
        case "INVALID_KEY":   print("Check your API credentials.")
        case "RATE_LIMITED":  print("Slow down.")
        case "FORBIDDEN":     print("Insufficient API key scope.")
        case "NETWORK_ERROR": print("Cannot reach the ToggleAI API.")
        case _:               print(f"Unexpected error: {exc}")
```

## API Reference

### Client

| Method | Returns | Description |
|---|---|---|
| `validate_credentials()` | `Awaitable[ValidateKeyResult]` | Validate API credentials & scopes |
| `validate_credentials_sync()` | `ValidateKeyResult` | Sync wrapper for credential validation |
| `init()` | `Awaitable[None]` | Fetch config, start polling |
| `init_sync()` | `None` | Sync wrapper for `init()` |
| `close()` | `Awaitable[None]` | Stop polling, flush, teardown |
| `close_sync()` | `None` | Sync wrapper for `close()` |
| `refresh()` | `Awaitable[None]` | Manually refresh config |
| `is_ready()` | `bool` | Is client initialised? |
| `get_state()` | `ClientState` | Current lifecycle state |

### Feature Flags

| Method | Returns | Description |
|---|---|---|
| `get_flag(key, ctx?, default?)` | `bool` | Boolean flag value |
| `get_flag_value(key, ctx?, default?)` | `Any` | Typed flag value |
| `evaluate_flag(key, ctx?)` | `FlagEvaluationResult` | Full local evaluation |
| `evaluate_all_flags(ctx?)` | `Dict[str, ...]` | All flags, local |
| `evaluate_flag_remote(key, ctx?)` | `Awaitable[FlagEvaluationResult]` | Server-side single |
| `evaluate_all_flags_remote(ctx?)` | `Awaitable[Dict[str, ...]]` | Server-side all |

### Remote Configs

| Method | Returns | Description |
|---|---|---|
| `get_config(key, default?)` | `Any` | Config value |
| `get_all_configs()` | `Dict[str, Any]` | All config values |
| `has_config(key)` | `bool` | Key existence check |
| `get_config_keys()` | `List[str]` | All config keys |

### Logging

| Method | Description |
|---|---|
| `get_logger()` | Get the attached `ToggleAILogger` |
| `logger.info(msg, **kw)` | Log an info message |
| `logger.error(msg_or_exc, **kw)` | Log error or exception |
| `logger.capture_error(exc, **kw)` | Capture exception + stack trace |
| `logger.set_context(ctx)` | Set default log context |
| `await logger.flush()` | Flush queued events |

## Backend Endpoints Reference

| Endpoint | Method | Description |
|---|---|---|
| `/sdk/validate` | `POST` | Validate `clientId` + `secret` credentials and retrieve key details |
| `/sdk/config` | `GET` | Fetch full configuration payload (flags, configs, experiments) |
| `/sdk/evaluate` | `POST` | Real-time server-side evaluation for all flags |
| `/sdk/evaluate/:flagKey` | `POST` | Real-time server-side evaluation for a single flag |
| `/sdk/connect` | `POST` | Register SDK connection runtime telemetry |
| `/sdk/metrics` | `POST` | Batch report evaluation counts and cache hit rates |
| `/sdk/logs/ingest` | `POST` | Ingest a single error / log event |
| `/sdk/logs/ingest/batch` | `POST` | Ingest a batch of log events |
| `/sdk/experiments/:id/track` | `POST` | Track conversion event for a specific experiment variation |
| `/sdk/track` | `POST` | Generic event tracking with auto-experiment attribution |
| `/sdk/expose` | `POST` | Record user exposure to active experiment variations |
| `/sdk/interactions` | `POST` | Ingest feature interaction event |
| `/sdk/interactions/batch` | `POST` | Ingest batch of feature interaction events |
| `/sdk/:projectId/lifecycle/code-references` | `POST` | Push AST/regex code references |
| `/sdk/executions/:id/health-metric` | `POST` | Ingest health probe metric for progressive deployment stage |
| `/sdk/executions/:id/logs` | `POST` | Ingest error log telemetry for progressive deployment stage |

## License

MIT
