Development Guidelines
This documentation is designed to establish a common idiom for writing and organizing code within our back-end Python project. It covers the environment setup, folder organization, database management, error handling, testing and more.
Table of Contents
Environment Setup
Requirements
-
Unix-like operating system or Windows (preferrably with WSL)
-
Python 3.12 or higher
-
Poetry for managing dependencies
curl -sSL https://install.python-poetry.org | python3.12 - # -
Docker and Docker Compose
Installation
-
Activate virtual environment
eval $(poetry env activate) -
Install dependencies in the virtual environment
poetry install -
Install pre-commit hooks
pre-commit install
Usage Instructions
Run application locally
# this command will spin up the application with Docker Compose
task up
Test application locally
task test
To learn more about how testing works, check out the TESTING.md documentation.
Other useful commands
task --list
Coding Standards & Conventions
Python Version & Types
- Python: 3.12+ required
- Type Hints: Mandatory for all functions and methods using
typingmodule - Return Types: Always specify, use
-> Nonefor void functions - Mypy: Strict mode enabled
Layers
-
Models (
features/{feature}/model.py): Define database entities using SQLAlchemy 2.0+ with async support. UseMappedfor columns,relationshipfor associations, andenum.Enumfor choice fields. -
Repositories (
features/{feature}/repository.py): Handle all database interactions. Methods are async and returnResult[T, E]from thepytresultlibrary for error handling. -
Services (
features/{feature}/service.py): Contain business logic, validation, and orchestration. Call repositories and perform checks before data operations. -
Routers (
routers/v1/routes.py): Define FastAPI endpoints using dependency injection for services and authentication. -
Core Config (
core/config/): Centralized configuration for database, auth, storage, and server setup.
Formatting & Linting
- Formatter: Black
- Import Sorting: isort with Black profile
- Linter: flake8
- Naming:
snake_casefor functions/variables,PascalCasefor classes
Imports
# Standard library imports first
from typing import List, Optional
import asyncio
# Third-party imports
from result import Err, Ok, Result
from fastapi import Depends
from sqlalchemy import select
# Local imports (absolute paths)
from app.features.user.model import User
from app.core.config.db.postgres import Base
Error Handling
- Pattern: Use
Result[T, E]frompytresultlibrary - Success:
return Ok(value) - Error:
return Err(CustomError()) - Custom Exceptions: Define in
features/{feature}/exceptions.pyand inherit fromBaseError - Error Checking: Use
result.is_ok(),result.is_err(),result.unwrap(),result.err()
Database Patterns
- ORM: SQLAlchemy 2.0+ with async support (
AsyncSession,async_sessionmaker) - Models: Use
MappedAsDataclassandDeclarativeBasefor type-safe models - Relationships: Define with
relationship()and appropriate cascade options - Enums: Use
str, enum.Enumfor choice fields, mapped withSQLEnum - Migrations: Alembic for schema changes
Example Model:
class User(Base):
__tablename__ = "users"
id: Mapped[int] = mapped_column(Integer, primary_key=True, init=False)
name: Mapped[str] = mapped_column(String(255), nullable=False)
role: Mapped[UserRole] = mapped_column(SQLEnum(UserRole), default=UserRole.collector, nullable=False)
Repository Patterns
- Class Structure: Inherit from no base, inject
AsyncSession - Methods: All async, return
Result[T, E] - Queries: Use
select(),update(),delete()with proper filtering - Error Handling: Catch exceptions and return
Err(SomeDeveloperDefinedError(e))
Example Repository:
class UserRepository:
def __init__(self, session: AsyncSession) -> None:
self.session = session
async def get_by_id(self, id: int) -> Result[Optional[User], UserError]:
stmt = select(User).where(User.id == id)
query = await self.session.execute(stmt)
user = query.scalar()
return Ok(user)
Service Patterns
- Class Structure: Inject repository in
__init__ - Business Logic: Perform validation, checks, and call repository methods
- Validation: Check lengths, existence, and business rules before operations
- Error Propagation: Handle repository errors and return appropriate service errors
Example Service:
class UserService:
def __init__(self, repository: UserRepository) -> None:
self._repository = repository
async def create(self, name: str, cpf: str, password: str) -> Result[None, UserError]:
if len(name) < 4:
return Err(UserInvalidNameError("Name must be at least 4 characters"))
# ... more validation and repository call
Router Patterns
- Framework: FastAPI with Pydantic schemas
- Dependencies: Use
Depends()for services, auth, and pagination - Error Handling: Catch service errors and raise HTTP exceptions
- Schemas: Define request/response models in
routers/v1/schemas.py
Example Router:
@router.post("/users", response_model=UserResponse)
async def create_user(
request: UserCreateRequest,
service: UserService = Depends(get_user_service),
) -> UserResponse:
result = await service.create(request.name, request.cpf, request.password)
if result.is_err():
raise HTTPException(status_code=400, detail=str(result.err()))
return UserResponse.from_user(result.unwrap())
Authentication & Authorization
- JWT: Custom implementation in
core/config/auth/jwt.py - Password Hashing: Use
argon2-cffiviafeatures/shared/utils/password_hasher.py - Dependencies:
get_current_logged_user
File Storage
- S3 Integration: Use
boto3for image uploads incore/config/bs.py
Testing
- Framework: pytest with pytest-asyncio
- Structure:
tests/features/{domain}/v1/test_{component}.py - Async Tests: Use
@pytest.mark.asyncio - Fixtures: Define in
conftest.py - Coverage: Use
pytest-covfor reports
Security Best Practices
- Secrets: Never log or commit secrets; use environment variables
- Input Validation: Validate all inputs in services and schemas
- SQL Injection: Use parameterized queries via SQLAlchemy
- CORS: Configured in
server.pywith environment-based origins
Development Workflow
- Setup:
poetry install && pre-commit install - Development:
task upfor dev server - Testing:
task testfor unit/integration tests - Code Quality:
task check-allbefore committing - Database:
task migrate-newfor schema changes
Key Dependencies
- Web Framework: FastAPI
- ORM: SQLAlchemy[asyncio]
- Validation: Pydantic
- Error Handling: pytresult
- Password Hashing: argon2-cffi
- JWT: pyjwt
- Testing: pytest, pytest-asyncio, pytest-cov
- Linting: flake8, mypy, black, isort, bandit
Best Practices
- Async/Await: Use for all I/O operations (DB, HTTP, file operations)
- Dependency Injection: Prefer over global state
- Single Responsibility: Each class/method has one purpose
- Immutable Data: Avoid mutating inputs
- Error Messages: Provide clear, user-friendly error messages
- Documentation: Use docstrings for public methods (Google style)
- Comments: Avoid in-code comments unless necessary for complex logic
- Version Control: Follow conventional commits