Documentation menu

SaaS Pro Max for Python

Python 3.10+ backend events, identity, errors, metrics, remote feature flags and optional OpenTelemetry traces. The core uses only the Python standard library. The package is downloaded from the console; it has not been published to PyPI.

Install

Download saaspro-0.1.0.tar.gz from Settings → Data → Python in your console, then:

python -m pip install ./saaspro-0.1.0.tar.gz
# Add traces when needed; the extra uses the official OpenTelemetry SDK 1.x.
python -m pip install './saaspro-0.1.0.tar.gz[otel]'

Pin the archive in your application's dependency file like any other package; no package index is required.

Backend events and flags

Create a secret ingest key for the correct application/environment in API keys. Supply it through your server's secret manager. A public key or Management API token cannot configure this client. HTTPS is mandatory outside loopback development.

import os
from saaspro import SaaSProMax

spm = SaaSProMax(
    os.environ["SPM_INGEST_KEY"],
    host=os.environ["SPM_HOST"],
    release="checkout@1.2.0",
    env="production",
    on_error=lambda error: print(str(error)),  # Diagnostics contain no payloads or keys.
)

# Pass the same stable user ID as your browser/mobile SDK after authentication.
with spm.scope(distinct_id="user-123"):
    spm.identify("user-123", {"plan": "pro"})
    spm.track("checkout_completed", {"items": 2}, value=25, currency="USD")
    enabled = spm.flags.is_enabled("new_checkout", fallback=False)
    try:
        raise ValueError("Checkout verification failed")
    except Exception as error:
        spm.capture_exception(error, extra={"stage": "confirmation"})

spm.metric("queue.depth", 12, kind="gauge", labels={"queue": "billing"})
spm.flush(timeout=10)
print(spm.stats)  # accepted, rejected, failed, dropped, pending
spm.shutdown(timeout=10)

track/identify/alias/metric/capture_exception return the queued event UUID or None when dropped. Queueing and a completed flush do not prove server acceptance: inspect stats and the scoped console receipts. identify sends an event; it does not mutate a shared identity. scope uses contextvars, restores its caller's identity and isolates threads and async tasks. Alternatively pass distinct_id, anonymous_id and session_id on each event. Never set one process-global user ID. Only join identities your application has authenticated; alias(new_id, previous_id) explicitly joins them. The SDK creates no browser identities, cookies or session IDs.

Flags evaluate remotely with the current secret key and stable identity on every call; there is no cache across users, environments or changed targeting properties. flags.evaluate(...) returns the map; flags.get(key, fallback="control", ...) supports variants. Like the Node/browser SDKs, is_enabled treats boolean true and nonempty variant strings other than "false" as enabled. The server remains the source of deterministic allocation. An identity is required. Failures use the supplied fallback; flags are product behavior, never authorization.

Delivery and lifecycle

Create one client inside each worker process, after Gunicorn/uWSGI fork. An inherited client refuses collection with a diagnostic. Default queue: 1,000 events including in-flight work, batches of at most 100 events/512,000 bytes, flush at 20 events or every 5 seconds. flush_interval=0 disables timed flushes; threshold and explicit flush still work. New events are dropped when full. Captures copy their payload immediately. Invalid payloads and callback exceptions are reported without interrupting application code. Constructor configuration errors raise ValueError.

Network requests time out after 5 seconds. Transient network failures, HTTP 429 and 5xx retry up to 3 times with jitter and bounded Retry-After. Authentication, permission, redirect, malformed-body and oversized-body refusals are not retried. Redirects never forward the secret key. Event IDs are unchanged across retries for server deduplication. Partial ingestion acknowledges accepted events and reports rejected counts without retrying the accepted portion. There is no disk queue and no guarantee during abrupt process termination.

flush(timeout=10) waits for all events queued at the time of the call and returns whether they finished attempting delivery. shutdown(timeout=10) closes collection, drains within the wait budget, cancels backoff and discards remaining queued events. shutdown(flush=False) discards immediately. An already-started standard-library HTTP request cannot be recalled and can run until its socket timeout; data already transmitted cannot be revoked. The worker is a daemon; call shutdown in your framework's graceful worker exit/lifespan hook. No process signal handlers are installed. In async code, call blocking flags/flush/shutdown via asyncio.to_thread; capture calls only queue work.

