Estrutura de um projeto Python profissional

1. Organização de diretórios e módulos

Um projeto Python profissional começa com uma estrutura de diretórios clara e previsível. A organização padrão adotada pela comunidade separa o código-fonte do código de configuração, facilitando a navegação e manutenção.

meu_projeto/
├── src/
│   └── meu_pacote/
│       ├── __init__.py
│       ├── modulo_principal.py
│       ├── utils/
│       │   ├── __init__.py
│       │   └── helpers.py
│       └── models/
│           ├── __init__.py
│           └── entidades.py
├── tests/
│   ├── __init__.py
│   ├── unitarios/
│   ├── integracao/
│   └── conftest.py
├── docs/
├── scripts/
├── pyproject.toml
├── README.md
├── CHANGELOG.md
├── CONTRIBUTING.md
└── .gitignore

A pasta src/ isola o código de produção, evitando importações acidentais de diretórios de configuração. A nomenclatura de pacotes segue o padrão snake_case, e módulos dentro do pacote devem ser nomeados de forma descritiva e curta.

# src/meu_pacote/__init__.py
from .modulo_principal import funcao_principal

__version__ = "0.1.0"
__all__ = ["funcao_principal"]

2. Gerenciamento de dependências e ambientes

O arquivo pyproject.toml tornou-se o padrão para configuração de projetos Python modernos, substituindo setup.py e setup.cfg. Ele centraliza metadados, dependências e configurações de ferramentas.

[build-system]
requires = ["setuptools>=64.0", "wheel"]
build-backend = "setuptools.backends._legacy:_Backend"

[project]
name = "meu-pacote"
version = "0.1.0"
description = "Um pacote profissional"
requires-python = ">=3.10"
dependencies = [
    "requests>=2.28",
    "pydantic>=2.0",
]

[project.optional-dependencies]
dev = [
    "pytest>=7.0",
    "black>=23.0",
    "ruff>=0.1.0",
    "mypy>=1.0",
    "pre-commit>=3.0",
]
test = [
    "pytest>=7.0",
    "pytest-cov>=4.0",
]

Para ambientes de desenvolvimento, ferramentas como poetry ou pipenv gerenciam automaticamente o bloqueio de versões. Se optar por pip, utilize requirements.txt e requirements-dev.txt:

# requirements.txt
requests==2.31.0
pydantic==2.5.0

# requirements-dev.txt
-r requirements.txt
pytest==7.4.3
black==23.11.0

3. Configuração de ferramentas de qualidade de código

A qualidade do código é mantida através de ferramentas automatizadas. O ruff é recomendado por sua velocidade e por unificar linting e formatação, substituindo flake8 e black em projetos modernos.

[tool.ruff]
target-version = "py310"
line-length = 88
select = ["E", "F", "I", "N", "W"]

[tool.ruff.format]
quote-style = "double"

[tool.black]
line-length = 88
target-version = ["py310"]

[tool.mypy]
python_version = "3.10"
strict = true
ignore_missing_imports = true

O pre-commit automatiza a execução dessas ferramentas antes de cada commit:

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.1.0
    hooks:
      - id: ruff
        args: [--fix]
      - id: ruff-format
  - repo: https://github.com/pre-commit/mirrors-mypy
    rev: v1.7.0
    hooks:
      - id: mypy

4. Estrutura de testes automatizados

Testes são organizados em camadas: unitários (testam funções isoladas), de integração (testam interações entre módulos) e de sistema (testam fluxos completos). O pytest é a ferramenta padrão.

# tests/conftest.py
import pytest
from meu_pacote.models.entidades import Usuario

@pytest.fixture
def usuario_padrao():
    return Usuario(nome="Maria", email="maria@exemplo.com")

@pytest.fixture
def dados_api():
    return {"status": "ok", "dados": []}

# tests/unitarios/test_modulo_principal.py
import pytest
from meu_pacote.modulo_principal import processar_dados

class TestProcessarDados:
    def test_dados_validos(self, dados_api):
        resultado = processar_dados(dados_api)
        assert resultado["status"] == "processado"

    @pytest.mark.integracao
    def test_com_api_externa(self):
        # Teste que requer integração
        pass

    @pytest.mark.slow
    def test_grande_volume(self):
        # Teste demorado
        pass

Para cobertura de código, configure o pytest-cov no pyproject.toml:

[tool.coverage.run]
source = ["src"]
omit = ["*/tests/*", "*/__init__.py"]

[tool.coverage.report]
fail_under = 80
show_missing = true

Execute os testes com: pytest --cov=src --cov-report=html tests/

5. Documentação e versionamento

Documentação inline segue a PEP 257 com docstrings no estilo Google ou NumPy. O Sphinx gera documentação HTML a partir dessas docstrings.

def calcular_media(valores: list[float]) -> float:
    """Calcula a média aritmética de uma lista de números.

    Args:
        valores: Lista de números float para cálculo.

    Returns:
        A média aritmética dos valores.

    Raises:
        ValueError: Se a lista estiver vazia.

    Examples:
        >>> calcular_media([1.0, 2.0, 3.0])
        2.0
    """
    if not valores:
        raise ValueError("Lista vazia não permite cálculo de média")
    return sum(valores) / len(valores)

O repositório deve conter:
- README.md: Visão geral, instalação e exemplos rápidos
- CHANGELOG.md: Registro de versões seguindo Keep a Changelog
- CONTRIBUTING.md: Guia para contribuidores com padrões de código e processo de PR

O versionamento semântico (SemVer) segue o formato MAJOR.MINOR.PATCH:
- MAJOR: mudanças incompatíveis na API
- MINOR: funcionalidades novas compatíveis
- PATCH: correções de bugs compatíveis

6. Automação de tarefas e CI/CD

Um Makefile automatiza tarefas comuns de desenvolvimento:

.PHONY: install test lint format clean

install:
    pip install -e ".[dev,test]"

test:
    pytest --cov=src tests/

lint:
    ruff check src/ tests/

format:
    ruff format src/ tests/

typecheck:
    mypy src/

clean:
    rm -rf build/ dist/ *.egg-info .coverage htmlcov/

all: lint format typecheck test

Para CI/CD com GitHub Actions, configure um pipeline completo:

# .github/workflows/ci.yml
name: CI
on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ["3.10", "3.11", "3.12"]

    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}

      - name: Install dependencies
        run: pip install -e ".[dev,test]"

      - name: Lint
        run: ruff check src/ tests/

      - name: Type check
        run: mypy src/

      - name: Test
        run: pytest --cov=src --cov-report=xml tests/

      - name: Upload coverage
        uses: codecov/codecov-action@v3

  publish:
    needs: test
    if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"

      - name: Build
        run: |
          pip install build
          python -m build

      - name: Publish to PyPI
        uses: pypa/gh-action-pypi-publish@release/v1
        with:
          password: ${{ secrets.PYPI_API_TOKEN }}

A publicação automatizada no PyPI dispara quando uma tag com prefixo v é criada, garantindo que apenas versões testadas sejam disponibilizadas.


Uma estrutura profissional não é burocrática — é um investimento que reduz custos de manutenção, acelera a integração de novos membros e aumenta a confiabilidade do software. Comece com o básico (estrutura de pastas, pyproject.toml, pytest) e adicione ferramentas conforme seu projeto cresce.

Referências