Verificando autenticación…

Saltar al contenido principal

Template FastAPI

Propósito y Alcance

Este proyecto sirve como un template para desarrollar aplicaciones FastAPI (Python) siguiendo los principios de la Arquitectura Hexagonal (Puertos y Adaptadores). El objetivo es promover una estructura de código desacoplada, modular y mantenible, facilitando la evolución y prueba de las aplicaciones.

Problema que Resuelve: Proporciona una base estructurada para construir servicios backend en Python, separando claramente la lógica de negocio de las preocupaciones de infraestructura (bases de datos, mensajería, APIs externas).

Alcance:

  • Implementación de API REST con FastAPI.
  • Estructura de proyecto basada en Arquitectura Hexagonal (Dominio, Aplicación, Infraestructura).
  • Integración opcional con:
    • Base de datos PostgreSQL (con alternativa en memoria).
    • Mensajería con Apache Kafka.
    • Mensajería con Google Cloud Pub/Sub.
  • Configuración para ejecución local y despliegue en GKE y Cloud Run.
  • Ejemplos de pruebas unitarias.
  • Mecanismo básico de autenticación.

Arquitectura

El template sigue una Arquitectura Hexagonal (o de Puertos y Adaptadores), que organiza el código en tres capas principales:

  1. Dominio (<modulo>/domain):

    • Contiene los modelos de dominio puros, lógica de negocio central y las interfaces (puertos) para los repositorios y servicios.
    • No depende de ninguna otra capa.
    • Ejemplos: app/main/user/domain/model/user.py, app/main/user/domain/repositories/user_repository.py (interface).
  2. Aplicación (<modulo>/application):

    • Orquesta los casos de uso de la aplicación.
    • Implementa las interfaces de servicio definidas en el dominio.
    • Utiliza los puertos (interfaces de repositorio) para interactuar con la infraestructura.
    • Contiene DTOs (Data Transfer Objects) y lógica de aplicación específica.
    • Ejemplos: app/main/user/application/user_service_impl.py, app/main/user/application/dtos/request_create_dto.py.
  3. Infraestructura (<modulo>/infrastructure):

    • Contiene las implementaciones concretas de los puertos (adaptadores) y toda la lógica relacionada con tecnologías externas.
    • Adaptadores de Entrada (Input Adapters): Exponen la aplicación al mundo exterior (p.ej., controladores API, consumidores de Kafka/PubSub).
      • Ejemplos: app/main/user/infrastructure/adapter/input/user_controller.py, app/main/user/infrastructure/adapter/input/kafka_receiver.py.
    • Adaptadores de Salida (Output Adapters): Implementan la interacción con servicios externos (p.ej., repositorios de base de datos, publicadores de Kafka/PubSub).
      • Ejemplos: app/main/user/infrastructure/adapter/output/pg_user_storage.py, app/main/message/infrastructure/kafka.py.

Estructura de Directorios (Ejemplo para módulo user):

main/
└── user/
├── application/
│ ├── config/ # Configuración específica del módulo (ej. conexión DB)
│ ├── dtos/ # Data Transfer Objects
│ ├── entities/ # Entidades de persistencia (si aplica)
│ └── user_service_impl.py # Implementación del caso de uso
├── domain/
│ ├── model/ # Modelos de dominio
│ ├── repositories/ # Interfaces de repositorio (puertos)
│ └── services/ # Interfaces de servicio (puertos)
└── infrastructure/
└── adapter/
├── input/
│ ├── api/ # Controladores API (FastAPI endpoints)
│ └── kafka_receiver.py # Consumidor Kafka
│ └── pubsub_receiver.py# Consumidor PubSub
└── output/
├── pg_user_storage.py # Implementación repositorio PostgreSQL
└── in_memory_user_repository.py # Implementación repositorio en memoria

Tecnologías y Dependencias

  • Lenguaje: Python (>=3.11)
  • Framework API: FastAPI
  • Servidor ASGI: Uvicorn
  • Servidor WSGI/ASGI (Producción): Gunicorn
  • Gestión de Dependencias: Pipenv, pip
  • Base de Datos (Opcional): PostgreSQL (con SQLAlchemy como ORM)
  • Mensajería (Opcional):
    • Apache Kafka (con confluent-kafka)
    • Google Cloud Pub/Sub (con google-cloud-pubsub)
  • Pruebas: Pytest, Coverage.py
  • Autenticación: Google OAuth2 Client (para validación de tokens)
  • Contenerización: Docker
  • CI/CD: Jenkins, GitLab CI
  • Orquestación (Despliegue): Google Kubernetes Engine (GKE), Google Cloud Run

