Metadata-Version: 2.4
Name: clue-backend-sdk
Version: 1.0.0
Summary: Clue Backend SDK (Python) — OTel-based observation-source event capture for Django, FastAPI, Celery, SQLAlchemy, LangChain, LlamaIndex, CrewAI, MCP, and AI providers.
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: opentelemetry-api<2.0,>=1.42.1
Requires-Dist: opentelemetry-sdk<2.0,>=1.42.1
Provides-Extra: instrumentation-requests
Requires-Dist: opentelemetry-instrumentation-requests<1.0,>=0.63b1; extra == "instrumentation-requests"
Provides-Extra: instrumentation-httpx
Requires-Dist: opentelemetry-instrumentation-httpx<1.0,>=0.63b1; extra == "instrumentation-httpx"
Provides-Extra: instrumentation-fastapi
Requires-Dist: fastapi<1.0,>=0.100; extra == "instrumentation-fastapi"
Requires-Dist: opentelemetry-instrumentation-fastapi<1.0,>=0.63b1; extra == "instrumentation-fastapi"
Provides-Extra: instrumentation-asgi
Requires-Dist: opentelemetry-instrumentation-asgi<1.0,>=0.63b1; extra == "instrumentation-asgi"
Provides-Extra: instrumentation-sqlalchemy
Requires-Dist: opentelemetry-instrumentation-sqlalchemy<1.0,>=0.63b1; extra == "instrumentation-sqlalchemy"
Provides-Extra: instrumentation-django
Requires-Dist: opentelemetry-instrumentation-django<1.0,>=0.63b1; extra == "instrumentation-django"
Provides-Extra: instrumentation-celery
Requires-Dist: opentelemetry-instrumentation-celery<1.0,>=0.63b1; extra == "instrumentation-celery"
Provides-Extra: test
Requires-Dist: pytest<9.0,>=8.0; extra == "test"
Requires-Dist: opentelemetry-instrumentation-requests<1.0,>=0.63b1; extra == "test"
Requires-Dist: opentelemetry-instrumentation-httpx<1.0,>=0.63b1; extra == "test"
Requires-Dist: opentelemetry-instrumentation-fastapi<1.0,>=0.63b1; extra == "test"
Requires-Dist: opentelemetry-instrumentation-asgi<1.0,>=0.63b1; extra == "test"
Requires-Dist: opentelemetry-instrumentation-sqlalchemy<1.0,>=0.63b1; extra == "test"
Requires-Dist: opentelemetry-instrumentation-django<1.0,>=0.63b1; extra == "test"
Requires-Dist: opentelemetry-instrumentation-celery<1.0,>=0.63b1; extra == "test"
Requires-Dist: requests>=2.31; extra == "test"
Requires-Dist: httpx>=0.26; extra == "test"
Requires-Dist: sqlalchemy>=2.0; extra == "test"
Requires-Dist: django<6.0,>=5.0; extra == "test"
Requires-Dist: fastapi<1.0,>=0.100; extra == "test"
Requires-Dist: celery<6.0,>=5.0; extra == "test"
Requires-Dist: langchain-core<1.0,>=0.3; extra == "test"
Requires-Dist: llama-index-core<1.0,>=0.12; extra == "test"
Dynamic: requires-python

# clue-backend-sdk

Shared Python backend core for Clue SDK wrappers (Django, FastAPI,
Celery). Events flow through an OTel `TracerProvider` + `BatchSpanProcessor` +
`ObservationSourceEventExporter` to
`/api/v1/ingest/backend`.

## Minimal integration

The SDK exposes a single ``clue`` namespace with eight methods (``init`` /
``identify`` / ``group`` / ``reset`` / ``track`` / ``set_consent`` /
``flush`` / ``close``). This mirrors the cross-SDK contract documented in
``docs/contracts/sdk/public-api.md`` §1.6.

```python
import asyncio
from clue_backend_sdk import clue

clue.init({
    "endpoint": "https://ingest.example.com/api/v1/ingest/backend",
    "project_key": "...",
    "api_key": "...",
    "service_key": "backend-api",
})

clue.identify("user_42", {"name": "Alice", "email": "alice@example.com"})
clue.group("organization", "org_7", {"name": "Acme Inc"})
clue.track("order_placed", {"product_name": "shirt", "amount": 100})
clue.set_consent("granted")

# Shutdown idiom — both are coroutines for cross-SDK parity.
asyncio.run(clue.flush())
asyncio.run(clue.close())
```

