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.txtFramework: 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 = trueTesting: pytest
# Instalar
pip install pytest pytest-asyncio pytest-cov
# Ejecutar tests
pytest tests/ -v
# Con coverage
pytest tests/ -v --cov=app --cov-report=term-missingEstructura 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()