Listado de dependencias principales (ver requirements.txt para la lista completa):

fastapi
uvicorn
gunicorn
pipenv
# Para PostgreSQL
sqlalchemy
psycopg2-binary
# Para Kafka
confluent-kafka
# Para Pub/Sub
google-cloud-pubsub
# Para Auth
google-auth
google-api-python-client
# Para Pruebas
pytest
coverage

Configuración Local

Prerrequisitos

  • Python 3.11 o superior.
  • pip instalado.
  • IDE recomendado: Visual Studio Code.
  • (Opcional) Docker y Docker Compose para dependencias como PostgreSQL, Kafka.
  • (Opcional) Acceso a servicios GCP si se usan Pub/Sub o Kafka en Confluent Cloud y se quiere probar contra ellos.

Pasos de Configuración

  1. Clonar el repositorio: Sigue los siguientes pasos

  2. Instalar Pipenv:

    pip install pipenv
  3. Activar el entorno virtual:

    pipenv shell

    Asegúrate de que tu IDE (VS Code) utilice este entorno virtual. Usualmente se encuentra en ~/.virtualenvs/template_fast_api-XXXXXX.

  4. Instalar dependencias del proyecto:

    pipenv install -r requirements.txt

    Esto instalará las dependencias listadas en requirements.txt y creará/actualizará Pipfile y Pipfile.lock.

  5. Crear archivo .env: En la raíz del proyecto, crea un archivo .env con la siguiente estructura básica. Descomenta y configura las secciones según los componentes que vayas a utilizar.

    ENVIRONMENT=dev
    DEBUG=true # o false

    # Configuración Base de Datos (PostgreSQL) - Opcional
    DATABASE_USERNAME=postgres
    DATABASE_PASSWORD=postgres
    DATABASE_NAME=hexa
    DATABASE_HOST=localhost
    DATABASE_PORT="5432"
    SCHEMA_NAME=public

    # Configuración Kafka (Confluent Cloud o Local) - Opcional
    # Ejemplo Confluent Cloud:
    BOOTSTRAP_SERVER=pkc-xxxx.xxxx.gcp.confluent.cloud:9092
    CLUSTER_API_KEY=TU_KAFKA_API_KEY
    CLUSTER_API_SECRET=TU_KAFKA_API_SECRET
    # Ejemplo Local:
    # BOOTSTRAP_SERVER=localhost:9092
    TOPIC_RECIEVER=tu_topic_kafka_consumidor # Topic que la app consume
    TOPIC_PRODUCER=tu_topic_kafka_productor # Topic donde la app produce (configurado en kafka.py)
    RETRIES=10
    CONSUMER_GROUP_ID=tu_grupo_consumidor_kafka

    # Configuración Google Cloud Pub/Sub - Opcional
    # Usado por PubsubConsumer
    PROJECT_ID=tu-gcp-project-id
    SUBSCRIPTION_ID=tu-pubsub-subscription-id-para-consumir
    # Usado por PubsubNotifier (main.py) - Asegúrate que coincidan o ajusta según necesidad
    PUBSUB_PROJECT_ID=tu-gcp-project-id # Puede ser el mismo que PROJECT_ID
    PUBSUB_TOPIC_ID=tu-pubsub-topic-id-para-producir
    # GOOGLE_APPLICATION_CREDENTIALS=/ruta/a/tus/credenciales-gcp.json # Necesario si no usas ADC

    # Configuración Autenticación (Google OAuth2) - Opcional
    GOOGLE_CLIENT_ID=tu-google-client-id.apps.googleusercontent.com

    # Configuración Logging
    LOGGING_SEVERITY="DEBUG"

    Nota: El archivo .env está incluido en .gitignore y no debe ser versionado. Para Kafka y Pub/Sub, si no tienes instancias locales, puedes usar emuladores o servicios en la nube.

  6. Ejecutar la aplicación localmente:

    uvicorn main:app --reload --port 8080

    O usando el script principal si está configurado para uvicorn.run:

    python main.py

    La aplicación estará disponible en http://localhost:8080. La documentación Swagger UI en http://localhost:8080/api/hexa/docs.

