Python

The Python SDK targets Python 3.8+ and uses only the standard library — no requests, no httpx, no transitive deps. It installs a sys.excepthook and a threading.excepthook at init time so unhandled exceptions on the main thread and worker threads are captured automatically.

Building a web app? Don't stop at init() — see Frameworks below to enable automatic 500-error capture for FastAPI, Flask, and Django in one line.

Install

$ pip install tinymonpy

Or pin it in requirements.txt / pyproject.toml:

tinymonpy==0.4.1

init()

Call init() once, at the very top of your app's entry point:

import os
import tinymonpy

tinymonpy.init(
    dsn=os.environ['TINYMON_DSN'],
    environment='production',
    release=os.environ.get('RELEASE'),  # e.g. git short SHA, set at deploy
    sample_rate=1.0,
)

Arguments

ArgumentTypeDescription
dsnstrRequired. The project DSN, e.g. tm_pub_….
endpointstr | NoneOverride the ingest URL. Defaults to https://console.tinymon.dev/api/ingest.
environmentstr | NoneFree-form tag — production, staging, etc.
releasestr | NoneVersion string for your app. Powers release tracking (first-seen-in-release, regression detection) and scopes source maps. Use the same value at deploy.
sample_ratefloat0 to 1. Default 1.0.
before_sendCallable[[dict], dict | None]Mutate or drop events. Return None to drop.

Capturing exceptions

import tinymonpy

try:
    risky_thing()
except Exception as e:
    tinymonpy.capture_exception(e)

# Or a plain message:
tinymonpy.capture_message('cron job took 28 seconds', level='warning')

Levels: 'error', 'warning', 'info'.

Delivery & flush()

Events are sent immediately on a background thread — no batching delay. Failed sends are queued and retried with backoff; successful ones never queue. An atexit hook makes a best-effort flush on shutdown.

In a long-running server you never need to do anything. In a short-lived process — an AWS Lambda handler, a CLI script, a per-job worker — call flush() before it exits so an in-flight send isn't dropped:

import tinymonpy

try:
    run_job()
except Exception as e:
    tinymonpy.capture_exception(e)
    tinymonpy.flush(timeout=2.0)  # seconds; never blocks forever
    raise
Rule of thumb. Long-running server → skip flush(). Serverless / script → call it once before exit. Recipes & mechanics: Delivery & flush() and Transport internals.

User & tag context

tinymonpy.set_user({'id': user.id})
tinymonpy.set_tag('plan', user.plan)
tinymonpy.set_tag('tenant_id', str(tenant.id))
Privacy. Only pass an identifier — not an email or name. The scope is module-global, not per-request, so for web frameworks see the per-framework section below.

Add short notes about what happened before an error. The last 30 are attached to the next event.

import time

tinymonpy.add_breadcrumb({
    'timestamp': time.time(),
    'category':  'http',
    'message':   'POST /api/orders → 500',
    'level':     'error',
})

Frameworks

The SDK ships built-in middleware so you get automatic 500 capture in one line — no boilerplate class to copy. Pick the one for your stack (TinymonWSGI for WSGI apps, TinymonASGI for async apps).

FastAPI / Starlette

import tinymonpy
from fastapi import FastAPI

tinymonpy.init(dsn=os.environ['TINYMON_DSN'])

app = FastAPI()
app.add_middleware(tinymonpy.TinymonFastAPIMiddleware)  # that's it

Flask

import tinymonpy
from flask import Flask

tinymonpy.init(dsn=os.environ['TINYMON_DSN'])

app = Flask(__name__)
app.wsgi_app = tinymonpy.TinymonWSGI(app.wsgi_app)  # wrap the WSGI app

Django

Wrap the WSGI application in wsgi.py:

import tinymonpy
from django.core.wsgi import get_wsgi_application

tinymonpy.init(dsn=os.environ['TINYMON_DSN'])

application = tinymonpy.TinymonWSGI(get_wsgi_application())

Celery / background workers

Capture in the task's on_failure hook or wrap the task body in try/except. The SDK installs a threading.excepthook so untouched worker threads are also covered.