29ffb42898
---ci--- project: atelier phase: 0 milestone: v0.4 status: complete requirements: covered: [ATELIER-92, ATELIER-93, ATELIER-94, ATELIER-95, ATELIER-96, ATELIER-97, ATELIER-98, ATELIER-99, ATELIER-100, ATELIER-101, ATELIER-102, ATELIER-103, ATELIER-104, ATELIER-105, ATELIER-106, ATELIER-107, ATELIER-108, ATELIER-109, ATELIER-110, ATELIER-111, ATELIER-112, ATELIER-113, ATELIER-114, ATELIER-115, ATELIER-116, ATELIER-117] partial: [] ---/ci---
3.2 KiB
3.2 KiB
Python — Language Application
How Atelier's domain principles apply in Python specifically. Derives from
domains/docs.
Derived Docs
- py-types.md — type hints + Pydantic, mypy/pyright, gradual typing.
- py-tooling.md — ruff, mypy, poetry, uv, virtualenv discipline.
- py-async.md — asyncio, anyio, cancellation, structured concurrency.
- py-testing.md — pytest, factory_boy, fixture discipline, parametrize.
Type System (C1 Correctness, Data P7 Type Fidelity)
- Type hints on every function:
def get_user(id: UUID) -> User | None:. mypy --strictorpyrightin CI: type check is not optional.- No
Anywithout justification:Anydisables the type checker. Useobject+ narrowing. - Pydantic for runtime validation: schemas validate and type at the boundary.
from pydantic import BaseModel
from uuid import UUID
class UserCreate(BaseModel):
email: str
name: str
# additionalProperties: false by default (extra='forbid')
Error Handling (Errors P1 Errors are Data)
- Exceptions for exceptional cases, not control flow.
raisenotreturn Nonefor errors. - Custom exception hierarchy:
class AppError(Exception): pass
class ValidationError(AppError): pass
class NotFoundError(AppError): pass
- Never bare
except::except Exception as e:(catch specific, not everything). - Never
except: pass: log and re-raise or handle, never swallow (Errors P2).
Async (Concurrency P7, P8)
asynciofor I/O-bound:async def,await. Not threads for I/O.anyiofor portability if you may switch runtimes (trio compatibility).- Timeout on every
await:asyncio.wait_for(coro, timeout=5), not bareawait. - Cancellation propagated:
asyncio.CancelledErroris not caught; it propagates.
Immutability (Concurrency P1)
frozen=Truedataclasses for value objects:
from dataclasses import dataclass
@dataclass(frozen=True)
class UserId:
value: str
- Tuples over lists for fixed-length, immutable sequences.
- No in-place mutation of shared state: return new objects.
Nullability (C1)
Optional[T]isT | None: explicit, must be checked.Noneis not "not found": raiseNotFoundErroror returnResult, notNone.assertis for invariants, not for runtime checks (stripped with-O).
Testing (Testing)
- pytest with fixtures (factories, not shared state).
pytest --randomlyto catch order-dependent tests (P2 Independence).freezegunfor time: nodatetime.now()in tests; inject the clock.factory_boyorpytest-factoryboyfor realistic factories.
Observability (Observability P1)
structlogorpython-json-logger: JSON logs, notprint.loggingwith structured formatter: every log hasrequest_id,user_id,event.- No secrets in logs:
mask_secret()helper, orstructlogprocessors.
Tooling (DevOps P2)
rufffor lint + format: replaces flake8 + black + isort.mypy --strictin CI: type check.pip-toolsorpoetryfor lockfile: pinned dependencies.pip install --no-deps -r requirements.txt: reproducible install.