Nuevas Dependencias

Para instalar nuevas dependencias:

pipenv install <nombre_dependencia>

Para actualizar requirements.txt a partir de Pipfile.lock (para mantener consistencia con Dockerfile u otros setups):

pipenv run pip freeze > requirements.txt

Despliegue

El proyecto está configurado para desplegarse en Google Kubernetes Engine (GKE) o Google Cloud Run.

Despliegue en GKE

  • Utiliza el Jenkinsfile con el pipeline gke-krane-python:
    pipelineLatam("gke-krane-python")
  • La configuración del despliegue (Deployment, Service, etc.) se encuentra en deploy/gke/deployment.yaml.erb.
  • Las variables de entorno específicas para cada ambiente (dev, intg, prod) se gestionan a través de ConfigMaps y Secrets en Kubernetes, populadas desde deploy/env/<ambiente>.yaml.

Despliegue en Cloud Run

  • Utiliza el .gitlab-ci.yml que incluye el componente de pipeline para Cloud Run:
    include:
    - component: $LATAM_PIPELINE_CLOUD_RUN_DEPLOY
  • La configuración del servicio de Cloud Run se encuentra en deploy/run/runtime-config.yaml.erb.
  • Las variables de entorno se definen en deploy/run/environment.yaml.erb y se basan en los archivos deploy/env/<ambiente>.yaml.

Variables de Entorno Comunes para Despliegue

Consultar los archivos en deploy/env/ para las variables específicas de cada entorno, como:

  • PROJECT_ID
  • CLOUD_RUN_REGION
  • Credenciales de base de datos (usualmente inyectadas como secrets)
  • Configuración de Kafka/PubSub (endpoints, topics, credenciales como secrets)
  • GOOGLE_CLIENT_ID para autenticación.

Endpoints de la API

La API base se encuentra en /api/hexa. Todos los endpoints del módulo user requieren un header uuid.

MétodoEndpointAutenticaciónDescripciónEjemplo Cuerpo Solicitud (JSON)Ejemplo Respuesta (Éxito)
GET/api/hexa/how-use-itNingunaDevuelve enlace a la documentación del templateN/A"https://docsrepo.appslatam.com/display/E2EDEV/Python+Template+FastAPI" (texto plano)
POST/api/hexa/user/registrationHeader uuidRegistra un nuevo usuario.{"username": "newuser", "email": "new@example.com"}{"status_code": 200, "data": "user created"}
POST/api/hexa/user/get_userHeader uuidObtiene un usuario por nombre de usuario.{"username": "testuser", "email": "ignored@example.com"}{"status_code": 200, "data": "{'username': 'testuser', 'email': 'test@example.com'}"}

Autenticación General (Opcional): El sistema incluye un validador de tokens Google OAuth2 en app/main/auth/infrastructure/auth.py. Si se activa en los endpoints (usando Depends(validate_token)), se requerirá un header Authorization: Bearer <token_id_google>. Actualmente, los endpoints de user_controller solo dependen del header uuid.

Ejemplos de Respuestas de Error para /registration y /get_user:

  • Usuario ya existe (/registration): {"status_code": 201, "data": "username already exists"}
  • Validación fallida (/registration): {"status_code": 202, "data": "username must not contain digits"}
  • Usuario no encontrado (/get_user): {"status_code": 404, "data": "user not found"}
  • Header uuid faltante/inválido: 400 Bad Request con detalle.

Componentes Específicos

