Copy page
Python SDK
Report Python exceptions and messages to Bicho with a dependency-free SDK. Install from Git, configure backend credentials, and handle synchronous delivery.
The bicho-fyi distribution, imported as bicho, reports Python exceptions and error messages over OTLP/HTTP JSON. It requires Python 3.11+ and has no runtime dependencies. Capture is explicit and delivery is synchronous.
Install from Git
Inside a Python 3.11+ virtual environment, install the pinned source revision with Git available:
python -m pip install "bicho-fyi @ git+https://github.com/bicho-fyi/bicho-python.git@0fa9c086a91c22a73ef2b81a5d6fdd1ae3dac460"
This installs the SDK, not the Bicho server. Deploy Bicho and register a project separately. The runnable Python example uses the same pinned source revision.
Configure a client
Use otlp.endpoint and otlp.headers['X-Bicho-Key'] from project registration:
from bicho import Client, Config, DeliveryError
tracker = Client(Config(
endpoint='https://your-bicho-worker.example/v1/logs',
key='YOUR_PUBLIC_KEY',
service_name='my-app/python',
environment='production',
release='my-app@1.0.0',
))
The endpoint must end at the exact /v1/logs path. HTTPS is required except on loopback for local testing. URL credentials, query strings, fragments, alternative normalized paths, and redirects are rejected. The transport does not use proxy environment variables.
For a secret-enabled backend project, also pass secret using the generated X-Bicho-Secret value from secure application configuration. The SDK does not load environment variables for you. Never use the admin token for capture or embed ingest secrets in distributed applications.
Capture explicitly
Use a dedicated test project for a setup check:
try:
raise RuntimeError('Bicho setup check')
except RuntimeError as error:
try:
uid = tracker.capture_exception(error, tags={'operation': 'setup'})
except DeliveryError as delivery:
print(f'Bicho delivery failed: {delivery}')
In an application’s existing error handler, preserve its original error handling after reporting. Do not let a reporting failure replace the application exception. capture_message('Catalog refresh failed') reports an ERROR message. Both capture methods accept keyword-only tags and contexts dictionaries.
Native exception capture records the type, bounded message, up to 32 traceback locations, and up to eight cause/context messages. It does not collect frame locals, source lines, or function arguments. Converting an exception message can invoke its custom __str__ method.
The SDK installs no global exception hooks, logging handlers, shutdown hooks, or framework integrations. It does not automatically collect requests, cookies, headers, sessions, user identities, or environment variables.
Delivery and retry identity
A capture call blocks until a valid acknowledgment or a DeliveryError; there is no queue or flush method. The total deadline defaults to 15 seconds and timeout accepts a finite positive value up to 60 seconds. Each attempt, including DNS, TLS, headers, and body reading, gets at most five seconds or the remaining deadline. A timed-out DNS lookup can finish later in a helper thread, but cannot send an event.
Network failures and HTTP 429/502/503/504 responses allow at most two retries with the same payload and occurrence UID. Retry-After is honored rather than retried early. Redirects, malformed acknowledgments, and populated OTLP partial success are terminal. A lost or truncated acknowledgment is a retryable transport failure.
DeliveryError exposes status, retryable, and retry_after without remote response bodies or raw transport errors. The retryable flag describes the failure, not whether the internal attempt or deadline budget remains. Invalid configuration and events raise ValueError.
For an explicit retry after ambiguous delivery, keep an immutable event and the same client configuration:
from bicho import Event
event = Event.message('Catalog refresh failed').with_tag('operation', 'refresh')
uid = tracker.send(event)
Reuse event with tracker.send(event) only when an appropriate bounded retry policy permits it. Another capture call creates a new occurrence. Fluent methods return copies preserving the UID and timestamp; mutable context input is normalized and snapshotted when attached.
A returned UID confirms a valid acknowledgment, but is not the server’s event lookup ID. Read the stored event through MCP or the API to check its evidence.
Async applications
Sending blocks the calling thread. An async caller can use await asyncio.to_thread(tracker.send, event) inside its existing event loop. This is caller-managed thread offloading, not an asynchronous SDK queue. Cancelling that await does not cancel a thread’s already-running delivery.
Limits and privacy
Events are limited to 60 KiB serialized. Messages and stack text stop at 16 KiB; tags and contexts have bounded key, entry, depth, and string limits. Context normalization accepts exact built-in containers and scalar types without invoking arbitrary objects’ string, repr, iteration, or serialization methods. Tags cannot override protocol-owned attributes.
with_level(...) accepts debug, info, warning, error, and fatal. Plain messages must use error or fatal; exceptions may use any level. Use with_context(...) for bounded structured evidence or with_stacktrace(...) for explicit stack text.
Credential-like keys and common credential strings are filtered before sending, but arbitrary messages, paths, and explicit stack text can still contain private information. There is no persistent offline queue, delivery guarantee, automatic fatal-process capture, tracing, metrics, replay, source-map upload, or native symbolication. See privacy and the OTLP guide for collector behavior.