Events
Code lives in app/models/event.py, app/schemas/event.py, app/services/events.py,
app/api/v1/routes/events.py.
An Event is one tracked run of a background operation - today, only
EventKind.CONNECTOR_SYNC (a connector's full sync pipeline, see
Connectors) exists, but kind is a discriminator meant to carry
future event types without a schema change. result is a kind-specific JSON payload - for
connector_sync, a serialized ConnectorFullSyncResult.
Immutable once finished, like ImportRun - an audit/history record, not a mutable catalog row,
so it has no deleted_at.
Shape
events
id uuid, PK
tenant_id uuid, FK -> tenants.id, indexed
kind str - EventKind, e.g. "connector_sync"
trigger str - EventTrigger: "scheduled" | "manual"
status str - ImportRunStatus: "running" | "succeeded" | "failed" | "partial"
connector_id uuid | null, FK -> connectors.id, indexed - null once the connector is
soft-deleted (or for a future event kind with no connector)
connector_name str | null - denormalized at creation time, so the row stays meaningful
after the connector is renamed or soft-deleted
result jsonb - kind-specific outcome payload
error str | null - set only when the run raised outright, as opposed to a
partial per-stage failure already captured inside `result`
started_at / finished_at / created_at
Lifecycle
app/services/events.py's run_and_record_connector_sync wraps
connector_full_sync.run_full_sync:
- Look up the connector (a missing connector's
NotFoundErrorpropagates before anyEventrow is created - there's nothing to attribute a row to) and check it is syncable. - Take a per-connector advisory lock (
app/core/locks.py); if another sync of the connector is running, raiseConflictError(409). A scheduled run is also skipped with aConflictErrorif a scheduled event for the connector started moments ago (another process already ran it). - Insert an
Eventwithstatus=running, then run the pipeline. On an unhandled exception, mark the eventstatus=failed, seterror, and re-raise - the caller's own error handling is unaffected, but the failure is still recorded. - On completion, store the result, and derive
statusfrom it:succeededif neitherplan_syncnor the subscriber import failed,partialif exactly one did,failedif both did.
Both POST /connectors/{id}/sync-all (trigger=manual) and the scheduler's per-connector cron
job (trigger=scheduled, app/core/scheduler.py) call this same function - an event's trigger
is the only thing that tells the two apart. The scheduler's reap_orphaned_events closes
running events left behind by a crashed process.
API
| Method | Path | Notes |
|---|---|---|
| GET | /api/v1/events | Paginated, filterable by kind, connector_id, status. Read-only - there is no create/update/delete endpoint; events are only ever written by run_and_record_connector_sync |
What's deliberately not built
- Only one
EventKindexists (connector_sync) - no event tracking yet for CSV imports (ImportRunalready serves that purpose for its own domain - see Customer/Subscriber CSV Import) or any other background work. POST /connectors/{id}/sync(single data kind, not the full pipeline) does not create anEvent- see Connectors.- No retention/cleanup policy - rows accumulate indefinitely, same posture as
ImportRun.