Skip to main content

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:

  1. Look up the connector (a missing connector's NotFoundError propagates before any Event row is created - there's nothing to attribute a row to) and check it is syncable.
  2. Take a per-connector advisory lock (app/core/locks.py); if another sync of the connector is running, raise ConflictError (409). A scheduled run is also skipped with a ConflictError if a scheduled event for the connector started moments ago (another process already ran it).
  3. Insert an Event with status=running, then run the pipeline. On an unhandled exception, mark the event status=failed, set error, and re-raise - the caller's own error handling is unaffected, but the failure is still recorded.
  4. On completion, store the result, and derive status from it: succeeded if neither plan_sync nor the subscriber import failed, partial if exactly one did, failed if 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​

MethodPathNotes
GET/api/v1/eventsPaginated, 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 EventKind exists (connector_sync) - no event tracking yet for CSV imports (ImportRun already 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 an Event - see Connectors.
  • No retention/cleanup policy - rows accumulate indefinitely, same posture as ImportRun.