feat(api): add validator, FastAPI app structure, and health endpoint
Wave 3 tasks complete:
- Task 7: Validator with 4 checks (pflichtfelder, betraege, ustid, pdf_abgleich)
- Task 8: FastAPI app with CORS, exception handlers, JSON logging
- Task 9: Health endpoint returning status and version
Features:
- validate_invoice() runs selected validation checks
- Exception handlers for ExtractionError and generic errors
- GET /health returns {status: healthy, version: 1.0.0}
Tests: 52 validator tests covering all validation rules
This commit is contained in:
74
src/main.py
74
src/main.py
@@ -1,7 +1,37 @@
|
||||
"""FastAPI application for ZUGFeRD invoice processing."""
|
||||
|
||||
import json
|
||||
import logging
|
||||
from datetime import datetime
|
||||
|
||||
import uvicorn
|
||||
from fastapi import FastAPI
|
||||
from fastapi import FastAPI, HTTPException, Request
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
from src.extractor import ExtractionError
|
||||
from src.models import HealthResponse
|
||||
|
||||
|
||||
class JSONFormatter(logging.Formatter):
|
||||
def format(self, record):
|
||||
log_data = {
|
||||
"timestamp": datetime.utcnow().isoformat() + "Z",
|
||||
"level": record.levelname,
|
||||
"message": record.getMessage(),
|
||||
}
|
||||
if hasattr(record, "data"):
|
||||
log_data["data"] = record.data
|
||||
return json.dumps(log_data)
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
if not logger.handlers:
|
||||
handler = logging.StreamHandler()
|
||||
handler.setFormatter(JSONFormatter())
|
||||
logger.addHandler(handler)
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
|
||||
app = FastAPI(
|
||||
title="ZUGFeRD Service",
|
||||
@@ -9,6 +39,48 @@ app = FastAPI(
|
||||
description="REST API for ZUGFeRD invoice extraction and validation",
|
||||
)
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
|
||||
@app.exception_handler(ExtractionError)
|
||||
async def extraction_error_handler(request: Request, exc: ExtractionError):
|
||||
return JSONResponse(
|
||||
status_code=400,
|
||||
content={
|
||||
"error": exc.error_code,
|
||||
"message": exc.message,
|
||||
"details": exc.details,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@app.exception_handler(Exception)
|
||||
async def generic_error_handler(request: Request, exc: Exception):
|
||||
logger.error(f"Internal error: {exc}")
|
||||
return JSONResponse(
|
||||
status_code=500,
|
||||
content={
|
||||
"error": "internal_error",
|
||||
"message": "An internal error occurred",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@app.get("/health", response_model=HealthResponse)
|
||||
async def health_check() -> HealthResponse:
|
||||
"""Health check endpoint.
|
||||
|
||||
Returns:
|
||||
HealthResponse with status and version.
|
||||
"""
|
||||
return HealthResponse(status="healthy", version="1.0.0")
|
||||
|
||||
|
||||
def run(host: str = "0.0.0.0", port: int = 5000) -> None:
|
||||
"""Run the FastAPI application.
|
||||
|
||||
Reference in New Issue
Block a user