Metadata-Version: 2.4
Name: prodantix-sdk
Version: 0.1.0
Summary: Prodantix server SDK: send events, evaluate feature flags, read the in-app inbox from a Python backend.
Project-URL: Homepage, https://prodantix.com
Author: Bomdisoft
License-Expression: MIT
License-File: LICENSE
Keywords: analytics,events,feature-flags,prodantix
Requires-Python: >=3.9
Description-Content-Type: text/markdown

# Prodantix Python SDK

The server-side SDK for [Prodantix](https://prodantix.com): send analytics events,
evaluate feature flags (remote or locally), and read the in-app inbox from a Python
backend. Zero runtime dependencies: pure standard library.

It speaks the same wire contract as the JS/TS SDK and the server, and its feature-flag
bucketing is **byte-exact** across all three (proven by the shared `flag-golden.json`
vectors), so a user lands in the same rollout everywhere.

## Docs

- [Quickstart](https://prodantix.com/en/docs/quickstart): install and send a first event
- [Get an API key](https://prodantix.com/en/docs/keys): signup, project creation, where the key lives
- [API reference](https://prodantix.com/en/docs/api): every REST operation, rendered

## Install

```
pip install prodantix-sdk
```

Requires Python 3.9+.

## Quickstart

```python
import os
from prodantix import Client

client = Client(
    api_key=os.environ["PRODANTIX_API_KEY"],   # a public pdx_pub_ project key
    host="https://eu.api.prodantix.com",
)

# Send events (distinct_id is explicit on every call, since a server serves many users).
client.capture("user-123", "order.completed", properties={"total": 42, "currency": "USD"})
client.identify("user-123", set_props={"plan": "pro"})
client.group("user-123", "company", "acme", properties={"seats": 10})

# Feature flags: remote (authoritative) …
if client.is_feature_enabled("user-123", "new-checkout"):
    ...

# … or local (evaluated in-process from a cached snapshot, no per-call round-trip).
# Prerequisites resolve against the snapshot; a flag targeting a cohort reads the
# user's memberships from the store unless you pass cohorts=[...] yourself.
if client.is_feature_enabled_local("user-123", "new-checkout", properties={"plan": "pro"}):
    ...

# In-app inbox
for message in client.get_inbox("user-123"):
    ...
client.mark_message_read("user-123", "message-id")

# Flush any buffered events and stop the background flusher.
client.shutdown()
```

`Client` is also a context manager, which shuts down (and flushes) on exit:

```python
with Client(api_key=..., host=...) as client:
    client.capture("user-123", "page.viewed")
```

## How events are delivered

Events are buffered in a thread-safe queue and delivered in batches by a background
flusher. A batch is sent when the queue reaches `flush_at` events (default 20) or every
`flush_interval_s` seconds (default 10), whichever comes first. Delivery retries transient
failures with exponential backoff and fails fast on a permanent 4xx. Always call
`shutdown()` (or use the context manager) before your process exits so the final batch
is flushed.

## Configuration

| Argument | Default | Purpose |
| --- | --- | --- |
| `api_key` | required | Public project key (`pdx_pub_…`). Required. |
| `host` | required | Ingest base URL. Required. |
| `flags_host` | `host` | Base URL for the flags + inbox endpoints. |
| `flush_at` | `20` | Queue size that triggers an immediate flush. |
| `flush_interval_s` | `10.0` | Background flush cadence (`<= 0` disables the timer). |
| `max_queue_size` | `1000` | Cap; oldest events are dropped on overflow. |
| `max_retries` | `3` | Delivery retry attempts. |
| `request_timeout_s` | `10.0` | Per-request timeout. |
| `default_properties` | `None` | A dict or a callable merged into every event. |
| `os`, `locale` | `None` | Added to each event's `context`. |
| `on_error` | no-op | Called with any background/capture error. |
| `stream_flags` | `False` | Hold a Socket.IO subscription to flag changes and refetch the snapshot on push, so local evaluation sees a change without waiting for the 30s poll. |
| `socket_factory` | stdlib client | Builds the websocket connection the flag stream uses; the test seam. |

## Development

```
python -m unittest discover -s tests
```
