py-worker architecture
Startup
Section titled “Startup”worker.py runs main() under asyncio:
- Importing
configbuilds theCONFIGsingleton from the environment (and.envthroughpython-dotenv). It fails immediately ifHOME_PATHis unset. setup_telemetry()starts OpenTelemetry if it’s connected, and does nothing otherwise.- SIGINT and SIGTERM handlers set a shutdown event.
- Each BullMQ consumer starts (
create_worker("hello", hello_process)), and with Temporal, the Temporal worker runs as a background task. - The process waits for the shutdown event.
BullMQ consumers
Section titled “BullMQ consumers”create_worker(queue_name, process_function, concurrency=1) in
utils/worker.py builds the Redis URL from config and starts consuming at once
(autorun: True). With telemetry, each job runs inside a span named
process <queue>, so every job is its own trace in OpenObserve.
A processor is async def process(job, job_token). Raising marks the job
failed and lets BullMQ retry it per the producer’s attempts and backoff.
JobUtil helps with progress and logs (process_log, finalize).
The queue name is a string literal in worker.py: keep it identical to the
producer’s.
Database
Section titled “Database”utils/db.py opens a SQLAlchemy connection and session from the DB_*
settings. The models are hand-written under models/, mirroring the API’s
Prisma schema; nothing checks they agree, so update them whenever the API’s
schema changes. The template ships the building blocks, not tables:
models/mixins.py: the declarativeBase, annotated column types (uuid_pk,dcml, and so on) and timestamp mixins;models/enums.py:e(MyEnum)binds to a PostgreSQL enum the API’s migrations created, withcreate_type=Falseso SQLAlchemy never tries to create or race it;models/decorators.py:@reprand@to_dict.
Temporal (opt-in)
Section titled “Temporal (opt-in)”utils/temporal.py connects (mTLS when certificates are set) and creates a
worker for TEMPORAL_TASK_QUEUE with the workflows and activities listed there.
When telemetry is on, Temporal’s TracingInterceptor adds a span per workflow
and activity, continuing the trace of whatever started the workflow.
The Python worker polls its own task queue (python), not the Node
worker’s: a workflow task handed to a worker that doesn’t know its type fails
and retries, so each language gets its own queue.
Logging
Section titled “Logging”utils/logger.Logger writes rotating files (100 kB, 5 backups) under
$HOME_PATH/logs: worker.log, and workers/<name>.log per job type. Records
at INFO and above also go to stdout, so docker logs shows them. With telemetry
on, every record also goes to OpenObserve.
Shutdown
Section titled “Shutdown”On SIGINT or SIGTERM: BullMQ consumers close, the Temporal worker stops polling and waits for running activities, then telemetry flushes. If the Temporal worker stops on its own (a fatal error), the whole process shuts down with it.