Como organizar projetos Python grandes

1. Estrutura de Diretórios e Módulos

A base de qualquer projeto Python grande começa com uma estrutura de diretórios bem definida. A abordagem mais recomendada para projetos escaláveis é o layout src/, que separa o código fonte da raiz do projeto:

meu_projeto/
├── src/
│   ├── meu_projeto/
│   │   ├── __init__.py
│   │   ├── core/
│   │   │   ├── __init__.py
│   │   │   ├── domain.py
│   │   │   └── use_cases.py
│   │   ├── services/
│   │   │   ├── __init__.py
│   │   │   ├── auth_service.py
│   │   │   └── payment_service.py
│   │   ├── apps/
│   │   │   ├── __init__.py
│   │   │   ├── web_api.py
│   │   │   └── cli.py
│   │   └── utils/
│   │       ├── __init__.py
│   │       ├── validators.py
│   │       └── helpers.py
│   └── tests/
│       ├── unit/
│       ├── integration/
│       └── e2e/
├── pyproject.toml
├── Dockerfile
├── Makefile
└── README.md

Separação por domínios: Cada pasta representa um domínio de responsabilidade. core contém lógica de negócio pura, services orquestra operações, apps expõe interfaces (API, CLI), e utils oferece funções auxiliares reutilizáveis.

Imports explícitos: Evite from modulo import *. Prefira imports claros:

# Bom
from src.meu_projeto.core.domain import Usuario
from src.meu_projeto.services.auth_service import autenticar

# Evite
from src.meu_projeto.core import *

Gerenciamento de __init__.py: Use __init__.py apenas para exportar interfaces públicas, não para carregar módulos inteiros:

# src/meu_projeto/__init__.py
from .core.domain import Usuario, Pedido
from .services.auth_service import autenticar

__all__ = ["Usuario", "Pedido", "autenticar"]

2. Gerenciamento de Dependências e Ambientes

O pyproject.toml se tornou o padrão para projetos Python modernos. Com Poetry ou PDM, você gerencia dependências de forma declarativa:

# pyproject.toml
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

[tool.poetry]
name = "meu-projeto"
version = "0.1.0"
description = "Sistema de e-commerce escalável"

[tool.poetry.dependencies]
python = "^3.11"
fastapi = "^0.104.0"
pydantic = "^2.5.0"
sqlalchemy = "^2.0.0"
dependency-injector = "^4.41.0"

[tool.poetry.group.dev.dependencies]
pytest = "^7.4.0"
ruff = "^0.1.0"
black = "^23.11.0"
pre-commit = "^3.5.0"

Lockfiles: Sempre versionar poetry.lock ou pdm.lock para garantir consistência entre ambientes.

Isolamento com Docker:

# Dockerfile
FROM python:3.11-slim

WORKDIR /app
COPY pyproject.toml poetry.lock ./
RUN pip install poetry && poetry install --no-dev
COPY src/ ./src/

CMD ["poetry", "run", "uvicorn", "src.meu_projeto.apps.web_api:app"]

3. Padrões de Projeto e Arquitetura

Clean Architecture organiza o código em camadas concêntricas, com dependências apontando para dentro:

src/meu_projeto/
├── domain/          # Entidades e regras de negócio (sem dependências externas)
├── application/     # Casos de uso e portas (interfaces)
├── infrastructure/  # Implementações concretas (banco, APIs externas)
└── interfaces/      # Controladores, serializers, presenters

Injeção de dependência com dependency-injector:

# containers.py
from dependency_injector import containers, providers
from src.meu_projeto.infrastructure.database import Database
from src.meu_projeto.application.repositories import UsuarioRepository

class Container(containers.DeclarativeContainer):
    config = providers.Configuration()

    database = providers.Singleton(Database, url=config.db_url)
    usuario_repo = providers.Factory(
        UsuarioRepository,
        session=database.provided.session
    )

Repositórios e serviços desacoplam lógica de negócio de infraestrutura:

# domain/repositories.py
from abc import ABC, abstractmethod

