chore: Remove obsolete build and GUI files, and introduce a new Docker Compose setup for microservices architecture. This change streamlines the project structure by eliminating legacy scripts and integrating a PostgreSQL database service alongside extraction and cleaning services.
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
FROM python:3.11-slim
|
||||
|
||||
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||
PYTHONUNBUFFERED=1 \
|
||||
PYTHONPATH=/app
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY requirements.txt /app/requirements.txt
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY api /app/api
|
||||
|
||||
EXPOSE 8000
|
||||
CMD ["uvicorn", "api.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||
@@ -0,0 +1,29 @@
|
||||
# IPSExtractor API Gateway
|
||||
|
||||
Gateway HTTP opcional. Esta carpeta puede vivir como repo propio: solo depende
|
||||
de HTTP para hablar con `extraction` y `cleaning`; no importa codigo interno de
|
||||
esos servicios.
|
||||
|
||||
## Estructura
|
||||
|
||||
```text
|
||||
api/ Endpoints FastAPI y proxy HTTP
|
||||
requirements.txt
|
||||
Dockerfile
|
||||
```
|
||||
|
||||
## Desarrollo local
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
uvicorn api.main:app --host 0.0.0.0 --port 8000
|
||||
```
|
||||
|
||||
## Endpoints principales
|
||||
|
||||
- `GET /health`
|
||||
- `GET /models`
|
||||
- `GET /config`
|
||||
- `ANY /extraction/{path}`
|
||||
- `ANY /cleaning/{path}`
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
"""API central opcional para enrutar a los microservicios."""
|
||||
@@ -0,0 +1,110 @@
|
||||
"""Gateway HTTP sin compartir codigo interno de los microservicios."""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
from fastapi import FastAPI, Request, Response
|
||||
|
||||
|
||||
EXTRACTION_URL = os.getenv("EXTRACTION_URL", "http://extraction:8001").rstrip("/")
|
||||
CLEANING_URL = os.getenv("CLEANING_URL", "http://cleaning:8002").rstrip("/")
|
||||
|
||||
app = FastAPI(title="IPSExtractor API Gateway", version="1.0.0")
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
async def health() -> dict[str, Any]:
|
||||
async with httpx.AsyncClient(timeout=10.0) as client:
|
||||
extraction = await _safe_health(client, EXTRACTION_URL)
|
||||
cleaning = await _safe_health(client, CLEANING_URL)
|
||||
return {
|
||||
"status": "ok" if extraction["ok"] and cleaning["ok"] else "degraded",
|
||||
"services": {
|
||||
"extraction": extraction,
|
||||
"cleaning": cleaning,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@app.get("/models")
|
||||
async def models() -> Response:
|
||||
return await _proxy("GET", f"{EXTRACTION_URL}/models")
|
||||
|
||||
|
||||
@app.get("/config")
|
||||
async def config() -> dict[str, Any]:
|
||||
async with httpx.AsyncClient(timeout=15.0) as client:
|
||||
extraction = await _safe_json(client, f"{EXTRACTION_URL}/config")
|
||||
cleaning_rules = await _safe_json(client, f"{CLEANING_URL}/rules")
|
||||
return {
|
||||
"extraction": extraction,
|
||||
"cleaning": {"rules": cleaning_rules},
|
||||
}
|
||||
|
||||
|
||||
@app.api_route("/extraction/{path:path}", methods=["GET", "POST", "PUT", "PATCH", "DELETE"])
|
||||
async def extraction_proxy(path: str, request: Request) -> Response:
|
||||
return await _proxy_request(request, f"{EXTRACTION_URL}/{path}")
|
||||
|
||||
|
||||
@app.api_route("/cleaning/{path:path}", methods=["GET", "POST", "PUT", "PATCH", "DELETE"])
|
||||
async def cleaning_proxy(path: str, request: Request) -> Response:
|
||||
return await _proxy_request(request, f"{CLEANING_URL}/{path}")
|
||||
|
||||
|
||||
async def _safe_health(client: httpx.AsyncClient, base_url: str) -> dict[str, Any]:
|
||||
try:
|
||||
response = await client.get(f"{base_url}/health")
|
||||
return {"ok": response.is_success, "status_code": response.status_code}
|
||||
except Exception as exc:
|
||||
return {"ok": False, "error": str(exc)}
|
||||
|
||||
|
||||
async def _safe_json(client: httpx.AsyncClient, url: str) -> Any:
|
||||
try:
|
||||
response = await client.get(url)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except Exception as exc:
|
||||
return {"error": str(exc)}
|
||||
|
||||
|
||||
async def _proxy(method: str, url: str) -> Response:
|
||||
async with httpx.AsyncClient(timeout=60.0) as client:
|
||||
upstream = await client.request(method, url)
|
||||
return _to_response(upstream)
|
||||
|
||||
|
||||
async def _proxy_request(request: Request, url: str) -> Response:
|
||||
body = await request.body()
|
||||
headers = {
|
||||
key: value
|
||||
for key, value in request.headers.items()
|
||||
if key.lower() not in {"host", "content-length"}
|
||||
}
|
||||
async with httpx.AsyncClient(timeout=None) as client:
|
||||
upstream = await client.request(
|
||||
request.method,
|
||||
url,
|
||||
params=request.query_params,
|
||||
content=body,
|
||||
headers=headers,
|
||||
)
|
||||
return _to_response(upstream)
|
||||
|
||||
|
||||
def _to_response(upstream: httpx.Response) -> Response:
|
||||
excluded = {"content-encoding", "transfer-encoding", "connection"}
|
||||
headers = {
|
||||
key: value
|
||||
for key, value in upstream.headers.items()
|
||||
if key.lower() not in excluded
|
||||
}
|
||||
return Response(
|
||||
content=upstream.content,
|
||||
status_code=upstream.status_code,
|
||||
headers=headers,
|
||||
media_type=upstream.headers.get("content-type"),
|
||||
)
|
||||
@@ -0,0 +1,3 @@
|
||||
fastapi==0.115.6
|
||||
httpx==0.28.1
|
||||
uvicorn[standard]==0.32.1
|
||||
Reference in New Issue
Block a user