Saltar a contenido

FastAPI, Peewee y Building Blocks

Con FastAPI y Peewee se vuelve bastante simple implementar los patrones tácticos requeridos en este proyecto.

1. Entidad (Entity)

Las Entidades representan objetos del dominio que poseen una identidad única. En el contexto de Peewee, las entidades se definen como modelos que se conectan a una tabla en la base de datos.

Ejemplo:

from peewee import Model, CharField, SqliteDatabase

# Conexión con la base de datos
db = SqliteDatabase('users.db')

class User(Model):
    name = CharField()
    email = CharField(unique=True)

    class Meta:
        database = db  # Define la base de datos asociada al modelo

2. Repositorio (Repository)

El Repositorio actúa como un intermediario entre la aplicación y la capa de persistencia de datos. Proporciona métodos para manipular las entidades.

Ejemplo:

class UserRepository:
    @staticmethod
    def create_user(name: str, email: str) -> User:
        user = User.create(name=name, email=email)
        return user

    @staticmethod
    def get_user(user_id: int) -> User:
        return User.get_or_none(User.id == user_id)

    @staticmethod
    def get_all_users() -> list:
        return list(User.select())

    @staticmethod
    def delete_user(user_id: int) -> None:
        user = User.get_or_none(User.id == user_id)
        if user:
            user.delete_instance()

3. Servicio (Service)

El Servicio encapsula la lógica de negocio de la aplicación, orquestando las interacciones entre las entidades y los repositorios.

Ejemplo:

class UserService:
    def __init__(self, user_repository: UserRepository):
        self.user_repository = user_repository

    def create_user(self, name: str, email: str) -> User:
        return self.user_repository.create_user(name, email)

    def get_user(self, user_id: int) -> User:
        return self.user_repository.get_user(user_id)

    def get_all_users(self) -> list:
        return self.user_repository.get_all_users()

    def delete_user(self, user_id: int) -> None:
        self.user_repository.delete_user(user_id)

4. Controlador (Controller)

El Controlador maneja las solicitudes del usuario, coordinando la interacción entre la interfaz de usuario y los servicios. FastAPI facilita la creación de APIs con rutas y manipulación de datos.

Ejemplo:

from fastapi import FastAPI, HTTPException

app = FastAPI()
user_repository = UserRepository()
user_service = UserService(user_repository)

@app.post("/users/")
def create_user(name: str, email: str):
    user = user_service.create_user(name, email)
    return {"id": user.id, "name": user.name, "email": user.email}

@app.get("/users/{user_id}")
def get_user(user_id: int):
    user = user_service.get_user(user_id)
    if user:
        return {"id": user.id, "name": user.name, "email": user.email}
    raise HTTPException(status_code=404, detail="User not found")

@app.get("/users/")
def get_all_users():
    users = user_service.get_all_users()
    return [{"id": user.id, "name": user.name, "email": user.email} for user in users]

@app.delete("/users/{user_id}")
def delete_user(user_id: int):
    user_service.delete_user(user_id)
    return {"message": "User deleted successfully"}

5. TDD

Vamos a usar el TestClient proporcionado por FastAPI. Este enfoque permite probar los endpoints de FastAPI de forma integrada, validando la funcionalidad de la aplicación sin la necesidad de mocks, además de garantizar que el comportamiento del sistema sea el esperado.

import pytest
from fastapi.testclient import TestClient
from your_module import app, UserRepository, UserService, User  # Ajusta la importación según sea necesario

# Inicialización del TestClient
client = TestClient(app)

# Suponiendo que tienes un UserRepository y UserService apropiados
@pytest.fixture(scope="module")
def setup_users():
    # Limpieza y configuración inicial del repositorio de usuarios
    user_repository = UserRepository()
    user_service = UserService(user_repository)

    # Agregando usuarios para la prueba
    user_service.create_user("Alice", "alice@example.com")
    user_service.create_user("Bob", "bob@example.com")

    yield  # Permite que las pruebas se ejecuten

    # Limpieza después de las pruebas
    user_repository.clear()  # Supón que tienes un método para limpiar los datos

def test_create_user():
    response = client.post("/users/", json={"name": "Charlie", "email": "charlie@example.com"})
    assert response.status_code == 200
    data = response.json()
    assert "id" in data
    assert data["name"] == "Charlie"
    assert data["email"] == "charlie@example.com"

def test_get_user(setup_users):
    response = client.get("/users/1")  # Asume que el ID del primer usuario es 1
    assert response.status_code == 200
    data = response.json()
    assert data["name"] == "Alice"
    assert data["email"] == "alice@example.com"

def test_get_user_not_found():
    response = client.get("/users/999")  # ID que no existe
    assert response.status_code == 404
    assert response.json() == {"detail": "User not found"}

def test_get_all_users(setup_users):
    response = client.get("/users/")
    assert response.status_code == 200
    data = response.json()
    assert len(data) == 2  # Se espera que haya 2 usuarios creados en el setup
    assert data[0]["name"] == "Alice"
    assert data[1]["name"] == "Bob"

def test_delete_user(setup_users):
    response = client.delete("/users/1")  # Eliminar el usuario con ID 1
    assert response.status_code == 200
    assert response.json() == {"message": "User deleted successfully"}

    # Verificar si el usuario fue realmente eliminado
    response = client.get("/users/1")
    assert response.status_code == 404