Factorizer/Cluster ERP WikiCluster ERP Türkçe
Home / Platform

Explanation

Server startup and app factory

How python -m cluster_erp.server.main and server/asgi.py build the FastAPI application with create_app, and what the lifespan does at start-up and shutdown.

This page explains how the Cluster ERP server process starts: the two entry points server/main.py and server/asgi.py, the factory create_app in server/app.py, and the lifespan context that runs at start-up and shutdown. It is for developers and operators. The middleware order is on request pipeline and middleware, and service units and gunicorn settings are in the operations section.

In short

  • python -m cluster_erp.server.main calls uvicorn.run("cluster_erp.server.app:create_app", factory=True,...) (src/cluster_erp/server/main.py:39-58).
  • cluster_erp.server.asgi:app is a module-level application made by create_app() for a process manager (src/cluster_erp/server/asgi.py:1-19).
  • create_app reads Settings, builds the FastAPI object, installs error handlers and middleware, mounts the shared routes and the web app, and returns the app (src/cluster_erp/server/app.py:1287-1646).
  • The lifespan context creates the database handle, the EventBus, the ModuleRegistry, and the storage backend, then loads every enabled module (src/cluster_erp/server/app.py:58-140).
  • At shutdown the lifespan closes the PDF renderer, cancels background tasks, shuts down every module, and closes the database (src/cluster_erp/server/app.py:196-224).

How it works

Entry points

There are two entry points.

Entry point Used for Behaviour Source
python -m cluster_erp.server.main (console script cluster-erp-server) Development and single-process runs Runs uvicorn with the factory; enables reload when debug is on; uses server_workers processes when greater than 1 and reload is off src/cluster_erp/server/main.py:12-58, pyproject.toml:87
cluster_erp.server.asgi:app gunicorn with uvicorn workers Creates the app at import time, once per worker src/cluster_erp/server/asgi.py:1-19

main.py ignores server_workers above 1 on Windows and logs a warning (src/cluster_erp/server/main.py:27-35). It passes the proxy-header settings, the access-log switch, and the keep-alive timeout to uvicorn (src/cluster_erp/server/main.py:50-55). See the settings reference for these settings.

The factory

create_app(settings=None) runs these steps in order:

  1. Creates Settings() when none is passed (src/cluster_erp/server/app.py:1288-1289).
  2. Configures logging at DEBUG or INFO (src/cluster_erp/server/app.py:1291-1294).
  3. Builds FastAPI with title "Cluster ERP", version 0.1.0, and the lifespan context. The /api/docs and /api/redoc pages exist only when debug is on (src/cluster_erp/server/app.py:1296-1303).
  4. Stores the settings on app.state.settings (src/cluster_erp/server/app.py:1304).
  5. Applies three process-wide settings: change-history redaction columns, the authorisation cache lifetime, and the list count cap (src/cluster_erp/server/app.py:1307-1313).
  6. Installs the error handlers (src/cluster_erp/server/app.py:1314). See errors and exceptions.
  7. Adds five middlewares (src/cluster_erp/server/app.py:1317-1430). See request pipeline and middleware.
  8. Includes eight shared routers, and in multi-tenant mode also the signup and vendor routers (src/cluster_erp/server/app.py:1432-1450). See shared server routes.
  9. Defines seven inline routes: GET /api/v1/signup/ (single-tenant only), /.well-known/security.txt, /health, /ready, /api/v1/modules, /api/v1/_meta, and /api/v1/_switches (src/cluster_erp/server/app.py:1451-1572).
  10. Mounts the web app at /app/v and /app when webapp/ exists (src/cluster_erp/server/app.py:1633-1644). See cache and asset stamp.

Module routes are not added by create_app. Each module adds its own routes when the lifespan loads it.

The lifespan at start-up

The sequence diagram shows the start-up path of the lifespan context.

sequenceDiagram
 participant U as uvicorn or gunicorn
 participant L as lifespan
 participant R as ModuleRegistry
 participant M as Module
 participant D as Database
 U->>L: start application
 L->>D: Database(settings)
 L->>L: EventBus(), ModuleRegistry(), create_storage_backend()
 L->>R: load_all(app, bus, database, enabled_modules)
 R->>M: initialize, register_routes, register_events
 L->>D: seed sequences, permissions, deactivation columns
 L->>L: start background tasks
 L-->>U: yield (serve requests)

