Metadata-Version: 2.4
Name: cluebase-backend-sdk
Version: 0.0.1
Summary: Cluebase 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.41.1
Requires-Dist: opentelemetry-sdk<2.0,>=1.41.1
Requires-Dist: rfc8785<0.2,>=0.1.4
Provides-Extra: mutation
Requires-Dist: mutmut<4.0,>=3.2; extra == "mutation"
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: pytest-cov<7.0,>=5.0; extra == "test"
Requires-Dist: pyright<1.2,>=1.1.411; extra == "test"
Requires-Dist: build<2.0,>=1.2; extra == "test"
Requires-Dist: packaging<26,>=25; extra == "test"
Requires-Dist: twine<7.0,>=6.1; 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"
Requires-Dist: crewai<1.16,>=1.15.1; extra == "test"
Requires-Dist: openai==2.45.0; extra == "test"
Requires-Dist: anthropic==0.116.0; extra == "test"
Requires-Dist: opentelemetry-instrumentation-openai==0.62.1; extra == "test"
Requires-Dist: opentelemetry-instrumentation-anthropic==0.62.1; extra == "test"
Requires-Dist: aiosqlite==0.21.0; extra == "test"
Requires-Dist: setuptools<83,>=68; extra == "test"
Dynamic: requires-python

# cluebase-backend-sdk

Shared Python backend core for Cluebase SDK wrappers (Django, FastAPI,
Celery). Events flow through an OTel `TracerProvider` + `ConsentSpanProcessor` +
`ObservationSourceEventExporter` and the shared delivery queue to
`/api/v1/ingest/backend`.

## Minimal integration

The SDK exposes a single ``cluebase`` 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 cluebase_backend_sdk import cluebase

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

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

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

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

``cluebase.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. ``cluebase.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 Cluebase
transport. Framework integrations create spans or annotate the current span,
the exporter serializes those spans as ``sdk_source_signal_observed`` events,
and the Cluebase backend ingest core performs classification, normalization,
privacy projection, and raw-ingest shaping.

```text
Python framework / library hook
  -> OTel span or Cluebase 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
(`@cluebase/shared` `INGEST_HARD_DENY_KEYS`), enforced by the
`tests/test_privacy_parity.py` cross-SDK parity test.

### 2. HTTP request values

This SDK does not read HTTP request or response bodies. The value surface it can
observe is the request query, and its values are masked by default. A value is
sent in plaintext only when the customer names that field on the Cluebase screen; the
decision is delivered to the SDK and re-checked by ingest. Hard-denied keys such
as `email` can never be released, whatever the screen says. See
`docs/arch/sdk/backend-sdk.md` for the boundary and for what would be required to
bring bodies into scope.

### 3. Analysis projection and later registration

Properties that pass the hard-deny gate are projected by default unless a
cardinality, free-text, or complex-value guard keeps them out of the analysis
index. Guarded non-private values remain available in
`analysisProperties.raw_only` so they can be registered and promoted later.
The map stores each original value as JSON so its type can be restored.
Hard-denied values are dropped and never stored in `raw_only`. After a privacy
review, use the project service allowlist to promote a guarded non-private
property into the analysis projection.

### 4. Identity traits stay privacy-safe

`cluebase.identify(user_id, traits={...})` accepts profile traits such as
`name`, `email`, and `avatarUrl`. Other customer-defined values remain generic
subject traits instead of becoming fixed Cluebase profile fields. 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
```
