Lightweight Python SDK for Business-Use event tracking and assertions
This project has been archived.
The maintainers of this project have marked this project as archived. No new releases are expected.
Project description
Business-Use Python SDK
A lightweight, production-ready Python SDK for tracking business events and assertions in your applications.
Features
- Simple API: Main function
ensure()with convenience helpersact()andassert_() - Non-blocking: Events are batched and sent asynchronously in the background
- Never fails: All errors are handled internally - your application never crashes
- Thread-safe: Safe to use from multiple threads concurrently
- Minimal dependencies: Only
httpxandpydantic - Context-aware: Filters and validators can access upstream dependency data
Installation
pip install business-use
Or with uv:
uv add business-use
Quick Start
from business_use import initialize, ensure
# Initialize with API key
initialize(api_key="your-api-key")
# Track a business action (no validator)
ensure(
id="user_signup",
flow="onboarding",
run_id="user_12345",
data={"email": "user@example.com", "plan": "premium"}
)
# Track a business assertion (with validator)
def validate_payment(data, ctx):
"""Validate payment and check upstream dependencies."""
# ctx["deps"] contains upstream dependency events
# Each dep: {"flow": str, "id": str, "data": dict}
has_cart = any(dep["id"] == "cart_created" for dep in ctx["deps"])
return data["amount"] > 0 and has_cart
ensure(
id="payment_valid",
flow="checkout",
run_id="order_67890",
data={"amount": 99.99, "currency": "USD"},
dep_ids=["cart_created"],
validator=validate_payment # Creates "assert" node
)
# Convenience wrappers (optional)
from business_use import act, assert_
# act() is ensure() without validator
act(id="user_signup", flow="onboarding", run_id="u123", data={...})
# assert_() is ensure() with validator
assert_(id="validation", flow="checkout", run_id="o123", data={...}, validator=fn)
API Reference
initialize()
Initialize the SDK before using ensure(). This validates the connection to the backend and starts the background batch processor.
Parameters:
api_key(str, optional): API key for authentication (defaults toBUSINESS_USE_API_KEYenv var)url(str, optional): Backend API URL (defaults toBUSINESS_USE_URLenv var orhttp://localhost:13370)batch_size(int, optional): Number of events per batch (default:100)batch_interval(int, optional): Flush interval in seconds (default:5)max_queue_size(int, optional): Max queue size (default:batch_size * 10)
Examples:
# With explicit parameters
initialize(
api_key="your-api-key",
url="https://api.example.com",
batch_size=50,
batch_interval=10
)
# With environment variables
# Set BUSINESS_USE_API_KEY and BUSINESS_USE_URL in your environment
initialize()
# Mix of both (parameters override env vars)
initialize(api_key="your-api-key") # URL from BUSINESS_USE_URL or default
ensure()
Main function to track business events. Type is auto-determined by the presence of a validator.
Parameters:
id(str, required): Unique node/event identifierflow(str, required): Flow identifierrun_id(str | callable, required): Run identifier (or lambda returning string)data(dict, required): Event data payloadfilter(callable, optional): Filter function(data, ctx) -> boolevaluated on backenddep_ids(list[str] | callable, optional): Dependency node IDsvalidator(callable, optional): Validation function(data, ctx) -> boolexecuted on backenddescription(str, optional): Human-readable descriptionconditions(list[NodeCondition] | callable, optional): Optional conditions (e.g., timeout constraints)additional_meta(dict, optional): Optional additional metadata
Type determination:
- If
validatoris provided → creates an "assert" node - If
validatorisNone→ creates an "act" node
Example:
# Action node (no validator)
ensure(
id="payment_processed",
flow="checkout",
run_id="run_12345",
data={"amount": 100, "currency": "USD"},
dep_ids=["cart_created"],
description="Payment processed successfully"
)
# Assertion node (with validator)
def validate_total(data, ctx):
items_total = sum(dep["data"]["price"] for dep in ctx["deps"])
return data["total"] == items_total
ensure(
id="order_total_valid",
flow="checkout",
run_id="run_12345",
data={"total": 150},
dep_ids=["item_added"],
validator=validate_total,
description="Order total validation"
)
act()
Convenience wrapper around ensure() without a validator. Creates an "act" node.
Parameters:
id(str, required): Unique node/event identifierflow(str, required): Flow identifierrun_id(str | callable, required): Run identifier (or lambda returning string)data(dict, required): Event data payloadfilter(callable, optional): Filter function(data, ctx) -> boolevaluated on backenddep_ids(list[str] | callable, optional): Dependency node IDsdescription(str, optional): Human-readable description
Filter Function Signature:
def my_filter(data: dict, ctx: dict) -> bool:
"""Filter function executed on the backend.
Args:
data: Current event data
ctx: Context with upstream dependencies:
ctx["deps"] = [{"flow": str, "id": str, "data": dict}, ...]
Returns:
bool: True to include event, False to filter it out
"""
pass
Example:
# Simple usage
act(
id="payment_processed",
flow="checkout",
run_id="run_12345",
data={"amount": 100, "currency": "USD"}
)
# With dependencies
act(
id="order_completed",
flow="checkout",
run_id="run_12345",
data={"order_id": "ord_123", "total": 150},
dep_ids=["payment_processed", "inventory_reserved"],
description="Order completed successfully"
)
# Using lambdas for dynamic values
act(
id="api_request",
flow="integration",
run_id=lambda: get_current_trace_id(),
data={"endpoint": "/users", "method": "POST"},
dep_ids=lambda: get_upstream_dependencies()
)
# With filter based on upstream dependencies
def check_prerequisites(data, ctx):
"""Only process if all upstream tasks are approved."""
return all(dep["data"].get("status") == "approved" for dep in ctx["deps"])
act(
id="order_completed",
flow="checkout",
run_id="run_12345",
data={"order_id": "ord_123"},
dep_ids=["payment_processed", "inventory_reserved"],
filter=check_prerequisites, # Evaluated on backend with ctx
description="Order completed after all prerequisites"
)
assert_()
Convenience wrapper around ensure() with a validator. Creates an "assert" node.
Named assert_ (with underscore) to avoid conflict with Python's built-in assert keyword.
Parameters:
id(str, required): Unique node/event identifierflow(str, required): Flow identifierrun_id(str | callable, required): Run identifier (or lambda returning string)data(dict, required): Event data payloadfilter(callable, optional): Filter function(data, ctx) -> boolevaluated on backenddep_ids(list[str] | callable, optional): Dependency node IDsvalidator(callable, optional): Validation function(data, ctx) -> boolexecuted on backenddescription(str, optional): Human-readable description
Validator Function Signature:
def my_validator(data: dict, ctx: dict) -> bool:
"""Validator function executed on the backend.
Args:
data: Current event data
ctx: Context with upstream dependencies:
ctx["deps"] = [{"flow": str, "id": str, "data": dict}, ...]
Returns:
bool: True if validation passes, False if it fails
"""
pass
Example:
# Simple assertion
assert_(
id="order_total_positive",
flow="checkout",
run_id="run_12345",
data={"total": 150}
)
# With validator accessing upstream dependencies
def validate_order(data, ctx):
"""Validate order total matches sum of items from upstream events.
Args:
data: Current order data
ctx: Context with upstream item events in ctx["deps"]
"""
# Calculate total from upstream item_added events
items_total = sum(
dep["data"]["price"]
for dep in ctx["deps"]
if dep["id"] == "item_added"
)
# Verify order total matches
return data["total"] == items_total
assert_(
id="order_total_matches",
flow="checkout",
run_id="run_12345",
data={"total": 150},
dep_ids=["item_added"], # Multiple item_added events
validator=validate_order,
description="Order total matches sum of item prices"
)
# Validator with complex logic
def validate_payment_and_inventory(data, ctx):
"""Validate both payment and inventory are ready."""
payment_approved = any(
dep["id"] == "payment_processed" and dep["data"]["status"] == "approved"
for dep in ctx["deps"]
)
inventory_reserved = any(
dep["id"] == "inventory_reserved" and dep["data"]["reserved"] == True
for dep in ctx["deps"]
)
return payment_approved and inventory_reserved and data["ready_to_ship"]
assert_(
id="order_ready",
flow="checkout",
run_id="run_12345",
data={"ready_to_ship": True},
dep_ids=["payment_processed", "inventory_reserved"],
validator=validate_payment_and_inventory
)
shutdown()
Gracefully shutdown the SDK and flush remaining events (optional - auto-shuts down on exit).
Parameters:
timeout(float, optional): Max time to wait for shutdown in seconds (default:5.0)
Example:
from business_use import shutdown
# At application shutdown
shutdown(timeout=10.0)
How It Works
Batching & Ingestion
The SDK uses a background worker thread to batch and send events:
- Events are added to an in-memory queue (thread-safe)
- Background thread collects events into batches
- Batches are sent when:
- Batch size reaches
batch_size(default: 100), OR batch_intervalseconds elapse (default: 5)
- Batch size reaches
- On program exit, remaining events are flushed (best-effort)
Error Handling
The SDK never raises exceptions. All errors are caught and logged internally:
- Network errors: Logged, batch dropped
- Queue overflow: Oldest events dropped, logged
- Invalid parameters: Logged, no-op
- Not initialized: Silent no-op
Lambda Serialization
Callable parameters (run_id, dep_ids, filter, validator) are serialized as Python source code and executed on the backend.
Important Notes:
- Filters are evaluated on the backend with access to upstream dependencies via
ctx - Validators are executed on the backend with the same
ctxstructure - Both filter and validator receive:
(data: dict, ctx: dict) -> bool ctx["deps"]contains all upstream dependency events as a list of{"flow": str, "id": str, "data": dict}- Only use simple lambdas or functions defined in the same file
- External references may fail serialization
Requirements
- Python >= 3.11
httpx >= 0.27.0pydantic >= 2.0.0
Architecture
See SDK_ARCHITECTURE.md for detailed architecture documentation.
Development & Local Setup
Prerequisites
- Python >= 3.11
- uv (recommended) or pip
Setup
-
Clone the repository:
git clone https://github.com/desplega-ai/business-use.git cd business-use/sdk-py
-
Install dependencies:
# With uv (recommended) uv sync # Or with pip pip install -e ".[dev]"
-
Run the backend locally:
The SDK requires the Business-Use backend API running locally:
# In a separate terminal, navigate to the core directory cd ../core # Install core dependencies uv sync # Run the backend uv run cli serve --reload
The API will be available at
http://localhost:13370
Running Tests
# Run all tests
uv run pytest
# Run with verbose output
uv run pytest -v
# Run specific test file
uv run pytest tests/test_serialization.py
# Run with coverage
uv run pytest --cov=business_use --cov-report=html
Running the Example
Once the backend is running:
uv run python example.py
You should see:
- SDK initialization logs
- Events being tracked
- Batches being sent every 5 seconds
- Graceful shutdown
Project Structure
sdk-py/
├── src/business_use/
│ ├── __init__.py # Public API exports
│ ├── client.py # Main SDK (initialize, act, assert_)
│ ├── batch.py # Background batching worker
│ └── models.py # Pydantic models
├── tests/
│ ├── test_client.py # Client behavior tests
│ └── test_serialization.py # Lambda serialization tests
├── example.py # Usage example
├── pyproject.toml # Project configuration
└── README.md # This file
Development Workflow
-
Make changes to the SDK code in
src/business_use/ -
Run tests to ensure nothing breaks:
uv run pytest
-
Test manually with the example:
# Terminal 1: Run backend cd ../core && uv run uvicorn src.api.api:app --port 13370 # Terminal 2: Run example uv run python example.py
-
Check code quality (optional):
# Format code uv run ruff format src/ tests/ # Lint code uv run ruff check src/ tests/ # Type check uv run mypy src/
Common Development Tasks
Add a new dependency
# Add runtime dependency
uv add package-name
# Add dev dependency
uv add --dev package-name
Debug SDK behavior
Enable debug logging in your code:
import logging
# Set SDK logger to debug level
logging.getLogger("business-use").setLevel(logging.DEBUG)
# Or configure all logging
logging.basicConfig(
level=logging.DEBUG,
format="[%(name)s] [%(levelname)s] %(message)s"
)
Test with different batch settings
Modify example.py to test different configurations:
initialize(
api_key="your-api-key",
batch_size=5, # Small batch for testing
batch_interval=1, # Flush every second
)
Simulate backend failures
Stop the backend and observe SDK behavior:
- Connection check should fail gracefully
- SDK should enter no-op mode
- No exceptions should be raised
Environment Variables
The SDK supports configuration via environment variables. This is the recommended approach for production deployments:
# Required: Set API key
export BUSINESS_USE_API_KEY="your-api-key"
# Optional: Set custom backend URL (defaults to http://localhost:13370)
export BUSINESS_USE_URL="https://api.desplega.ai"
Then simply initialize without parameters:
from business_use import initialize
# Automatically uses BUSINESS_USE_API_KEY and BUSINESS_USE_URL from environment
initialize()
Benefits:
- ✅ Keep secrets out of code
- ✅ Easy configuration per environment (dev/staging/prod)
- ✅ Works with Docker, Kubernetes, etc.
- ✅ Parameters still override env vars when needed
Troubleshooting
Issue: SDK not sending events
-
Check backend is running:
curl http://localhost:13370/health # Should return: {"status":"success","message":"API is healthy"}
-
Enable debug logging to see batch processing:
logging.getLogger("business-use").setLevel(logging.DEBUG)
-
Check API key is correct (default: check backend config)
Issue: Tests failing
-
Ensure dependencies are up-to-date:
uv sync -
Run tests with verbose output:
uv run pytest -v -s
-
Check if backend is interfering (tests should work without backend)
Issue: Import errors
Make sure you've installed the package in editable mode:
uv sync # Reinstalls in editable mode
Backend API Endpoints
When running locally, the backend exposes:
GET /health- Health check (no auth required)GET /v1/status- Verify API keyPOST /v1/events-batch- Batch event ingestionGET /v1/events- Query eventsGET /v1/nodes- Query nodes
Contributing
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Make your changes
- Run tests (
uv run pytest) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Best Practices
- Always run tests before committing
- Add tests for new features
- Keep dependencies minimal - avoid adding unnecessary packages
- Use type hints for better IDE support and maintainability
- Log errors instead of raising exceptions in SDK code
- Never block the main thread - all network I/O happens in background
License
MIT
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 business_use-0.3.3.tar.gz.
File metadata
- Download URL: business_use-0.3.3.tar.gz
- Upload date:
- Size: 22.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4e1a67f2d5257cd3d9cde25d093378606285ff30506d55c2df487458b59c2f29
|
|
| MD5 |
74206f72b5860ddfed739a625135f3d2
|
|
| BLAKE2b-256 |
3d96d4e8739ee55b79d1d0ce7daf8f3a0486d30cea710ea8f358893d6bf518b8
|
Provenance
The following attestation bundles were made for business_use-0.3.3.tar.gz:
Publisher:
release-sdk-py.yaml on desplega-ai/business-use
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
business_use-0.3.3.tar.gz -
Subject digest:
4e1a67f2d5257cd3d9cde25d093378606285ff30506d55c2df487458b59c2f29 - Sigstore transparency entry: 630811912
- Sigstore integration time:
-
Permalink:
desplega-ai/business-use@89351734c482bb50fa2e9310b7117fee947532e7 -
Branch / Tag:
refs/tags/sdk-py-v0.3.3 - Owner: https://github.com/desplega-ai
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release-sdk-py.yaml@89351734c482bb50fa2e9310b7117fee947532e7 -
Trigger Event:
push
-
Statement type:
File details
Details for the file business_use-0.3.3-py3-none-any.whl.
File metadata
- Download URL: business_use-0.3.3-py3-none-any.whl
- Upload date:
- Size: 16.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1d5c102a43933dd13c91df313bdefcdbba2a597050ae4f097517e8b6c7f896e0
|
|
| MD5 |
09d7b604ac7d2d994e47b89eecc74f7b
|
|
| BLAKE2b-256 |
d7801952011d5a882afdb2000ad658c01471cbe392454d1cdf1ac6d357e7dec4
|
Provenance
The following attestation bundles were made for business_use-0.3.3-py3-none-any.whl:
Publisher:
release-sdk-py.yaml on desplega-ai/business-use
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
business_use-0.3.3-py3-none-any.whl -
Subject digest:
1d5c102a43933dd13c91df313bdefcdbba2a597050ae4f097517e8b6c7f896e0 - Sigstore transparency entry: 630811942
- Sigstore integration time:
-
Permalink:
desplega-ai/business-use@89351734c482bb50fa2e9310b7117fee947532e7 -
Branch / Tag:
refs/tags/sdk-py-v0.3.3 - Owner: https://github.com/desplega-ai
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release-sdk-py.yaml@89351734c482bb50fa2e9310b7117fee947532e7 -
Trigger Event:
push
-
Statement type: