Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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

  1. Environment Setup
  2. Usage Instructions
  3. Coding Standards & Conventions

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

    See Docker Installation and Docker Compose Installation

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 typing module
  • Return Types: Always specify, use -> None for void functions
  • Mypy: Strict mode enabled

Layers

  1. Models (features/{feature}/model.py): Define database entities using SQLAlchemy 2.0+ with async support. Use Mapped for columns, relationship for associations, and enum.Enum for choice fields.

  2. Repositories (features/{feature}/repository.py): Handle all database interactions. Methods are async and return Result[T, E] from the pytresult library for error handling.

  3. Services (features/{feature}/service.py): Contain business logic, validation, and orchestration. Call repositories and perform checks before data operations.

  4. Routers (routers/v1/routes.py): Define FastAPI endpoints using dependency injection for services and authentication.

  5. 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_case for functions/variables, PascalCase for 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] from pytresult library
  • Success: return Ok(value)
  • Error: return Err(CustomError())
  • Custom Exceptions: Define in features/{feature}/exceptions.py and inherit from BaseError
  • 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 MappedAsDataclass and DeclarativeBase for type-safe models
  • Relationships: Define with relationship() and appropriate cascade options
  • Enums: Use str, enum.Enum for choice fields, mapped with SQLEnum
  • 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-cffi via features/shared/utils/password_hasher.py
  • Dependencies: get_current_logged_user

File Storage

  • S3 Integration: Use boto3 for image uploads in core/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-cov for 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.py with environment-based origins

Development Workflow

  1. Setup: poetry install && pre-commit install
  2. Development: task up for dev server
  3. Testing: task test for unit/integration tests
  4. Code Quality: task check-all before committing
  5. Database: task migrate-new for 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