Skeleton template for Python >=3.12 services with dual runtime support:
- API mode (
APP_TYPE=api) - Worker mode (
APP_TYPE=worker)
The template is spec-driven: implementation should follow openspec/.
- API + worker runtime in one package.
- Worker supports two modes:
WORKER_MODE=oneshot(default): run once and exit.WORKER_MODE=loop: poll continuously with graceful signal shutdown.
- Unified container entrypoint with explicit
APP_TYPErouting. - Multi-stage Docker build for smaller runtime image.
- Structured JSON logging to stdout.
- Postgres + Redis wiring and pytest scaffold.
src/skel_v3/— app packageapi/— Flask routes and health checksworker/— worker runtime helperslogs/,db/,util/— shared runtime components
openspec/— source of truth for behavior/specificationsentrypoint.sh— starts API (Gunicorn) or worker (python -m)iac/docker/alpine.dockerfile— multi-stage container build
Execution and worker:
APP_TYPE(default:api):apiorworkerWORKER_MODE(default:oneshot):oneshotorloopWORKER_POLL_INTERVAL(default:5): loop sleep interval in seconds
Service metadata:
SERVICE_NAME(default:skel-service)SERVICE_VERSION(default:0.1.0)SERVICE_NAMESPACE(default:default)SERVICE_ENV(default:local)
Logging:
LOG_LEVEL(default:DEBUGinlocal/dev,INFOotherwise)
Postgres:
PG_ENABLED(default:false)PG_HOST(default:postgres)PG_PORT(default:5432)PG_USER(default:postgres)PG_PASSWORD(default:postgres)PG_DBNAME(default:postgres)PG_MIN_CONN(default:1)PG_MAX_CONN(default:5)PG_SSLMODEis currently not active in code (left as commented placeholder in.env)
Redis:
REDIS_ENABLED(default:false)REDIS_HOST(default:redis)REDIS_PORT(default:6379)REDIS_DB(default:0)REDIS_PASSWORD(default: empty/None)REDIS_MAX_CONN(default:20)
API:
GUNIPORT(default:9000)GUNICORN_APP(default:skel_v3.app:app_factory)
Worker:
WORKER_TARGET(default:skel_v3.app)
APP_TYPE=api:
entrypoint.shruns Gunicorn with--factoryandGUNICORN_APP.
APP_TYPE=worker:
entrypoint.shrunspython -m $WORKER_TARGET.skel_v3.app.main()routes torun_worker_app(config).run_worker_appusesWORKER_MODE:oneshot: one work cycle then clean exit.loop: repeated work until SIGINT/SIGTERM.
Read before coding:
openspec/project.md- relevant domain file under
openspec/specs/<domain>/spec.md
Rules:
- spec is source of truth
- do not guess behavior not described in spec
- propose spec deltas when behavior changes are needed
Install:
poetry installRun API:
export APP_TYPE=api
poetry run python -m skel_v3.appRun worker one-shot:
export APP_TYPE=worker WORKER_MODE=oneshot
poetry run python -m skel_v3.appRun worker loop:
export APP_TYPE=worker WORKER_MODE=loop WORKER_POLL_INTERVAL=5
poetry run python -m skel_v3.appTests:
poetry run pytest