Metadata-Version: 2.5
Name: hanzo-flags
Version: 0.1.0
Summary: Hanzo feature flags + A/B client — the Python side of the native flags engine (cloud /v1/flags), zero runtime deps, PostHog-compatible
Project-URL: Homepage, https://hanzo.ai
Project-URL: Documentation, https://docs.hanzo.ai
Project-URL: Repository, https://github.com/hanzoai/python-sdk
Author-email: Hanzo AI <dev@hanzo.ai>
License: MIT
Keywords: ab-testing,experiments,feature-flags,hanzo,posthog
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
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: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.9
Provides-Extra: dev
Requires-Dist: mypy>=1.0.0; extra == 'dev'
Requires-Dist: pytest>=7.0.0; extra == 'dev'
Requires-Dist: ruff>=0.1.0; extra == 'dev'
Description-Content-Type: text/markdown

# hanzo-flags

Feature flags + A/B testing for Python, on the Hanzo native flags engine.

The Python client for cloud `/v1/flags` — the same PostHog-compatible evaluation
endpoint the Rust core, Go binding, and `@hanzo/flags` (TypeScript) all speak. One
flag definition, evaluated identically in every language.

**Zero runtime dependencies** — stdlib `urllib` only. A flag check must never drag
a dependency tree into a service.

## Install

```bash
pip install hanzo-flags     # or: uv add hanzo-flags
```

## Use

```python
from hanzo_flags import HanzoFlags

flags = HanzoFlags("https://api.hanzo.ai", token="sk-...")
flags.load("user-123", person_properties={"plan": "pro"})

if flags.is_enabled("checkout-exp"):
    ...

variant = flags.variant("pricing-test")   # "control" | "b" | None
payload = flags.payload("pricing-test")   # the flag's JSON payload, or None
```

Group targeting (orgs/teams):

```python
from hanzo_flags import Group

flags.load(
    "user-123",
    groups={"0": Group(key="acme", properties={"tier": "gold"})},
)
```

Async (asyncio services):

```python
from hanzo_flags import AsyncHanzoFlags

flags = AsyncHanzoFlags("https://api.hanzo.ai", token="sk-...")
await flags.load("user-123")
if flags.is_enabled("checkout-exp"):
    ...
```

One-shot:

```python
from hanzo_flags import evaluate
res = evaluate("https://api.hanzo.ai", "user-123", token="sk-...")
res.is_enabled("checkout-exp")
```

## Guarantees

- **Fail-open.** A transport or decode error returns the last good (or empty)
  result with `errors_while_computing` set — `load()` never raises on the hot path.
- **Cached by context + TTL** (default 15s). Re-evaluating the same context inside
  the TTL is free, so a hot path may call `load()` freely.

## The family

| Language   | Package               | Mode                                   |
|------------|-----------------------|----------------------------------------|
| Rust       | `hanzo-flags` crate   | native (the evaluation core)           |
| Go         | `cloud/clients/flags` | in-process via FFI to the Rust core    |
| TypeScript | `@hanzo/flags`        | HTTP to `/v1/flags` (browser + node)   |
| Python     | `hanzo-flags`         | HTTP to `/v1/flags` (this package)     |

All four resolve the same flag to the same value: the definitions live once in the
cloud flags cockpit (`/v1/flags/defs`), and every client evaluates against them.