class AbstractUsuarioRepository(ABC):
    @abstractmethod
    def buscar_por_email(self, email: str) -> Usuario | None:
        pass

# infrastructure/repositories.py
class SQLAlchemyUsuarioRepository(AbstractUsuarioRepository):
    def buscar_por_email(self, email: str) -> Usuario | None:
        return self.session.query(UsuarioORM).filter_by(email=email).first()

4. Organização de Testes e Qualidade de Código

Estrutura de testes paralela ao código fonte:

tests/
├── unit/
│   ├── test_domain.py
│   └── test_use_cases.py
├── integration/
│   ├── test_database.py
│   └── test_repositories.py
└── e2e/
    └── test_api.py

Ferramentas de qualidade configuradas no pyproject.toml:

[tool.ruff]
line-length = 100
select = ["E", "F", "I", "N", "W"]
ignore = ["E501"]

[tool.black]
line-length = 100
target-version = ["py311"]

[tool.isort]
profile = "black"
line_length = 100

Pre-commit hooks para automação:

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.1.0
    hooks:
      - id: ruff
      - id: ruff-format
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.5.0
    hooks:
      - id: trailing-whitespace
      - id: end-of-file-fixer

5. Documentação e Convenções

Docstrings padronizadas (Google Style):

def calcular_frete(cep_origem: str, cep_destino: str, peso: float) -> float:
    """Calcula o valor do frete baseado na distância e peso.

    Args:
        cep_origem: CEP de origem no formato XXXXX-XXX
        cep_destino: CEP de destino no formato XXXXX-XXX
        peso: Peso do pacote em quilogramas

    Returns:
        Valor do frete em reais

    Raises:
        ValueError: Se o CEP for inválido ou peso negativo
    """

Arquivos essenciais:

  • README.md: Visão geral, instalação, uso
  • CONTRIBUTING.md: Diretrizes para contribuidores
  • CHANGELOG.md: Histórico de versões (seguindo Keep a Changelog)

Makefile para comandos comuns:

.PHONY: install test lint format clean

install:
    poetry install

test:
    poetry run pytest tests/ -v --cov=src

lint:
    poetry run ruff check src/ tests/

format:
    poetry run black src/ tests/
    poetry run isort src/ tests/

clean:
    find . -type d -name "__pycache__" -exec rm -rf {} +

6. Performance e Escalabilidade

Lazy imports para módulos pesados:

# Em vez de importar no topo do arquivo
def processar_relatorio():
    from src.meu_projeto.utils.relatorios import gerar_pdf
    return gerar_pdf()

Profiling com cProfile:

python -m cProfile -o output.prof src/meu_projeto/apps/cli.py
python -m pstats output.prof  # Análise interativa

Estratégias de cache:

from functools import lru_cache

@lru_cache(maxsize=128)
def consultar_tabela_frete(cep_prefixo: str) -> dict:
    # Consulta em banco ou API externa
    pass

7. Versionamento e Colaboração

Trunk-based development para equipes grandes: branches curtas (máximo 2 dias), merge frequente para main, feature flags para código incompleto.

Checklist de code review específico para Python:

  • [ ] Imports estão organizados (isort)
  • [ ] Tipagem está correta (mypy)
  • [ ] Docstrings seguem o padrão definido
  • [ ] Testes unitários cobrem novos casos
  • [ ] Nenhum print() ou # TODO esquecido
  • [ ] Dependências novas foram adicionadas ao pyproject.toml

Gerenciamento de configurações com pydantic-settings:

from pydantic_settings import BaseSettings

class Settings(BaseSettings):
    database_url: str
    secret_key: str
    debug: bool = False

    class Config:
        env_file = ".env"
        env_file_encoding = "utf-8"

Organizar projetos Python grandes exige disciplina desde o início. A estrutura src/, combinada com Clean Architecture, injeção de dependência e ferramentas modernas de qualidade, permite que o código evolua sem se tornar um monólito frágil. Lembre-se: a organização não é um fim, mas um meio para manter a produtividade da equipe e a saúde do projeto a longo prazo.

Referências