Unisights Python SDK provides a lightweight event collection endpoint for Python web frameworks.
Project description
Unisights Python
Server package for the unisights ecosystem.
Unisights Python provides a complete, production-ready event collection system for Python web frameworks. It allows your backend to receive analytics events from the Unisights client library and process them using custom handlers.
The package exposes a single POST API endpoint (/events by default) that receives event payloads and forwards them to your handler for processing.
Supported Frameworks
- FastAPI (recommended for new projects)
- Flask (lightweight, microservices)
- Django (full-featured, async support in 4.1+)
- ASGI-compatible frameworks (Starlette, Quart, etc.)
Features
✨ Complete Event System
- 5 event types:
page_view,click,web_vital,custom,error - Full device and UTM tracking
- Scroll depth and time-on-page metrics
🔒 Type-Safe & Validated
- Complete type definitions (matching
@pradeeparul2/unisights-node) - UUID, URL, and schema validation
- Comprehensive error messages
🔐 Automatic Encryption/Decryption
- Auto-detects encrypted payloads
- XOR-based encryption with HMAC-SHA256 authentication
- Seamless decryption - handler receives clean data
- Browser SDK format auto-detection
- Works with both encrypted and unencrypted payloads
⚡ Framework-Agnostic
- Works seamlessly with FastAPI, Flask, Django, or raw ASGI
- Unified configuration across all frameworks
- Single handler interface for all frameworks
🎯 Production-Ready
- Async + sync handler support
- Background queue processing (Flask)
- CORS support built-in
- Comprehensive error handling
- Request size limiting
🔗 Easy Integration
- Connect to any analytics pipeline (Kafka, Redis, databases, etc.)
- Simple async handler function
- Framework-agnostic core logic
Installation
# Core package
pip install unisights
# With FastAPI
pip install unisights[fastapi]
# With Flask
pip install unisights[flask]
# With Django
pip install unisights[django]
# All frameworks
pip install unisights[all]
Event Payload Structure
The endpoint receives a comprehensive JSON payload with complete user session and event data:
{
"encrypted": false,
"data": {
"asset_id": "prop_123",
"session_id": "550e8400-e29b-41d4-a716-446655440000",
"page_url": "https://example.com/products",
"entry_page": "https://example.com",
"exit_page": null,
"utm_params": {
"utm_source": "google",
"utm_medium": "cpc",
"utm_campaign": "summer_sale"
},
"device_info": {
"browser": "Chrome",
"os": "Windows",
"device_type": "Desktop"
},
"scroll_depth": 75,
"time_on_page": 120,
"events": [
{
"type": "page_view",
"data": {
"location": "https://example.com/products",
"title": "Products",
"timestamp": 1234567890
}
},
{
"type": "click",
"data": {
"x": 100,
"y": 200,
"timestamp": 1234567891
}
},
{
"type": "web_vital",
"data": {
"name": "LCP",
"value": 2.5,
"rating": "good",
"delta": 0.1,
"id": "abc123",
"entries": 1,
"navigation_type": "navigation",
"timestamp": 1234567892
}
}
]
}
}
Event Types
- page_view - User loaded a page
- click - User clicked an element
- web_vital - Performance metric (FCP, LCP, CLS, INP, TTFB, FID)
- custom - Custom event with arbitrary data
- error - JavaScript error with stack trace
Encryption & Decryption
The Unisights Python package includes automatic encryption and decryption support for secure event transmission.
Auto-Decryption
The package automatically detects and decrypts encrypted payloads:
async def handle_event(payload, request):
# If payload was encrypted, it's automatically decrypted
# You work with fully decrypted data - no additional setup needed
session_id = payload.data.session_id
events = payload.data.events
# Same handler for encrypted and unencrypted payloads
await process_events(payload.data)
How it works:
- ✅ Client encrypts payload using XOR keystream + HMAC-SHA256
- ✅ Package receives encrypted payload
- ✅ Auto-detects
encrypted: trueflag - ✅ Automatically decrypts using site_id, bucket, and ua_hash
- ✅ Returns fully decrypted, validated data to handler
Encrypted Payload Format
Encrypted payloads come in two formats (both automatically supported):
Standard Envelope Format:
{
"encrypted": true,
"envelope": {
"site_id": "unisights-html-test-site",
"ua_hash": "abc123def456",
"bucket": 59119024,
"tag": "8zweOhmtEzKl2iXbCGcnFd/MJmiP8qbvjjn8OQy2JTg=",
"ciphertext": "B3hpX9b+iG5p7I7P8jd4sO2aI20..."
}
}
Browser SDK Format (Auto-detected):
{
"encrypted": true,
"data": "B3hpX9b+iG5p7I7P8jd4sO2aI20...",
"tag": "8zweOhmtEzKl2iXbCGcnFd/MJmiP8qbvjjn8OQy2JTg=",
"bucket": 59119024,
"site_id": "unisights-html-test-site",
"ua_hash": ""
}
Both formats are automatically detected, converted to standard format, and decrypted.
Unencrypted Payload Format
For unencrypted transmission:
{
"encrypted": false,
"data": {
"asset_id": "prop_123",
"session_id": "550e8400-e29b-41d4-a716-446655440000"
// ... rest of payload
}
}
Encryption Algorithm Details
Cipher: XOR-based stream cipher with HMAC-SHA256 authentication
Key Derivation:
client_key = SHA256(site_id : bucket : ua_hash)
Authentication Tag:
tag = HMAC-SHA256(client_key, ciphertext)
Keystream Generation:
keystream = SHA256(client_key || 0) || SHA256(client_key || 1) || ...
The package handles all cryptographic operations automatically - your handler receives clean, decrypted data regardless of transmission encryption.
Configuration
Encryption is handled automatically - no configuration needed:
from unisights import UnisightsOptions
options = UnisightsOptions(
handler=handle_event,
validate_schema=True # Still validates encrypted payloads
)
# Automatically works with both encrypted and unencrypted payloads
Note: Encryption/decryption is automatic and requires no configuration. The package handles all encryption formats (standard envelope and browser SDK) transparently.
Error Handling
The package handles encryption errors gracefully:
| Error | Cause | Handling |
|---|---|---|
TagMismatchError |
Ciphertext tampered or wrong key | Returns 422 + validation error |
DecryptionError |
Missing encryption fields | Returns 422 + validation error |
Empty ua_hash |
Browser didn't send user agent hash | ✅ Handled gracefully (defaults to empty string) |
Missing envelope |
Browser SDK sends unencrypted data with encrypted flag | ✅ Auto-detected and treated as unencrypted |
| Invalid base64 | Tag or ciphertext not properly encoded | Returns 422 + clear error message |
Security Considerations
- ✅ Payload authentication via HMAC-SHA256 prevents tampering
- ✅ XOR keystream regenerated per message (time-bucketed)
- ✅ Client key derived from public inputs (site_id, bucket, ua_hash)
- ✅ No server-side secrets required - keys derived from client context
- ✅ Suitable for client-to-server encryption with public key derivation
- ⚠️ For end-to-end encryption with shared secrets, implement additional layers
Testing Encrypted Payloads
Test with encrypted payload from browser:
curl -X POST http://localhost:8000/api/events \
-H "Content-Type: application/json" \
-d '{
"encrypted": true,
"data": "B3hpX9b+iG5p7I7P8jd4sO2aI20dVkAXP2ylux2Vv86h...",
"tag": "8zweOhmtEzKl2iXbCGcnFd/MJmiP8qbvjjn8OQy2JTg=",
"bucket": 59119024,
"site_id": "unisights-html-test-site",
"ua_hash": ""
}'
Expected: ✅ 200 OK with decrypted data processed by handler
Browser SDK Compatibility
The package is fully compatible with the browser SDK's encryption implementation:
- ✅ Detects browser SDK encrypted format automatically
- ✅ Handles missing
ua_hash(empty string) - ✅ Handles missing
envelopewrapper - ✅ Extracts encryption fields from root level or envelope
- ✅ Validates HMAC tag before decryption
- ✅ Returns clean, validated data to handler
- ✅ Works seamlessly with all frameworks (FastAPI, Flask, Django)
FastAPI Integration
from fastapi import FastAPI
from unisights import UnisightsOptions
from unisights.fastapi import unisights_fastapi
app = FastAPI()
async def handle_event(payload, request):
"""Process analytics event (automatically decrypted if encrypted)"""
session_id = payload.data.session_id
events = payload.data.events
# Save to database, send to analytics, etc.
print(f"Session {session_id}: {len(events)} events")
# Configure and include router
options = UnisightsOptions(
path="/api/events",
handler=handle_event,
validate_schema=True,
max_payload_size=5*1024*1024 # 5MB
)
app.include_router(unisights_fastapi(options))
Start server:
pip install fastapi uvicorn
uvicorn main:app --reload
Endpoint: POST /api/events
Flask Integration
from flask import Flask
from unisights import UnisightsOptions
from unisights.flask import unisights_flask
app = Flask(__name__)
async def handle_event(payload, request):
"""Process analytics event (automatically decrypted if encrypted)"""
session_id = payload.data.session_id
events = payload.data.events
# Save to database, send to analytics, etc.
print(f"Session {session_id}: {len(events)} events")
# Configure and register blueprint
options = UnisightsOptions(
path="/api/events",
handler=handle_event,
validate_schema=True
)
app.register_blueprint(unisights_flask(options))
app.run()
Endpoint: POST /api/events
Note: Flask includes background queue support for async handlers. Events are queued and processed in a background worker thread, allowing immediate response to the client.
Django Integration
Add the route to your Django URL configuration:
from django.urls import path
from unisights import UnisightsOptions
from unisights.django import unisights_django
async def handle_event(payload, request):
"""Process analytics event (automatically decrypted if encrypted)"""
session_id = payload.data.session_id
events = payload.data.events
# Save to database, send to analytics, etc.
print(f"Session {session_id}: {len(events)} events")
# Sync view (default)
options = UnisightsOptions(handler=handle_event)
urlpatterns = [
path("api/events/", unisights_django(options))
]
# OR async view (Django 4.1+)
# from unisights.django import unisights_django_async
# urlpatterns = [
# path("api/events/", unisights_django_async(options))
# ]
Endpoint: POST /api/events/
CORS Configuration (required):
# settings.py
INSTALLED_APPS = [
'corsheaders',
# ...
]
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
# ... other middleware
]
CORS_ALLOWED_ORIGINS = [
"https://yourdomain.com",
]
Configuration
All frameworks use the same UnisightsOptions configuration:
from unisights import UnisightsOptions
options = UnisightsOptions(
# Routing
path="/api/events", # Default: "/events"
# Handler
handler=my_handler, # Optional: async function
# Validation
validate_schema=True, # Validate event structure
validate_required_fields=True, # Require all fields
# Security
max_payload_size=5*1024*1024, # Max 5MB
allow_origins=["*"], # CORS origins
# Debugging
debug=False # Verbose logging
)
Handler Functions
Your handler receives a fully-validated, type-safe payload (automatically decrypted if needed):
async def handle_event(payload, request):
"""
Args:
payload: UnisightsPayload (fully validated and decrypted)
request: Framework request object
"""
# Access session data
session_id = payload.data.session_id
page_url = payload.data.page_url
device = payload.data.device_info.browser
# Process events
for event in payload.data.events:
if event.type == "error":
# Handle JavaScript errors
await send_to_error_tracking(event)
elif event.type == "web_vital":
# Track performance metrics
await send_to_analytics(event)
elif event.type == "click":
# Log user interactions
await log_click(event)
# Save entire session to database
# Data is already decrypted, fully validated
await db.sessions.insert_one(payload.data.to_dict())
Using with Analytics Pipelines
Kafka Example
from aiokafka import AIOKafkaProducer
async def handle_event(payload, request):
producer = AIOKafkaProducer(bootstrap_servers='localhost:9092')
await producer.start()
try:
# Data is already decrypted
await producer.send_and_wait(
"analytics-events",
json.dumps(payload.data.to_dict()).encode()
)
finally:
await producer.stop()
Database Example
async def handle_event(payload, request):
# MongoDB - data already decrypted
await db.sessions.insert_one(payload.data.to_dict())
# PostgreSQL - data already decrypted
await db.execute(
"INSERT INTO sessions VALUES ($1, $2, $3, ...)",
payload.data.session_id,
payload.data.asset_id,
payload.data.page_url
)
Redis Queue Example
async def handle_event(payload, request):
# Data already decrypted before reaching handler
await redis.lpush(
"analytics-queue",
json.dumps(payload.data.to_dict())
)
Testing
Quick Test with cURL
Unencrypted Payload:
curl -X POST http://localhost:8000/api/events \
-H "Content-Type: application/json" \
-d '{
"encrypted": false,
"data": {
"asset_id": "prop_123",
"session_id": "550e8400-e29b-41d4-a716-446655440000",
"page_url": "https://example.com",
"entry_page": "https://example.com",
"utm_params": {},
"device_info": {"browser": "Chrome", "os": "Linux", "device_type": "Desktop"},
"scroll_depth": 50,
"time_on_page": 60,
"events": []
}
}'
Encrypted Payload (Browser SDK Format):
curl -X POST http://localhost:8000/api/events \
-H "Content-Type: application/json" \
-d '{
"encrypted": true,
"data": "B3hpX9b+iG5p7I7P8jd4sO2aI20...",
"tag": "8zweOhmtEzKl2iXbCGcnFd/MJmiP8qbvjjn8OQy2JTg=",
"bucket": 59119024,
"site_id": "unisights-html-test-site",
"ua_hash": ""
}'
Both should return 200 OK with decrypted data processed by handler.
Unit Testing
from unisights.validator import UnisightsValidator
from uuid import uuid4
import pytest
def test_event_validation():
validator = UnisightsValidator()
payload = validator.validate({
"encrypted": False,
"data": {
"asset_id": "prop_123",
"session_id": str(uuid4()),
"page_url": "https://example.com",
"entry_page": "https://example.com",
"events": []
}
})
assert payload.data.asset_id == "prop_123"
def test_encrypted_payload_decryption():
# Encrypted payloads are automatically decrypted during validation
validator = UnisightsValidator()
# In real scenario, this would be an actual encrypted payload
# The validator will auto-decrypt and return clean data
Project Structure
unisights/
├── __init__.py
├── types.py # Type definitions
├── validator.py # Validation + auto-decryption
├── encryption.py # Encryption/decryption engine
├── collector.py # Core event processor
├── config.py # Configuration
├── fastapi.py # FastAPI adapter
├── flask.py # Flask adapter
├── django.py # Django adapter
└── asgi.py # Generic ASGI middleware
HTTP Status Codes
| Code | Meaning | Cause |
|---|---|---|
| 200 | OK | Event processed successfully |
| 202 | Accepted | Event queued for processing (Flask background) |
| 400 | Bad Request | Invalid JSON syntax |
| 413 | Too Large | Payload exceeds size limit |
| 415 | Unsupported | Wrong Content-Type |
| 422 | Unprocessable | Validation or decryption failed |
| 500 | Server Error | Handler error (debug=True shows details) |
Performance
Benchmark results (100-event payloads):
- FastAPI (async): 5,000-10,000 events/sec
- FastAPI (encrypted): 3,000-5,000 events/sec (includes decryption)
- Flask (background queue): 1,000-2,000 events/sec
- Django (sync): 1,000-2,000 events/sec
- Django (async): 3,000-5,000 events/sec
Deployment
Docker
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
# FastAPI
CMD ["uvicorn", "app:app", "--host", "0.0.0.0"]
Production Commands
FastAPI:
gunicorn app:app -w 4 -k uvicorn.workers.UvicornWorker
Flask:
gunicorn app:app -w 4
Django:
gunicorn django_project.wsgi -w 4
Development
Clone repository:
git clone https://github.com/pradeeparul2/unisights
cd unisights/packages/python
Install in development mode:
pip install -e ".[dev]"
Run tests:
pytest tests/ -v
Build package:
python -m build
Publish to PyPI:
pip install twine
twine upload dist/*
Examples
Complete working examples are provided for all frameworks:
examples/fastapi_example.py- FastAPI implementationexamples/flask_example.py- Flask implementationexamples/django_example.py- Django implementation
Troubleshooting
CORS Errors
Set allow_origins to your domain:
UnisightsOptions(allow_origins=["https://yourdomain.com"])
413 Payload Too Large
Increase max_payload_size:
UnisightsOptions(max_payload_size=10*1024*1024) # 10MB
422 Validation Failed
Check payload structure matches specification. See Event Payload section.
For encrypted payloads, ensure:
encrypted: trueflag is set- Encryption fields are present (either in
envelopeor at root level) - HMAC tag is valid
422 Decryption Failed
"TagMismatchError" - Payload was tampered or using wrong encryption key "DecryptionError" - Missing encryption fields (site_id, bucket, tag, data/ciphertext)
Check:
- site_id matches between client and server
- bucket is correctly calculated on client
- Tag and ciphertext are properly base64-encoded
Async Handler Blocks
For Flask, use the built-in background queue. For Django, use async views (Django 4.1+):
from unisights.django import unisights_django_async
path("api/events/", unisights_django_async(options))
License
MIT License
Links
- GitHub: https://github.com/pradeeparul2/unisights-python
- PyPI: https://pypi.org/project/unisights/
- Node.js SDK: https://github.com/pradeeparul2/unisights-node
Support
- Report issues on GitHub
- Check examples for common patterns
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file unisights-1.0.0.tar.gz.
File metadata
- Download URL: unisights-1.0.0.tar.gz
- Upload date:
- Size: 34.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.28 {"installer":{"name":"uv","version":"0.9.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9ad5810195de6d68292bffe8bfb534bef9bf6f497f4f7cf0c52eac1b46e1fd77
|
|
| MD5 |
39873452bc6531d642d1aa3197505188
|
|
| BLAKE2b-256 |
9c4e9cb065eddbb0999b8de647b47f85d3ac6ec06ced8eae19ddee220e8a9b5d
|
File details
Details for the file unisights-1.0.0-py3-none-any.whl.
File metadata
- Download URL: unisights-1.0.0-py3-none-any.whl
- Upload date:
- Size: 29.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.28 {"installer":{"name":"uv","version":"0.9.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ded011ffc39fa535b8de5cfd3fa005becb3cee3e116901df1b1ae97b6a5e45da
|
|
| MD5 |
268ea3b436189d721e9e9045fc00d3f9
|
|
| BLAKE2b-256 |
099bbe3996f78c38931149cc2aa59a8fe86d7bb49eadbc70850a344c0d4fc147
|