Metadata-Version: 2.4
Name: audiencelab_python_sdk
Version: 1.2.0
Summary: Python SDK for AudienceLab services
Home-page: https://github.com/Geeklab-Ltd/audiencelab_python_sdk
Author: Nathan Nylund
Author-email: nathan@geeklab.app
Classifier: Programming Language :: Python :: 3
Requires-Python: >=3.6
Description-Content-Type: text/markdown
License-File: LICENSE.md
Requires-Dist: requests>=2.0.0
Dynamic: author
Dynamic: author-email
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: home-page
Dynamic: license-file
Dynamic: requires-dist
Dynamic: requires-python
Dynamic: summary

# AudienceLab Python SDK

A server-side Python client for integrating with the AudienceLab analytics API. Designed for backend services that need to register users, fetch creative tokens, and send in-app events (purchases, ad views, retention, and custom events) on behalf of your application.

## Dependencies

| Package | Minimum Version | Purpose |
|---|---|---|
| `requests` | `>=2.0.0` | HTTP client for all AudienceLab API communication |
| Python | `>=3.6` | Runtime requirement (f-strings, `datetime.fromisoformat`) |

No additional system-level dependencies are required.

---

## Installation

```bash
pip install audiencelab-python-sdk
```

Or install from source:

```bash
git clone https://github.com/Geeklab-Ltd/audiencelab_python_sdk.git
cd audiencelab_python_sdk
pip install -e .
```

---

## Backend Integration Checklist

Before using the SDK, confirm the following with your AudienceLab backend:

| Requirement | Detail |
|---|---|
| **API Key** | Obtain a valid `geeklab-api-key` from the AudienceLab dashboard or your account manager. |
| **Base URL** | Confirm the correct API base URL for your environment (production vs. staging). |
| **User ID scheme** | Agree on the user identifier format your backend will use (e.g. UUID, internal ID, hashed value). |
| **Endpoint availability** | Verify that `/v2/fetch-token`, `/v2/creativetoken`, and `/webhook` endpoints are reachable from your server. |
| **IP forwarding** | If your backend sits behind a proxy/load balancer, ensure the real client IP is forwarded to the SDK. |
| **Timestamp format** | All timestamps must be ISO 8601 with timezone offset. The SDK normalizes to UTC internally. |
| **Event deduplication** | If you send events from multiple services, use the optional `dedupe_key` parameter to prevent duplicate processing. |
| **Creative token flow** | Decide whether your backend will call `RegisterUser` (first launch) or `FetchToken` (subsequent sessions) to obtain the creative token. |

---

## SDK Version

```python
from audiencelab_python_sdk import SDKVersion

print(SDKVersion.get_version())          # "1.2.0"
print(SDKVersion.get_sdk_type())         # "S2S_python"
print(SDKVersion.get_full_identifier())  # "S2S_python-1.2.0"
```

The package also exposes `audiencelab_python_sdk.__version__` for compatibility.

---

## Basic Usage

### 1. Initialize the Client and Construct User Data

Import the necessary classes from the SDK. Create a client instance using your API key and base URL, then build the user data object.

> **IMPORTANT:** Full device information is only required during the initial user registration. For subsequent events, provide the user ID and IP address. If the event did not occur in real time, include the event timestamp (ISO 8601).

```python
from audiencelab_python_sdk import (
    Client,
    UserData,
    RegisterUser,
    FetchToken,
    RetentionData,
    PurchaseData,
    AdData,
    CustomEventData,
    SessionData,
    AppEvent,
)

api_key = "your_api_key"
base_url = "https://example.com"
client = Client(api_key, base_url)

user_data = (
    UserData()
    .set_user_id("user_12345")
    .set_user_ip("123.123.1.1")
    .set_event_timestamp("2024-10-05T16:48:00+02:00")
    .set_device_info({
        "device_name": "My Device",
        "height": 2436,
        "width": 1125,
        "os": "iOS",
        "os_version": "14.4",
        "device_model": "iPhone15,4",
        "timezone": "America/New_York",
    })
    .set_app_version("2.5.1")
    .set_ifv("6D92078A-8246-4BA4-AE5B-76104861E7DC")
    .set_whitelisted_properties({"subscription_tier": "gold"})
)
```

Whitelisted properties are durable SDK context and are sent as `wp` on registration and app-event requests. Use `set_whitelisted_property`, `set_whitelisted_properties`, `unset_whitelisted_property`, `clear_whitelisted_properties`, and `get_whitelisted_properties` to manage them. Customer properties are limited to 50 keys, 64 characters per key, 256 characters per string value, and 2048 serialized bytes. Keys beginning with `_` are reserved for backend-returned properties; when `set_user_creative_token_info(response)` receives `wp`, the SDK merges those backend properties without overwriting caller-set values.

If your backend/app can track sessions, you can optionally attach session context to regular app events:

```python
session_id = UserData.generate_session_id()
user_data.set_session_context(session_id, 1)
```

---

### 2.1 Register a User

Call this once when a new user first downloads and opens the app. The SDK posts the current S2S envelope to `/v2/fetch-token`, including SDK metadata, device metrics, identity fields, and `wp` when provided. The response includes creative token information, which updates your user data when you pass the response to `set_user_creative_token_info`. When using this flow, step 2.2 is not required.

