Python Standards

Estándares y convenciones para desarrollo Python

Versión y Setup

  • Versión: Python 3.10+ (preferido 3.11)
  • Gestor de entornos: venv (nativo)
  • Gestor de dependencias: pip con requirements.txt
# Crear entorno
python3 -m venv venv
source venv/bin/activate

# Instalar dependencias
pip install -r requirements.txt

# Desarrollo
pip install -r requirements-test.txt

Framework: FastAPI

FastAPI es el framework estándar para APIs.

Estructura de Proyecto

app/
├── main.py              # Entry point, FastAPI app
├── api/
│   └── v1/
│       ├── __init__.py
│       ├── routes.py    # Routers
│       └── endpoints/   # Endpoints por dominio
├── models/              # SQLAlchemy models
├── schemas/             # Pydantic schemas
├── services/            # Business logic
├── core/
│   ├── config.py        # Settings
│   └── database.py      # DB connection
└── __init__.py

Entry Point (main.py)

from fastapi import FastAPI
from app.api.v1 import routes
from app.core.config import settings

app = FastAPI(
    title=settings.APP_NAME,
    version=settings.VERSION,
)

# Health checks obligatorios
@app.get("/healthz")
def healthz():
    return {"ok": True}

@app.get("/readyz")
def readyz():
    return {"ok": True}

@app.get("/version")
def version():
    return {"name": settings.APP_NAME, "sha": settings.GIT_SHA}

# Routers
app.include_router(routes.router, prefix="/api/v1")

Linting: Ruff

Ruff reemplaza flake8, isort, y otros linters.

# Instalar
pip install ruff

# Lint
ruff check .

# Fix automático
ruff check --fix .

pyproject.toml

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

[tool.ruff.lint]
select = ["E", "F", "I", "UP", "B"]
ignore = ["E501"]  # line too long (handled by formatter)

Formatting: Black

# Instalar
pip install black

# Formatear
black .

# Check sin modificar
black --check .

pyproject.toml

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

Type Hints

Obligatorios en todas las funciones públicas.

# ✓ Correcto
def get_user(user_id: int) -> User | None:
    ...

async def create_item(item: ItemCreate) -> Item:
    ...

# ✗ Incorrecto
def get_user(user_id):
    ...

Type Checking: mypy

pip install mypy
mypy app/

pyproject.toml

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

Testing: pytest

# Instalar
pip install pytest pytest-asyncio pytest-cov

# Ejecutar tests
pytest tests/ -v

# Con coverage
pytest tests/ -v --cov=app --cov-report=term-missing

Estructura de Tests

tests/
├── conftest.py          # Fixtures compartidos
├── test_main.py         # Tests de endpoints
├── test_services.py     # Tests de lógica
└── test_models.py       # Tests de modelos

Ejemplo de Test

import pytest
from httpx import AsyncClient
from app.main import app

@pytest.mark.asyncio
async def test_healthz():
    async with AsyncClient(app=app, base_url="http://test") as client:
        response = await client.get("/healthz")
    assert response.status_code == 200
    assert response.json() == {"ok": True}

Dependencias

requirements.txt (Producción)

fastapi>=0.104.0
uvicorn[standard]>=0.24.0
sqlalchemy>=2.0.0
pydantic>=2.0.0
python-dotenv>=1.0.0

requirements-test.txt (Desarrollo)

-r requirements.txt
pytest>=7.0.0
pytest-asyncio>=0.21.0
pytest-cov>=4.0.0
httpx>=0.25.0
ruff>=0.1.0
black>=23.0.0
mypy>=1.0.0

Pydantic Settings

# app/core/config.py
from pydantic_settings import BaseSettings

class Settings(BaseSettings):
    APP_NAME: str = "mi-proyecto"
    APP_ENV: str = "development"
    HOST: str = "0.0.0.0"
    PORT: int = 8105
    DATABASE_URL: str = ""
    GIT_SHA: str = "dev"

    class Config:
        env_file = ".env"

settings = Settings()