Base de Datos (PostgreSQL / En Memoria)

  • Descripción: El template permite usar una base de datos PostgreSQL o una implementación en memoria para persistencia.
  • Selección: En main.py, se selecciona el repositorio a usar:

    # Para PostgreSQL
    # from app.main.user.infrastructure.adapter.output.pg_user_storage import PgUserStorage
    # repository = PgUserStorage()

    # Para En Memoria (por defecto en el template base)
    from app.main.user.infrastructure.adapter.output.in_memory_user_repository import InMemoryUserRepository
    repository = InMemoryUserRepository()

    Cambiar las líneas comentadas para elegir la implementación.

  • Configuración (PostgreSQL - Local /.env):

    DATABASE_USERNAME=postgres
    DATABASE_PASSWORD=postgres
    DATABASE_NAME=hexa
    DATABASE_HOST=localhost
    DATABASE_PORT="5432"
    SCHEMA_NAME=public
  • Implementación:

    • PostgreSQL: app/main/user/infrastructure/adapter/output/pg_user_storage.py, app/main/user/application/config/pg_connection.py, app/main/user/application/entities/user_entity.py.
    • En Memoria: app/main/user/infrastructure/adapter/output/in_memory_user_repository.py.
  • Deshabilitar PostgreSQL: Comentar la inicialización de PgUserStorage y asegurarse que se usa InMemoryUserRepository. No se necesitarían las dependencias sqlalchemy y psycopg2-binary si no se usa.

Kafka

  • Descripción: Integración con Apache Kafka para producción y consumo de mensajes.

  • Configuración (Local /.env):

    # Ejemplo Confluent Cloud:
    BOOTSTRAP_SERVER=pkc-xxxx.xxxx.gcp.confluent.cloud:9092
    CLUSTER_API_KEY=TU_KAFKA_API_KEY
    CLUSTER_API_SECRET=TU_KAFKA_API_SECRET
    # Ejemplo Local:
    # BOOTSTRAP_SERVER=localhost:9092
    TOPIC_RECIEVER=tu_topic_kafka_consumidor # Para KafkaConsumer
    # Para KafkaNotifier (hardcodeado en kafka.py, ajustar si es necesario)
    TOPIC_PRODUCER=tmpl_arq-dev-topic-test_topic_producer
    RETRIES=10 # Para productor
    CONSUMER_GROUP_ID=tu_grupo_consumidor_kafka # Para KafkaConsumer

    Las configuraciones se leen en app/main/message/application/configurations.py.

  • Implementación:

    • Productor: app/main/message/infrastructure/kafka.py (clase KafkaNotifier).
    • Consumidor: app/main/user/infrastructure/adapter/input/kafka_receiver.py (clase KafkaConsumer).
    • El consumidor se inicia en main.py durante el ciclo de vida de la aplicación. El notificador se inyecta en UserServiceUserImpl.
  • Deshabilitar:

    1. Comentar o eliminar las siguientes secciones en main.py:

      # {{ KAFKA }}
      # from app.main.message.infrastructure.kafka import KafkaNotifier
      # from app.main.user.infrastructure.adapter.input.kafka_receiver import KafkaConsumer
      # {{! KAFKA }}

      # ...
      # {{ KAFKA }}
      # notifier = KafkaNotifier() # Si Kafka es el único notificador activo
      # {{! KAFKA }}

      # ...
      # {{ KAFKA }}
      # kafka_consumer = KafkaConsumer()
      # {{! KAFKA }}

      # En app_lifespan:
      # {{ KAFKA }}
      # await asyncio.create_task(KafkaConsumer.receive_user(user_service))
      # {{! KAFKA }}
    2. Si KafkaNotifier es el notificador asignado a user_service, asignar None o otro notificador.

    3. Eliminar la dependencia confluent-kafka de requirements.txt y Pipfile.

