Linting and formatting

This commit is contained in:
Jonas Linter
2025-10-07 09:46:44 +02:00
parent b4b7a537e1
commit f0945ed431
21 changed files with 930 additions and 945 deletions

View File

@@ -1,62 +1,57 @@
from fastapi import (
FastAPI,
HTTPException,
BackgroundTasks,
Request,
Depends,
APIRouter,
Form,
File,
UploadFile,
)
from fastapi.concurrency import asynccontextmanager
from fastapi.middleware.cors import CORSMiddleware
from fastapi.security import HTTPBearer, HTTPBasicCredentials, HTTPBasic
from .config_loader import load_config
from fastapi.responses import HTMLResponse, PlainTextResponse, Response
from .models import WixFormSubmission
from datetime import datetime, date, timezone
from .auth import (
generate_unique_id,
validate_api_key,
validate_wix_signature,
generate_api_key,
)
from .rate_limit import (
limiter,
webhook_limiter,
custom_rate_limit_handler,
DEFAULT_RATE_LIMIT,
WEBHOOK_RATE_LIMIT,
BURST_RATE_LIMIT,
)
from slowapi.errors import RateLimitExceeded
import logging
from datetime import datetime
from typing import Dict, Any, Optional, List
import json
import os
import asyncio
import gzip
import xml.etree.ElementTree as ET
import json
import logging
import os
import urllib.parse
from datetime import UTC, date, datetime
from functools import partial
from typing import Any
import httpx
from fastapi import (
APIRouter,
Depends,
FastAPI,
HTTPException,
Request,
)
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import HTMLResponse, Response
from fastapi.security import HTTPBasic, HTTPBasicCredentials
from slowapi.errors import RateLimitExceeded
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
from .alpinebits_server import (
AlpineBitsActionName,
AlpineBitsClientInfo,
AlpineBitsServer,
Version,
AlpineBitsActionName,
)
import urllib.parse
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker
from functools import partial
import httpx
from .auth import (
generate_api_key,
generate_unique_id,
validate_api_key,
)
from .config_loader import load_config
from .db import (
Base,
Customer as DBCustomer,
Reservation as DBReservation,
get_database_url,
)
from .db import (
Customer as DBCustomer,
)
from .db import (
Reservation as DBReservation,
)
from .rate_limit import (
BURST_RATE_LIMIT,
DEFAULT_RATE_LIMIT,
WEBHOOK_RATE_LIMIT,
custom_rate_limit_handler,
limiter,
webhook_limiter,
)
# Configure logging
logging.basicConfig(level=logging.INFO)
@@ -98,8 +93,7 @@ event_dispatcher = EventDispatcher()
async def push_listener(customer: DBCustomer, reservation: DBReservation, hotel):
"""
Push listener that sends reservation data to hotel's push endpoint.
"""Push listener that sends reservation data to hotel's push endpoint.
Only called for reservations that match this hotel's hotel_id.
"""
push_endpoint = hotel.get("push_endpoint")
@@ -186,7 +180,7 @@ async def lifespan(app: FastAPI):
try:
config = load_config()
except Exception as e:
_LOGGER.error(f"Failed to load config: {str(e)}")
_LOGGER.error(f"Failed to load config: {e!s}")
config = {}
DATABASE_URL = get_database_url(config)
@@ -263,9 +257,8 @@ app.add_middleware(
)
async def process_form_submission(submission_data: Dict[str, Any]) -> None:
"""
Background task to process the form submission.
async def process_form_submission(submission_data: dict[str, Any]) -> None:
"""Background task to process the form submission.
Add your business logic here.
"""
try:
@@ -297,7 +290,7 @@ async def process_form_submission(submission_data: Dict[str, Any]) -> None:
# - Process the data further
except Exception as e:
_LOGGER.error(f"Error processing form submission: {str(e)}")
_LOGGER.error(f"Error processing form submission: {e!s}")
@api_router.get("/")
@@ -332,9 +325,8 @@ async def health_check(request: Request):
# Extracted business logic for handling Wix form submissions
async def process_wix_form_submission(request: Request, data: Dict[str, Any], db):
"""
Shared business logic for handling Wix form submissions (test and production).
async def process_wix_form_submission(request: Request, data: dict[str, Any], db):
"""Shared business logic for handling Wix form submissions (test and production).
"""
timestamp = datetime.now().isoformat()
@@ -485,7 +477,7 @@ async def process_wix_form_submission(request: Request, data: Dict[str, Any], db
num_children=num_children,
children_ages=",".join(str(a) for a in children_ages),
offer=offer,
created_at=datetime.now(timezone.utc),
created_at=datetime.now(UTC),
utm_source=data.get("field:utm_source"),
utm_medium=data.get("field:utm_medium"),
utm_campaign=data.get("field:utm_campaign"),
@@ -532,38 +524,36 @@ async def process_wix_form_submission(request: Request, data: Dict[str, Any], db
@api_router.post("/webhook/wix-form")
@webhook_limiter.limit(WEBHOOK_RATE_LIMIT)
async def handle_wix_form(
request: Request, data: Dict[str, Any], db_session=Depends(get_async_session)
request: Request, data: dict[str, Any], db_session=Depends(get_async_session)
):
"""
Unified endpoint to handle Wix form submissions (test and production).
"""Unified endpoint to handle Wix form submissions (test and production).
No authentication required for this endpoint.
"""
try:
return await process_wix_form_submission(request, data, db_session)
except Exception as e:
_LOGGER.error(f"Error in handle_wix_form: {str(e)}")
_LOGGER.error(f"Error in handle_wix_form: {e!s}")
# log stacktrace
import traceback
traceback_str = traceback.format_exc()
_LOGGER.error(f"Stack trace for handle_wix_form: {traceback_str}")
raise HTTPException(status_code=500, detail=f"Error processing Wix form data")
raise HTTPException(status_code=500, detail="Error processing Wix form data")
@api_router.post("/webhook/wix-form/test")
@limiter.limit(DEFAULT_RATE_LIMIT)
async def handle_wix_form_test(
request: Request, data: Dict[str, Any], db_session=Depends(get_async_session)
request: Request, data: dict[str, Any], db_session=Depends(get_async_session)
):
"""
Test endpoint to verify the API is working with raw JSON data.
"""Test endpoint to verify the API is working with raw JSON data.
No authentication required for testing purposes.
"""
try:
return await process_wix_form_submission(request, data, db_session)
except Exception as e:
_LOGGER.error(f"Error in handle_wix_form_test: {str(e)}")
raise HTTPException(status_code=500, detail=f"Error processing test data")
_LOGGER.error(f"Error in handle_wix_form_test: {e!s}")
raise HTTPException(status_code=500, detail="Error processing test data")
@api_router.post("/admin/generate-api-key")
@@ -571,8 +561,7 @@ async def handle_wix_form_test(
async def generate_new_api_key(
request: Request, admin_key: str = Depends(validate_api_key)
):
"""
Admin endpoint to generate new API keys.
"""Admin endpoint to generate new API keys.
Requires admin API key and is heavily rate limited.
"""
if admin_key != "admin-key":
@@ -593,8 +582,7 @@ async def generate_new_api_key(
async def validate_basic_auth(
credentials: HTTPBasicCredentials = Depends(security_basic),
) -> str:
"""
Validate basic authentication for AlpineBits protocol.
"""Validate basic authentication for AlpineBits protocol.
Returns username if valid, raises HTTPException if not.
"""
# Accept any username/password pair present in config['alpine_bits_auth']
@@ -626,9 +614,8 @@ async def validate_basic_auth(
return credentials.username, credentials.password
def parse_multipart_data(content_type: str, body: bytes) -> Dict[str, Any]:
"""
Parse multipart/form-data from raw request body.
def parse_multipart_data(content_type: str, body: bytes) -> dict[str, Any]:
"""Parse multipart/form-data from raw request body.
This is a simplified parser for the AlpineBits use case.
"""
if "multipart/form-data" not in content_type:
@@ -692,8 +679,7 @@ async def alpinebits_server_handshake(
credentials_tupel: tuple = Depends(validate_basic_auth),
dbsession=Depends(get_async_session),
):
"""
AlpineBits server endpoint implementing the handshake protocol.
"""AlpineBits server endpoint implementing the handshake protocol.
This endpoint handles:
- Protocol version negotiation via X-AlpineBits-ClientProtocolVersion header
@@ -747,10 +733,10 @@ async def alpinebits_server_handshake(
try:
body = gzip.decompress(body)
except Exception as e:
except Exception:
raise HTTPException(
status_code=400,
detail=f"ERROR: Failed to decompress gzip content",
detail="ERROR: Failed to decompress gzip content",
)
# Check content type (after decompression)
@@ -767,10 +753,10 @@ async def alpinebits_server_handshake(
if "multipart/form-data" in content_type:
try:
form_data = parse_multipart_data(content_type, body)
except Exception as e:
except Exception:
raise HTTPException(
status_code=400,
detail=f"ERROR: Failed to parse multipart/form-data",
detail="ERROR: Failed to parse multipart/form-data",
)
elif "application/x-www-form-urlencoded" in content_type:
# Parse as urlencoded
@@ -829,15 +815,14 @@ async def alpinebits_server_handshake(
# Re-raise HTTP exceptions (auth errors, etc.)
raise
except Exception as e:
_LOGGER.error(f"Error in AlpineBits handshake: {str(e)}")
raise HTTPException(status_code=500, detail=f"Internal server error")
_LOGGER.error(f"Error in AlpineBits handshake: {e!s}")
raise HTTPException(status_code=500, detail="Internal server error")
@api_router.get("/admin/stats")
@limiter.limit("10/minute")
async def get_api_stats(request: Request, admin_key: str = Depends(validate_api_key)):
"""
Admin endpoint to get API usage statistics.
"""Admin endpoint to get API usage statistics.
Requires admin API key.
"""
if admin_key != "admin-key":
@@ -862,8 +847,7 @@ app.include_router(api_router)
@app.get("/", response_class=HTMLResponse)
async def landing_page():
"""
Serve the under construction landing page at the root route
"""Serve the under construction landing page at the root route
"""
try:
# Get the path to the HTML file
@@ -871,7 +855,7 @@ async def landing_page():
html_path = os.path.join(os.path.dirname(__file__), "templates", "index.html")
with open(html_path, "r", encoding="utf-8") as f:
with open(html_path, encoding="utf-8") as f:
html_content = f.read()
return HTMLResponse(content=html_content, status_code=200)