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, usoCONTRIBUTING.md: Diretrizes para contribuidoresCHANGELOG.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# TODOesquecido - [ ] 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
- Python Packaging User Guide — Guia oficial sobre estrutura de projetos, pyproject.toml e boas práticas de empacotamento.
- Clean Architecture in Python — Livro "Architecture Patterns with Python" que aborda DDD, repositórios e inversão de dependência.
- Poetry Documentation — Documentação oficial do Poetry para gerenciamento de dependências e ambientes virtuais.
- dependency-injector Documentation — Biblioteca para implementação de injeção de dependência em Python.
- Ruff Documentation — Ferramenta de linting e formatação extremamente rápida para Python.
- Pre-commit Hooks — Framework para gerenciamento de hooks de git que automatizam verificações de qualidade.
- pydantic-settings Documentation — Biblioteca para gerenciamento de configurações e variáveis de ambiente com validação de tipos.
- Testing Python Applications with pytest — Guia oficial do pytest com exemplos de organização de testes para projetos grandes.