```python
register_event = RegisterUser(client, user_data)
try:
    response = register_event.send()
    user_data.set_user_creative_token_info(response)
    print("User registered & creative token set.")
except Exception as e:
    print("Error during user registration:", e)
```

---

### 2.2 Fetch the Creative Token

Call this once before sending any in-app events when the user has already been registered.

```python
fetch_event = FetchToken(client, user_data)
try:
    response = fetch_event.send()
    user_data.set_user_creative_token_info(response)
    print("Creative token set.")
except Exception as e:
    print("Error fetching creative token:", e)
```

---

### 3. Send In-App Events

`AppEvent` sends the current webhook envelope: stable generated `eid`, optional developer dedupe key as `dk`, `created_at`, `user_id`, SDK metadata, persisted `wp`, cumulative totals, and event payload. Pass `blacklisted_properties={...}` to `AppEvent` when you need to send per-event `bp` values. Blacklisted properties are copied only into that event's webhook body and are not stored on `UserData` or reused on later events.

#### Retention Event

Send each time the user opens the app, including after the initial registration event.

```python
if user_data.should_send_retention_event():
    retention_data = RetentionData().set_retention_data(user_data)
    retention_event = AppEvent(client, retention_data, user_data)
    try:
        response = retention_event.send()
        print("Retention event sent")
    except Exception as e:
        print("Error sending retention event:", e)
```

`should_send_retention_event()` is an in-process helper. For multi-worker backends, persist `user_data.get_retention_guard_key()` or the last sent retention day in your own store and pass it back into `should_send_retention_event(last_sent_retention_day=...)`.

#### Purchase Event

```python
purchase_data = (
    PurchaseData()
    .set_item_id("item_123")
    .set_item_name("No Ads")
    .set_value(3.99)
    .set_currency("usd")
    .set_status("completed")
    .set_transaction_id("txn_abc123")
    .set_dedupe_key("purchase-txn-abc123")
)
purchase_event = AppEvent(client, purchase_data, user_data)
try:
    response = purchase_event.send()
    print("Purchase event sent")
except Exception as e:
    print("Error sending purchase event:", e)
```

Completed and successful purchases update the SDK's local cumulative mirror and include `total_purchase_value` in the webhook body. The S2S backend remains authoritative and updates stored totals atomically from the event payload.

#### Ad View Event

```python
ad_data = (
    AdData()
    .set_ad_id("ad_123")
    .set_name("New Game Ad")
    .set_source("rewarded_video")
    .set_media_source("admob")
    .set_channel("paid")
    .set_watch_time(4321)
    .set_reward(True)
    .set_value(0.00035)
    .set_currency("usd")
    .set_dedupe_key("ad-view-xyz789")
)
ad_event = AppEvent(client, ad_data, user_data)
try:
    response = ad_event.send()
    print("Ad view event sent")
except Exception as e:
    print("Error sending ad view event:", e)
```

Ad events update the SDK's local cumulative mirror and include `total_ad_value` in the webhook body. The SDK does not call `/v2/cumulative` for new S2S webhook flows.

#### Custom Event

```python
custom_data = (
    CustomEventData()
    .set_event_name("level_complete")
    .set_properties({"level": 5, "score": 12000, "time_seconds": 94})
    .set_dedupe_key("level-5-complete-user123")
)
custom_event = AppEvent(client, custom_data, user_data)
try:
    response = custom_event.send()
    print("Custom event sent")
except Exception as e:
    print("Error sending custom event:", e)
```

Custom event properties are normalized before sending. The public setters stay Pythonic, but the payload emitted to the webhook uses the current keys: event name as `en` and properties as `pr`. Scalar values, nested dictionaries, lists/tuples/sets, dates, datetimes, UUIDs, and enums are converted into JSON-friendly values.

#### Session Events

Session tracking is optional for server-side integrations. Use it only when your backend or app can reliably provide a session ID, index, and duration.

```python
session_id = SessionData.generate_session_id()

session_start = SessionData.start(session_id=session_id, session_index=1)
AppEvent(client, session_start, user_data).send()

session_end = SessionData.end(
    session_id=session_id,
    session_index=1,
    duration_seconds=342,
    reason="background_timeout",
)
AppEvent(client, session_end, user_data).send()
```

---

## API Endpoints Used

| Endpoint | Method | Used By | Purpose |
|---|---|---|---|
| `/v2/fetch-token` | POST | `RegisterUser` | Register a new user and obtain creative token |
| `/v2/creativetoken` | GET | `FetchToken` | Fetch creative token for an existing user |
| `/webhook` | POST | `AppEvent` | Send retention, purchase, ad, and custom events |

---

## Environment Variables (for testing)

| Variable | Purpose |
|---|---|
| `AUDIENCELAB_API_KEY` | API key for integration tests |
| `AUDIENCELAB_BASE_URL` | Base URL for integration tests |

---

## Running Tests

```bash
# Set up virtual environment
./setup_venv.sh

# Set environment variables
export AUDIENCELAB_API_KEY="your_key"
export AUDIENCELAB_BASE_URL="https://your-api-endpoint.com"

# Run unit tests. Integration tests are skipped unless the variables above are set.
python -m unittest discover -s tests -p '*test.py' -v
```

---

## License

This project is licensed under the terms of the [GEEKLAB SDK EULA](https://github.com/Geeklab-Ltd/audiencelab_python_sdk/blob/main/LICENSE.md).
