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 inservices/, database access inmodels/+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 (
>=) inpyproject.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.
| File | Do we commit it? |
|---|---|
pyproject.toml | yes |
uv.lock / requirements.txt | yes |
.env.example | yes |
.env | no (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.