Privacy

Default payload normalization masks credential-like fields and credential strings, common emails and IP addresses. IP/user-agent context is omitted unless send_default_pii=True. This explicit option also permits intentional email/name traits; ingest still HMACs raw IPs. Technical exception frames include filename, function and line only: no absolute directory, locals, source code, request body, headers or cookies. Request URLs drop credentials, queries and fragments. Keep raw personal data out of free-form messages, path segments, identifiers and custom properties; pattern redaction cannot recognize every secret. Application release/environment/version labels should be technical identifiers.

Use before_send(event) to redact domain-specific content or return None to drop an event. It runs before enqueueing and may not make network calls. Do not add credentials in this callback. Set on_error for safe delivery diagnostics. Application context, top-level exception release and OTel resource release/environment use the client's explicit labels; tenant/environment routing comes exclusively from the key, never a label. Use a separate client for each app/environment.

ASGI / WSGI

from saaspro.frameworks import ASGIMiddleware, WSGIMiddleware

# FastAPI/Starlette: place inside OTel's active request span boundary.
app.add_middleware(ASGIMiddleware, client=spm)
# Generic ASGI: app = ASGIMiddleware(app, spm)
# Flask: app.wsgi_app = WSGIMiddleware(app.wsgi_app, spm)
# Django: application = WSGIMiddleware(application, spm)

These dependency-free boundaries capture exceptions that escape the wrapped app, re-raise the original exception and preserve responses/streaming. They omit raw paths and capture the HTTP method only. Errors handled internally by a framework need an explicit capture_exception call in its error hook. Do not install overlapping capture hooks for the same error. Background task errors and fatal process/native crashes are not automatically captured. Set identity within the authenticated request's own spm.scope(...); middleware clears any outer identity at request entry.

OpenTelemetry

from saaspro.otel import active_trace_context, create_trace_provider

spm = SaaSProMax(
    os.environ["SPM_INGEST_KEY"],
    host=os.environ["SPM_HOST"],
    release="checkout@1.2.0",
    env="production",
    get_trace_context=active_trace_context,
)
provider = create_trace_provider(spm, service_name="checkout-api")
tracer = provider.get_tracer("checkout")
with tracer.start_as_current_span("confirm checkout") as span:
    span.set_attribute("http.request.method", "POST")
    try:
        raise ValueError("Checkout verification failed")
    except Exception as error:
        spm.capture_exception(error)
provider.force_flush(timeout_millis=10000)
provider.shutdown()  # End trace export before closing its transport client.
spm.shutdown()

The helper owns a provider with an official bounded BatchSpanProcessor (1,024 spans, batches of 128). It does not replace your global provider or install framework instrumentation. Supply it to official framework instrumentation, or add BatchSpanProcessor(SaaSProSpanExporter(spm, service_name="checkout-api"), max_queue_size=1024, max_export_batch_size=128) to an existing provider instead; do not create duplicate export pipelines. Existing W3C trace IDs, parents and links remain unchanged. Captures read the active context immediately; capture_exception(error, trace={"traceId": ..., "spanId": ..., "traceFlags": 1}) overrides it, and trace=None suppresses correlation.

The exporter sends standard OTLP HTTP JSON to /api/v1/otlp/v1/traces, not gRPC. It keeps technical HTTP/RPC/database-operation attributes and strips SQL statements, bodies, headers, user identifiers, exception messages/stacks and arbitrary custom attributes before transmission. Set template routes and technical operation names. Only explicit client service/release/environment resource labels are exported. Partial success is acknowledged without retries; rejected counts report through on_error. The current collector does not ingest OTLP logs or metrics. Export requires the Metrics module and enabled trace collection; view under Metrics → Traces, with error links requiring Errors access too.

For an existing official OTLP exporter, keep it and configure the documented endpoint/key in the console; use active_trace_context just for SDK correlation. Configure privacy filters in that pipeline separately. Reference: official Python exporter integration.

Supported boundary

This package handles Python backend collection. The existing React Native/manual adapter and native symbolication support do not provide automatic iOS/Android crash capture. Native crash handlers, fatal-signal collection, on-device persistence and platform release/symbol upload still require a separate native integration.