Project structure, venv and dependencies

A repeatable project structure makes every ZEUS service look the same, so a new person finds their way around it in five minutes. On top of that comes environment isolation and deterministic dependencies.

Directory structure

We keep ZEUS backend services in a layered layout:

service-inventory/
├── app/
│   ├── __init__.py
│   ├── main.py            # building the FastAPI application
│   ├── api/               # routers (the HTTP layer)
│   ├── core/              # config, security, settings
│   ├── models/            # SQLAlchemy models
│   ├── schemas/           # Pydantic models (DTOs)
│   ├── services/          # domain logic
│   ├── workers/           # ARQ tasks
│   └── db/                # session, migrations
├── tests/
├── pyproject.toml
└── README.md

ProfessNet standard: routers contain no domain logic. An endpoint validates the input (Pydantic), calls services/ and returns the result. The logic lives in services/, database access in models/ + db/.

Virtual environment (venv)

Each service has its own isolated environment. We never install packages globally.

python -m venv .venv
source .venv/bin/activate        # Windows: .venv\Scripts\activate
pip install -e ".[dev]"

Dependencies in pyproject.toml

We declare dependencies in pyproject.toml, not in a bare requirements.txt. We separate production dependencies from development ones.

[project]
name = "service-inventory"
requires-python = ">=3.12"
dependencies = [
    "fastapi>=0.115",
    "sqlalchemy[asyncio]>=2.0",
    "arq>=0.26",
    "pydantic>=2.7",
]

[project.optional-dependencies]
dev = ["pytest", "pytest-asyncio", "mypy", "ruff", "black"]

Pinning versions (lock)

For deterministic builds we generate a lock file. At ProfessNet we use uv (a fast resolver) or pip-tools:

uv lock                 # creates uv.lock
uv sync                 # installs exactly what's in the lock

ProfessNet standard: we declare version ranges (>=) in pyproject.toml, and pin exact versions in the lock file, which we commit to the repo. CI and production install from the lock — that guarantees repeatability.

Environment variables

We don't hard-code configuration. We read it through Pydantic Settings:

from pydantic_settings import BaseSettings


class Settings(BaseSettings):
    database_url: str
    redis_url: str

    class Config:
        env_file = ".env"

The .env file is in .gitignore — in the repo we keep only .env.example.

FileDo we commit it?
pyproject.tomlyes
uv.lock / requirements.txtyes
.env.exampleyes
.envno (secrets)
.venv/no

A uniform structure + isolated venv + lock = a service that builds the same way on a laptop, in CI and in production. That's the foundation on which we build FastAPI.