Google Cloud Pub/Sub

  • Descripción: Integración con Google Cloud Pub/Sub para producción y consumo de mensajes.

  • Configuración (Local /.env):

    # Usado por PubsubConsumer (configurations.py)
    PROJECT_ID=tu-gcp-project-id
    SUBSCRIPTION_ID=tu-pubsub-subscription-id-para-consumir

    # Usado por PubsubNotifier (main.py)
    PUBSUB_PROJECT_ID=tu-gcp-project-id # Puede ser el mismo que PROJECT_ID
    PUBSUB_TOPIC_ID=tu-pubsub-topic-id-para-producir

    # Autenticación GCP (localmente)
    GOOGLE_APPLICATION_CREDENTIALS=/ruta/a/tus/credenciales-gcp.json

    La configuración del consumidor se lee en app/main/message/application/configurations.py. El productor se configura directamente en main.py. Nota: PUBSUB_PROJECT_ID y PUBSUB_TOPIC_ID en main.py están como "${{...}}". Para ejecución local, deben ser reemplazados por environ.get("PUBSUB_PROJECT_ID") o proporcionar valores directamente en main.py si no se usa templating.

  • Implementación:
    • Productor: app/main/message/infrastructure/pubsub.py (clase PubsubNotifier).
    • Consumidor: app/main/user/infrastructure/adapter/input/pubsub_receiver.py (clase PubsubConsumer).
    • El consumidor se inicia en main.py durante el ciclo de vida de la aplicación. El notificador se inyecta en UserServiceUserImpl.
  • Deshabilitar:

    1. Comentar o eliminar las siguientes secciones en main.py:

      # {{ PUBSUB }}
      # from app.main.message.infrastructure.pubsub import PubsubNotifier
      # from app.main.user.infrastructure.adapter.input.pubsub_receiver import PubsubConsumer
      # {{! PUBSUB }}

      # ...
      # {{ PUBSUB }}
      # notifier = PubsubNotifier(environ.get("PUBSUB_PROJECT_ID"), environ.get("PUBSUB_TOPIC_ID")) # Si Pub/Sub es el único notificador activo
      # {{! PUBSUB }}

      # ...
      # {{ PUBSUB }}
      # pubsub_consumer = PubsubConsumer()
      # {{! PUBSUB }}

      # En app_lifespan:
      # {{ PUBSUB }}
      # await asyncio.create_task(PubsubConsumer.receive_user(user_service))
      # {{! PUBSUB }}
    2. Si PubsubNotifier es el notificador asignado a user_service, asignar None o otro notificador.

    3. Eliminar la dependencia google-cloud-pubsub de requirements.txt y Pipfile.

Pruebas

El proyecto utiliza pytest para pruebas unitarias y coverage para medir la cobertura de código.

Ejecutar Pruebas Unitarias

Desde la raíz del proyecto, dentro del entorno virtual (pipenv shell):

python -m pytest -v --junitxml=reports/result.xml
  • -v: Modo verbose.
  • --junitxml=reports/result.xml: Genera un reporte en formato JUnit XML en la carpeta reports.

Validar Cobertura (Coverage)

  1. Ejecutar pruebas con coverage: Se puede integrar coverage con pytest o ejecutarlo por separado. Para ejecutar con pytest-cov (si está instalado):

    pipenv run pytest --cov=app --cov-report=xml --cov-report=html

    O usando coverage directamente:

    pipenv run coverage run -m pytest
  2. Ver reporte de cobertura en consola:

    pipenv run coverage report
  3. Generar reporte HTML de cobertura:

    pipenv run coverage html

    Esto creará una carpeta htmlcov/ con el reporte navegable.

Los archivos de prueba se encuentran en el directorio app/tests/.

Consideraciones de Seguridad

  • Gestión de Secretos:
    • Localmente: Utilizar el archivo .env para almacenar credenciales y claves sensibles. Este archivo está incluido en .gitignore y NO debe ser subido al repositorio.
    • Despliegue (GKE/Cloud Run): Los secretos (como contraseñas de base de datos, API keys de Kafka/PubSub, GOOGLE_CLIENT_ID) deben gestionarse a través de los mecanismos de secretos del entorno (ej. Kubernetes Secrets, Google Secret Manager) y ser inyectados como variables de entorno en los contenedores. Ver deploy/gke/deployment.yaml.erb para ejemplos de cómo se referencian secrets.
  • Autenticación de API:
    • Header uuid: Los endpoints del módulo user actualmente requieren un header uuid no vacío. Su propósito y validación más allá de la existencia no están especificados y deberían ser definidos según los requisitos de seguridad.
    • Token Google OAuth2: El archivo app/main/auth/infrastructure/auth.py contiene lógica para validar tokens ID de Google. Requiere la variable de entorno GOOGLE_CLIENT_ID. Esta validación puede ser agregada como una dependencia (Depends(validate_token)) a los endpoints de FastAPI que necesiten ser protegidos.
  • Archivos No Versionados: Asegúrate de no versionar los siguientes archivos y directorios que pueden contener información sensible o específica del entorno:
    • htmlcov/
    • reports/
    • .env
    • Pipfile (si contiene información sensible, aunque usualmente no lo hace)
    • Pipfile.lock (por las mismas razones que Pipfile)
    • .coverage