Docs
FastAPI
Async-native recipes: atrack() in route handlers or BackgroundTasks, a lifespan-managed client, and an afeed_token endpoint.
1. Install and initialize
pip install softechlog, set SOFTECHLOG_SECRET_KEY, and create one module-level client — it keeps persistent connections, so events don't pay a TLS handshake each:
app/softechlog.pyPython SDK
# app/softechlog.py — one client for the whole app import os from softechlog import Softechlog log = Softechlog(secret_key=os.environ["SOFTECHLOG_SECRET_KEY"])
app/main.pylifespan
# app/main.py — close the client cleanly on shutdown
from contextlib import asynccontextmanager
from fastapi import FastAPI
from app.softechlog import log
@asynccontextmanager
async def lifespan(app: FastAPI):
yield
await log.aclose()
app = FastAPI(lifespan=lifespan)2. Track from route handlers
Use the async variant, atrack() — it never raises and returns None on failure, so there's no try/except to write. Two placement options:
app/routers/members.pyatrack()
# app/routers/members.py
from fastapi import APIRouter, BackgroundTasks, Depends
from app.softechlog import log
router = APIRouter()
@router.post("/workspaces/{ws_id}/members", status_code=201)
async def invite_member(ws_id: str, body: InviteIn, tasks: BackgroundTasks,
user=Depends(current_user)):
invitee = await do_invite(ws_id, body)
# Option A — await it (atrack never raises; adds a few ms):
await log.atrack(
actor={"id": str(user.id), "name": user.name, "email": user.email},
action="workspace.member.invited",
target_type="workspace", target_id=ws_id,
metadata={"invited_email": invitee.email, "role": invitee.role},
)
# Option B — off the request path entirely:
# tasks.add_task(log.atrack, actor={...}, action="workspace.member.invited",
# target_type="workspace", target_id=ws_id)
return inviteeSync codebase? The same client also exposes blocking
track() — identical arguments, same never-raises contract.3. Feed token endpoint
Unlike atrack(), afeed_token() does raise on failure — a missing token is something your UI must handle:
app/routers/activity.pyafeed_token()
# app/routers/activity.py — feed token for the signed-in user
from fastapi import APIRouter, Depends, HTTPException
from softechlog import SoftechlogError
from app.softechlog import log
router = APIRouter()
@router.get("/activity-token")
async def activity_token(user=Depends(current_user)):
try:
ft = await log.afeed_token(actor_id=str(user.id), ttl_seconds=3600)
except SoftechlogError as exc:
raise HTTPException(502, "Could not mint feed token") from exc
return {"token": ft.token, "expires_at": ft.expires_at}Front-end half — the drop-in component or your own UI over GET /v1/events — is in the Feed Component guide.