In order, the lifespan does the following:

  1. Creates Database(settings), EventBus(), ModuleRegistry(), and the storage backend, and stores them on app.state (src/cluster_erp/server/app.py:60-92).
  2. Subscribes a handler to AnonymiseSubject that drops cached user names of an anonymised account (src/cluster_erp/server/app.py:80-90).
  3. In multi-tenant mode, creates and migrates the default database, opens the TenantRegistry, and ensures the bootstrap operator. In single-tenant mode, checks the connection budget (src/cluster_erp/server/app.py:100-129). Tenancy is documented in multi-tenancy.
  4. Loads the modules listed in enabled_modules (src/cluster_erp/server/app.py:140). See module lifecycle.
  5. Validates that every sub-table tab points at an entity with columns, and fails start-up otherwise (src/cluster_erp/server/app.py:146, src/cluster_erp/server/app.py:547-589).
  6. Seeds the code sequences (src/cluster_erp/server/app.py:148, src/cluster_erp/server/app.py:1058-1066). See base model and ids.
  7. Builds the tenant-field specs from every module's metadata (src/cluster_erp/server/app.py:151). See tenant fields.
  8. Seeds permissions from metadata, adds missing deactivation columns, and builds the deactivation registry (src/cluster_erp/server/app.py:152-154).
  9. Runs three data reconciliation passes: CRM customers to parties, party role flags, and orphaned MRP runs (src/cluster_erp/server/app.py:155-157).
  10. Runs a log retention pass, starts the audit-log redaction backfill as a task, and replays the erasure ledger (src/cluster_erp/server/app.py:160-168).
  11. Starts background tasks: retention purge, subscription sweep, analytics summary refresh, and in multi-tenant mode the membership expiry loop (src/cluster_erp/server/app.py:170-194).
  12. Yields; the server now serves requests (src/cluster_erp/server/app.py:196).

The daily background loops sleep for 24 hours before each pass (src/cluster_erp/server/app.py:384, src/cluster_erp/server/app.py:424, src/cluster_erp/server/app.py:521). The analytics loop sleeps for a configured interval (src/cluster_erp/server/app.py:483). A periodic pass runs only in the worker that wins a PostgreSQL advisory lock (src/cluster_erp/server/app.py:357-369).

The lifespan at shutdown

After the yield, the lifespan closes the shared PDF renderer, cancels the background tasks and awaits them, calls registry.shutdown_all(), closes the tenant registry, and closes the database (src/cluster_erp/server/app.py:198-224).

Why it is built this way

  • The project's written engineering rules state that the server uses the uvicorn factory pattern.
  • The asgi.py docstring states why production uses gunicorn with uvicorn workers: on SIGHUP gunicorn starts new workers that import the current code and stops the old ones, which gives zero-downtime upgrades. The app is created per worker so that a reload loads new code (src/cluster_erp/server/asgi.py:3-12).
  • A comment states that main.py keeps one process on Windows because a shared socket was measured slower there (src/cluster_erp/server/main.py:27-34).
  • Rationale for the factory itself, beyond the above, is not determined from code.

Rules and boundaries

  • Settings() validates at construction and refuses unsafe combinations, for example multi-tenant mode with debug on (src/cluster_erp/config/settings.py:766-770). The server then does not start. See the settings reference.
  • A sub-table tab without columns stops start-up with an error (src/cluster_erp/server/app.py:547-589).

Interaction with other parts

Limits and open points

  • Whether a single-tenant start applies Alembic migrations is not stated in server/app.py; the lifespan calls upgrade_database_to_head only in multi-tenant mode (src/cluster_erp/server/app.py:100-113). Migration handling is documented in database migrations.
  • Most start-up passes log a warning and continue when they fail, for example _seed_sequences (src/cluster_erp/server/app.py:1058-1066).

Terms introduced

Term Code identifier Definition Turkish term Source
app factory create_app The function that builds and returns the FastAPI application. uygulama fabrikası src/cluster_erp/server/app.py:1287
lifespan lifespan The async context that runs start-up work before serving and cleanup after. yaşam süresi bağlamı src/cluster_erp/server/app.py:58-59

Verified against commit 92f8051 on 2026-11-13.

Verified against commit 92f8051 · 2026-11-13