diff --git a/.bandit b/.bandit new file mode 100644 index 0000000..35821b7 --- /dev/null +++ b/.bandit @@ -0,0 +1,18 @@ +[bandit] +# Configuración de Bandit para NeuroBank FastAPI Toolkit + +# Excluir directorios +exclude_dirs = ["/tests", ".venv", "__pycache__"] + +# Saltar tests específicos +skips = [ + "B101", # assert_used - normal en tests + "B601", # paramiko_calls - no usamos paramiko + "B602", # subprocess_popen_with_shell_equals_true +] + +# Nivel de confianza mínimo para reportar +confidence = "MEDIUM" + +# Formateo de salida +format = "json" diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml new file mode 100644 index 0000000..e29f4c6 --- /dev/null +++ b/.github/workflows/ci-cd.yml @@ -0,0 +1,120 @@ +name: CI/CD Pipeline + +on: + push: + branches: [ main, develop ] + pull_request: + branches: [ main ] + +env: + AWS_REGION: eu-west-1 + ECR_REPOSITORY: neurobank-fastapi + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Run tests with coverage + run: | + python -m pytest --cov=app --cov-report=xml --cov-report=html + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v4 + if: always() + with: + files: ./coverage.xml + + security: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Install security tools + run: pip install bandit safety pytest-cov + + - name: Run Bandit (exclude tests from assert checking) + run: | + bandit -r app/ -f json -o bandit-report.json --skip B101 || true + echo "Bandit scan completed - check bandit-report.json for details" + + - name: Run Safety scan + run: | + pip freeze > current-requirements.txt + safety scan --json --output safety-report.json --continue-on-error || true + echo "Safety scan completed - check safety-report.json for details" + + - name: Upload security reports as artifacts + uses: actions/upload-artifact@v4 + if: always() + with: + name: security-reports + path: | + bandit-report.json + safety-report.json + + build-and-deploy: + needs: [test, security] + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/main' + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ env.AWS_REGION }} + + - name: Setup SAM CLI + uses: aws-actions/setup-sam@v2 + with: + use-installer: true + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v2 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + IMAGE_TAG: ${{ github.sha }} + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker tag $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG $ECR_REGISTRY/$ECR_REPOSITORY:latest + docker push $ECR_REGISTRY/$ECR_REPOSITORY:latest + + - name: Deploy to AWS Lambda + run: | + sam build --region ${{ env.AWS_REGION }} + sam deploy --no-confirm-changeset --no-fail-on-empty-changeset --stack-name neurobank-api --capabilities CAPABILITY_IAM --region ${{ env.AWS_REGION }} --parameter-overrides ApiKey=${{ secrets.API_KEY }} diff --git a/.gitignore b/.gitignore index b7faf40..962bef8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,207 +1,57 @@ -# Byte-compiled / optimized / DLL files +# --- Basado en la plantilla oficial de Python --- __pycache__/ -*.py[codz] +*.py[cod] *$py.class -# C extensions -*.so +# Entornos virtuales +.venv/ +venv/ +ENV/ +env/ +env.bak/ +venv.bak/ -# Distribution / packaging -.Python +# Paquetes/compilados build/ -develop-eggs/ dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -share/python-wheels/ *.egg-info/ -.installed.cfg +.eggs/ *.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py.cover -.hypothesis/ -.pytest_cache/ -cover/ - -# Translations -*.mo -*.pot +wheels/ +pip-wheel-metadata/ -# Django stuff: +# Archivos de logs *.log -local_settings.py -db.sqlite3 -db.sqlite3-journal - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -.pybuilder/ -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -# For a library or package, you might want to ignore these files since the code is -# intended to run in multiple environments; otherwise, check them in: -# .python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock -# UV -# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. -# This is especially recommended for binary packages to ensure reproducibility, and is more -# commonly ignored for libraries. -#uv.lock - -# poetry -# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. -# This is especially recommended for binary packages to ensure reproducibility, and is more -# commonly ignored for libraries. -# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control -#poetry.lock -#poetry.toml - -# pdm -# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. -# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python. -# https://pdm-project.org/en/latest/usage/project/#working-with-version-control -#pdm.lock -#pdm.toml -.pdm-python -.pdm-build/ - -# pixi -# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control. -#pixi.lock -# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one -# in the .venv directory. It is recommended not to include this directory in version control. -.pixi - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm -__pypackages__/ - -# Celery stuff -celerybeat-schedule -celerybeat.pid - -# SageMath parsed files -*.sage.py - -# Environments -.env -.envrc -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy +# Cachés de pruebas y cobertura +.pytest_cache/ +.coverage +htmlcov/ .mypy_cache/ -.dmypy.json -dmypy.json - -# Pyre type checker .pyre/ -# pytype static type analyzer -.pytype/ - -# Cython debug symbols -cython_debug/ +# Configuración de IDEs +.vscode/ +.idea/ -# PyCharm -# JetBrains specific template is maintained in a separate JetBrains.gitignore that can -# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore -# and can be added to the global gitignore or merged into this file. For a more nuclear -# option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ - -# Abstra -# Abstra is an AI-powered process automation framework. -# Ignore directories containing user credentials, local state, and settings. -# Learn more at https://abstra.io/docs -.abstra/ +# Variables de entorno (dotenv) +.env +.env.* -# Visual Studio Code -# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore -# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore -# and can be added to the global gitignore or merged into this file. However, if you prefer, -# you could uncomment the following to ignore the entire vscode folder -# .vscode/ +# Documentación generada +docs/_build/ -# Ruff stuff: -.ruff_cache/ +# Docker (si lo usas) +*.docker +docker-compose.override.yml -# PyPI configuration file -.pypirc +# Sistema operativo +.DS_Store +Thumbs.db -# Cursor -# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to -# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data -# refer to https://docs.cursor.com/context/ignore-files -.cursorignore -.cursorindexingignore +# AWS SAM +.aws-sam/ -# Marimo -marimo/_static/ -marimo/_lsp/ -__marimo__/ +# Security reports +bandit-report.json +safety-report.json diff --git a/README.md b/README.md index 49e740f..5b2f3ff 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,73 @@ -# NeuroBank-FastAPI-Toolkit +# Operator API (FASE 2) + +Backend en FastAPI para consultar estados de pedidos y generar facturas. + +## Requisitos + +- Python 3.10+ +- macOS Sonoma 14.7.6 (compatible) +- Poetry o pip + virtualenv + +## Instalación + +```bash +python -m venv .venv +source .venv/bin/activate +pip install -r requirements.txt +cp .env.example .env # Ajusta la API_KEY si lo deseas +``` + +## Ejecución + +```bash +uvicorn app.main:app --reload +# Visita http://localhost:8000/docs +``` + +## Tests + +```bash +pytest +``` + +## Despliegue + +- Contenedor Docker (opcional) +- AWS ECS/Fargate o EC2 + Systemd +- Observabilidad: CloudWatch, Prometheus + Grafana, etc. + +--- + +## Cómo levantar el proyecto + +1. **Crear entorno virtual** + ```bash + python -m venv .venv + source .venv/bin/activate + ``` + +2. **Instalar dependencias** + ```bash + pip install -r requirements.txt + ``` + +3. **Configurar .env** + ```bash + cp .env.example .env + # Edita API_KEY si lo deseas + ``` + +4. **Ejecutar servidor** + ```bash + uvicorn app.main:app --reload + ``` + +5. **Probar endpoints** (con la API Key en cabecera X-API-Key) + - `GET /operator/order_status/123` + - `POST /operator/generate_invoice` con body `{"order_id": "123"}` + +6. **Ejecutar tests** + ```bash + pytest + ```NeuroBank-FastAPI-Toolkit Senior‑grade FastAPI microservice blueprint for AI‑driven banking. Python 3.10+, Pydantic v2, Docker & AWS stack (Lambda, AppRunner, CloudWatch, X‑Ray) with CI/CD via GitHub Actions. Incluye clean code, tests completos, observabilidad y módulos listos para estado de pedidos, facturación y analítica. diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..eaf0262 --- /dev/null +++ b/app/__init__.py @@ -0,0 +1 @@ +# NeuroBank FastAPI Toolkit diff --git a/app/auth/__init__.py b/app/auth/__init__.py new file mode 100644 index 0000000..b3163d3 --- /dev/null +++ b/app/auth/__init__.py @@ -0,0 +1 @@ +# Authentication utilities diff --git a/app/auth/dependencies.py b/app/auth/dependencies.py new file mode 100644 index 0000000..0786817 --- /dev/null +++ b/app/auth/dependencies.py @@ -0,0 +1,41 @@ +from fastapi import HTTPException, Depends +from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials +import os +from typing import Optional + +# Configuración del esquema de seguridad +security = HTTPBearer() + +def get_api_key() -> str: + """Obtiene la API key desde las variables de entorno""" + api_key = os.getenv("API_KEY") + if not api_key: + raise HTTPException( + status_code=500, + detail="API_KEY not configured" + ) + return api_key + +async def verify_api_key(credentials: HTTPAuthorizationCredentials = Depends(security)) -> str: + """ + Verifica que la API key proporcionada sea válida + + Args: + credentials: Credenciales HTTP Bearer + + Returns: + str: API key válida + + Raises: + HTTPException: Si la API key no es válida + """ + expected_api_key = get_api_key() + + if credentials.credentials != expected_api_key: + raise HTTPException( + status_code=401, + detail="Invalid API key", + headers={"WWW-Authenticate": "Bearer"} + ) + + return credentials.credentials diff --git a/app/config.py b/app/config.py new file mode 100644 index 0000000..afc8c53 --- /dev/null +++ b/app/config.py @@ -0,0 +1,29 @@ +import os +from functools import lru_cache +from pydantic import BaseSettings + +class Settings(BaseSettings): + """Configuración de la aplicación""" + + # API Configuration + api_key: str = os.getenv("API_KEY", "default-test-key") + app_name: str = "NeuroBank FastAPI Toolkit" + app_version: str = "1.0.0" + + # Server Configuration + host: str = "0.0.0.0" + port: int = 8000 + + # AWS Configuration + aws_region: str = os.getenv("AWS_REGION", "eu-west-1") + + # Logging Configuration + log_level: str = os.getenv("LOG_LEVEL", "INFO") + + class Config: + env_file = ".env" + +@lru_cache() +def get_settings() -> Settings: + """Obtiene la configuración de la aplicación (cached)""" + return Settings() diff --git a/app/main.py b/app/main.py new file mode 100644 index 0000000..68dd558 --- /dev/null +++ b/app/main.py @@ -0,0 +1,63 @@ +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware +from fastapi.responses import JSONResponse +import logging +from .routers import operator +from .utils.logging import setup_logging + +# Configuración constantes +APP_NAME = "NeuroBank FastAPI Toolkit" +APP_VERSION = "1.0.0" + +# Configurar logging +setup_logging() +logger = logging.getLogger(__name__) + +# Crear la aplicación FastAPI +app = FastAPI( + title=APP_NAME, + description="API toolkit for banking operations", + version=APP_VERSION, + docs_url="/docs", + redoc_url="/redoc" +) + +# Configurar CORS +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], # En producción, especificar dominios exactos + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +# Incluir routers +app.include_router(operator.router, prefix="/operator", tags=["operator"]) + +# Health check endpoint +@app.get("/health") +async def health_check(): + """Endpoint de health check""" + return JSONResponse( + status_code=200, + content={ + "status": "healthy", + "service": APP_NAME, + "version": APP_VERSION + } + ) + +# Root endpoint +@app.get("/") +async def root(): + """Endpoint raíz con información básica""" + return { + "message": APP_NAME, + "version": APP_VERSION, + "docs": "/docs", + "health": "/health" + } + +if __name__ == "__main__": + import uvicorn + uvicorn.run(app, host="0.0.0.0", port=8000) diff --git a/app/routers/__init__.py b/app/routers/__init__.py new file mode 100644 index 0000000..df5374a --- /dev/null +++ b/app/routers/__init__.py @@ -0,0 +1 @@ +# API routers diff --git a/app/routers/operator.py b/app/routers/operator.py new file mode 100644 index 0000000..ca26f8b --- /dev/null +++ b/app/routers/operator.py @@ -0,0 +1,41 @@ +from fastapi import APIRouter, Depends, HTTPException, status +from pydantic import BaseModel +from ..services.order_service import get_order_status +from ..services.invoice_service import generate_invoice +from ..auth.dependencies import verify_api_key + +router = APIRouter() + +# ----- Modelos Pydantic ----- +class OrderStatusResponse(BaseModel): + order_id: str + status: str + carrier: str + eta: str + +class InvoiceRequest(BaseModel): + order_id: str + +class InvoiceResponse(BaseModel): + invoice_id: str + order_id: str + amount: float + currency: str + issued_at: str + +# ----- Endpoints ----- +@router.get( + "/order_status/{order_id}", + response_model=OrderStatusResponse, + dependencies=[Depends(verify_api_key)] +) +async def order_status(order_id: str): + return get_order_status(order_id) + +@router.post( + "/generate_invoice", + response_model=InvoiceResponse, + dependencies=[Depends(verify_api_key)] +) +async def invoice(data: InvoiceRequest): + return generate_invoice(data.order_id) diff --git a/app/services/__init__.py b/app/services/__init__.py new file mode 100644 index 0000000..0f92eb3 --- /dev/null +++ b/app/services/__init__.py @@ -0,0 +1 @@ +# Business services diff --git a/app/services/invoice_service.py b/app/services/invoice_service.py new file mode 100644 index 0000000..1df266f --- /dev/null +++ b/app/services/invoice_service.py @@ -0,0 +1,13 @@ +def generate_invoice(order_id: str) -> dict: + """ + Genera la factura de un pedido. + Por ahora es mock; persiste en BBDD o S3 cuando lo implementes. + """ + # TODO: Lógica real + return { + "invoice_id": "INV-2025-0001", + "order_id": order_id, + "amount": 149.99, + "currency": "EUR", + "issued_at": "2025-07-20" + } diff --git a/app/services/order_service.py b/app/services/order_service.py new file mode 100644 index 0000000..d6cce67 --- /dev/null +++ b/app/services/order_service.py @@ -0,0 +1,12 @@ +def get_order_status(order_id: str) -> dict: + """ + Obtiene el estado de un pedido. + Por ahora es mock; conecta aquí tu BBDD o servicio externo. + """ + # TODO: Lógica real + return { + "order_id": order_id, + "status": "En tránsito", + "carrier": "Correos Express", + "eta": "2025-07-25" + } diff --git a/app/tests/__init__.py b/app/tests/__init__.py new file mode 100644 index 0000000..374728e --- /dev/null +++ b/app/tests/__init__.py @@ -0,0 +1 @@ +# Test modules diff --git a/app/tests/test_main.py b/app/tests/test_main.py new file mode 100644 index 0000000..e3fcbc3 --- /dev/null +++ b/app/tests/test_main.py @@ -0,0 +1,28 @@ +import pytest +from httpx import AsyncClient +from app.main import app + +@pytest.mark.asyncio +async def test_health_check(): + """Test del endpoint de health check""" + async with AsyncClient(app=app, base_url="http://test") as ac: + response = await ac.get("/health") + + assert response.status_code == 200 + data = response.json() + assert data["status"] == "healthy" + assert "service" in data + assert "version" in data + +@pytest.mark.asyncio +async def test_root_endpoint(): + """Test del endpoint raíz""" + async with AsyncClient(app=app, base_url="http://test") as ac: + response = await ac.get("/") + + assert response.status_code == 200 + data = response.json() + assert "message" in data + assert "version" in data + assert data["docs"] == "/docs" + assert data["health"] == "/health" diff --git a/app/tests/test_operator.py b/app/tests/test_operator.py new file mode 100644 index 0000000..8ddb538 --- /dev/null +++ b/app/tests/test_operator.py @@ -0,0 +1,31 @@ +import pytest +import os +from httpx import AsyncClient +from app.main import app + +# Configurar API key para tests +API_KEY = "test-api-key" +os.environ["API_KEY"] = API_KEY + +@pytest.mark.asyncio +async def test_order_status(): + async with AsyncClient(app=app, base_url="http://test") as ac: + resp = await ac.get( + "/operator/order_status/123", + headers={"Authorization": f"Bearer {API_KEY}"} + ) + assert resp.status_code == 200 + data = resp.json() + assert data["order_id"] == "123" + +@pytest.mark.asyncio +async def test_generate_invoice(): + async with AsyncClient(app=app, base_url="http://test") as ac: + resp = await ac.post( + "/operator/generate_invoice", + json={"order_id": "123"}, + headers={"Authorization": f"Bearer {API_KEY}"} + ) + assert resp.status_code == 200 + data = resp.json() + assert data["order_id"] == "123" diff --git a/app/utils/__init__.py b/app/utils/__init__.py new file mode 100644 index 0000000..80f423e --- /dev/null +++ b/app/utils/__init__.py @@ -0,0 +1 @@ +# Utility functions diff --git a/app/utils/logging.py b/app/utils/logging.py new file mode 100644 index 0000000..414e4ff --- /dev/null +++ b/app/utils/logging.py @@ -0,0 +1,30 @@ +import logging +import sys +from pythonjsonlogger import jsonlogger + +def setup_logging(): + """Configura el sistema de logging para la aplicación""" + + # Crear formateador JSON + formatter = jsonlogger.JsonFormatter( + fmt='%(asctime)s %(name)s %(levelname)s %(message)s' + ) + + # Configurar handler para stdout + handler = logging.StreamHandler(sys.stdout) + handler.setFormatter(formatter) + + # Configurar logger raíz + root_logger = logging.getLogger() + root_logger.setLevel(logging.INFO) + root_logger.addHandler(handler) + + # Configurar logger específico para uvicorn + uvicorn_logger = logging.getLogger("uvicorn") + uvicorn_logger.setLevel(logging.INFO) + + return root_logger + +def get_logger(name: str) -> logging.Logger: + """Obtiene un logger configurado para el módulo especificado""" + return logging.getLogger(name) \ No newline at end of file diff --git a/lambda_function.py b/lambda_function.py new file mode 100644 index 0000000..3016bd2 --- /dev/null +++ b/lambda_function.py @@ -0,0 +1,5 @@ +from mangum import Mangum +from app.main import app + +# Handler para AWS Lambda +lambda_handler = Mangum(app) diff --git a/neurobank-fastapi.code-workspace b/neurobank-fastapi.code-workspace new file mode 100644 index 0000000..443f5a5 --- /dev/null +++ b/neurobank-fastapi.code-workspace @@ -0,0 +1,7 @@ +{ + "folders": [ + { + "path": "." + } + ] +} \ No newline at end of file diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..28d8366 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,7 @@ +[pytest] +testpaths = app/tests +python_files = test_*.py +python_classes = Test* +python_functions = test_* +pythonpath = . +asyncio_mode = auto diff --git a/requirements-for-safety.txt b/requirements-for-safety.txt new file mode 100644 index 0000000..c4e2cb5 --- /dev/null +++ b/requirements-for-safety.txt @@ -0,0 +1,89 @@ +annotated-types==0.7.0 +anyio==4.9.0 +attrs==25.3.0 +Authlib==1.6.0 +aws-sam-translator==1.99.0 +aws-xray-sdk==2.14.0 +bandit==1.8.6 +boto3==1.39.9 +botocore==1.39.9 +certifi==2025.7.14 +cffi==1.17.1 +cfn-lint==1.38.0 +charset-normalizer==3.4.2 +click==8.2.1 +cryptography==45.0.5 +dnspython==2.7.0 +dparse==0.6.4 +email_validator==2.2.0 +fastapi==0.115.6 +fastapi-cli==0.0.8 +filelock==3.16.1 +h11==0.16.0 +httpcore==1.0.9 +httptools==0.6.4 +httpx==0.27.0 +idna==3.10 +iniconfig==2.1.0 +Jinja2==3.1.6 +jmespath==1.0.1 +joblib==1.5.1 +jsonpatch==1.33 +jsonpointer==3.0.0 +jsonschema==4.25.0 +jsonschema-specifications==2025.4.1 +loguru==0.7.2 +mangum==0.19.0 +markdown-it-py==3.0.0 +MarkupSafe==3.0.2 +marshmallow==4.0.0 +mdurl==0.1.2 +mpmath==1.3.0 +networkx==3.5 +nltk==3.9.1 +orjson==3.11.0 +packaging==25.0 +pbr==6.1.1 +pluggy==1.6.0 +psutil==6.1.1 +pycparser==2.22 +pydantic==2.7.0 +pydantic_core==2.18.1 +Pygments==2.19.2 +pytest==8.2.0 +pytest-asyncio==1.1.0 +python-dateutil==2.9.0.post0 +python-dotenv==1.0.1 +python-multipart==0.0.20 +PyYAML==6.0.2 +referencing==0.36.2 +regex==2024.11.6 +requests==2.32.4 +rich==14.0.0 +rich-toolkit==0.14.8 +rpds-py==0.26.0 +ruamel.yaml==0.18.14 +ruamel.yaml.clib==0.2.12 +s3transfer==0.13.1 +safety==3.6.0 +safety-schemas==0.0.14 +setuptools==80.9.0 +shellingham==1.5.4 +six==1.17.0 +sniffio==1.3.1 +starlette==0.41.3 +stevedore==5.4.1 +sympy==1.14.0 +tenacity==9.1.2 +tomlkit==0.13.3 +tqdm==4.67.1 +typer==0.16.0 +typing_extensions==4.14.1 +ujson==5.10.0 +urllib3==2.5.0 +uvicorn==0.29.0 +uvloop==0.21.0 +watchfiles==1.1.0 +watchtower==3.4.0 +websockets==15.0.1 +wrapt==1.17.2 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..b717715 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,14 @@ +fastapi==0.115.6 +uvicorn[standard]==0.29.0 +pydantic==2.7.0 +python-dotenv==1.0.1 +loguru==0.7.2 +pytest==8.2.0 +pytest-asyncio==0.23.6 +pytest-cov==5.0.0 +httpx==0.27.0 +watchtower==3.0.0 +aws-xray-sdk==2.13.0 +mangum==0.17.0 +starlette==0.41.3 +python-json-logger==2.0.7