Do not pass ``environment`` or ``service_name`` in the MVP setup path.
Clue derives dev/prod from the project key prefix, and ``service_key`` is the
single literal customer-facing backend service label.

``clue.track`` custom event names must match
``^[A-Za-z0-9_.:-]{1,128}$``. Invalid names are ignored and do not emit
a source-signal custom track intent. ``clue.set_consent`` only records local
SDK state in the MVP backend SDK; it does not emit consent transition events.

Framework wrappers and runtime helpers are imported from their owning modules.
The package root is intentionally small: customer-facing lifecycle methods plus
stable setup helpers. Direct event builders are not public extension
points.

## Architecture boundary

The Python SDK is a thin language wrapper around OpenTelemetry and the Clue
transport. Framework integrations create spans or annotate the current span,
the exporter serializes those spans as ``sdk_source_signal_observed`` events,
and the Clue backend ingest core performs classification, normalization,
privacy projection, and raw-ingest shaping.

```text
Python framework / library hook
  -> OTel span or Clue source-signal attributes
  -> ObservationSourceEventExporter
  -> /api/v1/ingest/backend
  -> apps/api/src/modules/ingest core
```

Rules:

- Keep framework code limited to raw facts, correlation context, and standard
  OTel attributes.
- Do not add Python-only classification, direct event builders, or custom
  fallback behavior.
- Add classifier behavior in the backend ingest core so Node.js, Python, and
  future SDK wrappers receive the same behavior.
- Preserve ``interaction_id``, ``request_span_id``, ``request_id``, and
  ``trace_id`` whenever they are available. User-action linkage is an ingest
  contract, not a display-only field.

## Privacy and PII handling

### 1. Hard-deny: PII / secrets are stripped before transport

The SDK strips a built-in set of property keys **before** the event
leaves the customer process. Caller-supplied `denied_keys` are added
on top of `DEFAULT_DENIED_KEYS` — they cannot remove a default-denied
key.

Hard-deny categories (case- and separator-insensitive — `userEmail`,
`user-email`, `USER_EMAIL`, `email_address` all match):

- Auth credentials: `authorization`, `cookie`, `set-cookie`,
  `password`, `passwd`, `secret`, `token`, `access_token`,
  `refresh_token`, `session`, `session_token`, `api_key`, `apikey`,
  `private_key`
- PII categories: `email`, `phone`, `credit_card`, `ssn`

This list is a strict superset of the server-side ingest hard-deny
(`@clue/shared` `INGEST_HARD_DENY_KEYS`), enforced by the
`tests/test_privacy_parity.py` cross-SDK parity test.

### 2. HTTP body capture (Django / FastAPI wrappers)

For framework wrappers that capture HTTP request bodies via
`parameter_snapshot.py`, the same hard-deny applies: `body.email`
cannot be unmasked via `allowed_value_paths` because `email` is in
`DEFAULT_DENIED_KEYS`. Use a non-PII path (e.g. `body.product_name`)
for explicit plaintext capture.

### 3. Default-masked: configure server allowlist for analysis projection

Properties that pass the hard-deny gate are still **masked by default**
on the server side. To make a property appear in Clue's analysis UI,
the property key must be listed on the resolved project service's
`observationAnalysisPropertyAllowlist`. Without this, the property
lands in `raw_only_keys` (visible in raw observation storage only) and not in
`analysisProperties.projected`.

### 4. Identity traits stay privacy-safe

`clue.identify(user_id, traits={...})` accepts profile traits such as
`name`, `email`, `avatarUrl`, `role`, and `plan`. Use a stable opaque
`user_id`; the SDK converts `email` into privacy-safe contact-derived
fields before canonical identity events leave the process.

## Build / test

```sh
python3 -m venv .venv
source .venv/bin/activate
pip install -e '.[test]'
python -m pytest
```

Live-verify (opt-in, requires `apps/api` + `apps/worker` running):

```sh
CLUE_LIVE_VERIFY=1 \
CLUE_LIVE_PROJECT_KEY=pk_dev_xxx \
CLUE_LIVE_API_KEY=cluesk_dev_xxx \
python -m pytest tests/test_canonical_endpoint_live.py -v
```
