Architecture Overview
The WispHub API is designed as a high-performance middleware layer that optimizes and secures access to WispHub Net’s billing and support infrastructure. This page provides a comprehensive overview of the architectural decisions and design patterns.
System Architecture
┌─────────────────────┐
│ Client Application │
│ (WhatsApp Bot, │
│ Web Dashboard) │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ WispHub API │
│ (FastAPI) │
│ ┌───────────────┐ │
│ │ LRU Cache │ │
│ │ (async_lru) │ │
│ └───────────────┘ │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ WispHub Net API │
│ (External Service) │
└─────────────────────┘
FastAPI Application Structure
The application is organized following a clean architecture pattern:
Entry Point (app/main.py)
The main application file initializes FastAPI and configures global middleware:
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from app.api.v1 import clients, tickets, network, internet_plans
from app.api.exception_handlers import (
validation_exception_handler,
http_exception_handler,
general_exception_handler
)
app = FastAPI(
title = "WispHub API" ,
description = "API de integración local con WispHub." ,
version = "1.0.0"
)
# CORS Configuration
origins = [
"http://localhost" ,
"http://localhost:3000" ,
"http://localhost:8080" ,
"http://localhost:8081" ,
"http://localhost:8082" ,
]
app.add_middleware(
CORSMiddleware,
allow_origins = origins,
allow_credentials = True ,
allow_methods = [ "*" ],
allow_headers = [ "*" ],
)
# Register exception handlers
app.add_exception_handler(RequestValidationError, validation_exception_handler)
app.add_exception_handler(StarletteHTTPException, http_exception_handler)
app.add_exception_handler( Exception , general_exception_handler)
# Register routers
app.include_router(clients.router)
app.include_router(tickets.router)
app.include_router(network.router)
app.include_router(internet_plans.router)
Project Structure
app/
├── main.py # FastAPI application entry point
├── core/
│ └── config.py # Configuration management
├── api/
│ ├── exception_handlers.py # Global exception handlers
│ └── v1/
│ ├── clients.py # Client endpoints
│ ├── tickets.py # Ticket endpoints
│ ├── network.py # Network diagnostic endpoints
│ └── internet_plans.py # Plan management endpoints
├── services/
│ ├── clients_service.py # Client business logic
│ ├── tickets_service.py # Ticket business logic
│ ├── network_service.py # Network diagnostic logic
│ └── internet_plans_service.py
├── schemas/
│ ├── clients.py # Client Pydantic models
│ ├── tickets.py # Ticket Pydantic models
│ ├── internet_plans.py # Plan Pydantic models
│ └── responses/
│ ├── backend_response.py # Standardized response wrapper
│ ├── response_actions.py # Action enums
│ └── response_types.py # Type enums
└── utils/
├── responses.py # Response builder utilities
├── dates.py # Date manipulation helpers
└── ticket_rules.py # Business rules for tickets
Caching System
The caching system is the cornerstone of the API’s performance optimization strategy.
LRU Cache Implementation
The API uses async_lru to implement Least Recently Used caching with Time-To-Live (TTL) support:
app/services/clients_service.py
from async_lru import alru_cache
@alru_cache ( maxsize = 1 , ttl = 300 )
async def get_clients () -> List[ClientResponse]:
"""
Loads ALL clients from WispHub following pagination.
Results are cached for 5 minutes to avoid repeated load.
"""
all_results: List[ClientResponse] = []
next_url: Optional[ str ] = settings. CLIENTS_URL
async with httpx.AsyncClient( timeout = 30 , follow_redirects = True ) as client:
while next_url:
response = await client.get(next_url, headers = HEADERS )
if response.status_code != 200 :
break
try :
data = response.json()
except Exception :
break
results = data.get( "results" )
if not isinstance (results, list ):
break
all_results.extend(parse_client(c) for c in results)
next_url = data.get( "next" ) # None when no more pages
return all_results
Cache Strategy by Resource
Client List TTL : 5 minutes (300s)Max Size : 1 (single cached list)Rationale : Client data changes infrequently but is queried constantly by bots
Internet Plans TTL : 15 minutes (900s)Max Size : 32 plansRationale : Plans are rarely modified and heavily referenced during verification flows
The caching system dramatically improves response times:
Without cache : 500-1000ms (network roundtrip to WispHub Net)
With cache : Less than 5ms (in-memory lookup)
Cache hit ratio : Greater than 95% during normal bot operations
The cache automatically handles pagination, ensuring all client records are available even when WispHub Net returns paginated results.
Middleware Stack
CORS Middleware
Configured to allow requests from development and production origins:
origins = [
"http://localhost" ,
"http://localhost:3000" , # Frontend dev common port
"http://localhost:8080" , # Frontend dev common port
"http://localhost:8081" , # Chatbot interface dev server
"http://localhost:8082" , # Chatbot interface dev server (fallback)
]
app.add_middleware(
CORSMiddleware,
allow_origins = origins,
allow_credentials = True ,
allow_methods = [ "*" ],
allow_headers = [ "*" ],
)
For production deployment, update the origins list to include your production domains instead of localhost.
Exception Handling
All exceptions are caught and wrapped in the standardized BackendResponse format:
Validation Errors (422)
app/api/exception_handlers.py
async def validation_exception_handler ( request : Request, exc : RequestValidationError):
"""
Captures 422 Pydantic errors (type issues or missing fields)
and wraps them in BackendResponse standard.
"""
errors = exc.errors()
simplified_errors = [ f " { err[ 'loc' ][ - 1 ] } : { err[ 'msg' ] } " for err in errors]
error_msg = "; " .join(simplified_errors)
response = BackendResponse.error(
action = GeneralAction. ERROR ,
message = f "Error de validación: { error_msg } "
)
return JSONResponse(
status_code = 422 ,
content = response.model_dump()
)
HTTP Exceptions (404, etc.)
async def http_exception_handler ( request : Request, exc : StarletteHTTPException):
"""
Captures HTTP errors (like 404 Not Found)
and wraps them in BackendResponse standard.
"""
response = BackendResponse.error(
action = GeneralAction. ERROR ,
message = str (exc.detail)
)
return JSONResponse(
status_code = exc.status_code,
content = response.model_dump()
)
Internal Server Errors (500)
async def general_exception_handler ( request : Request, exc : Exception ):
"""
Captures any other internal error (500) to ensure
a structured BackendResponse JSON is always returned.
"""
response = BackendResponse.error(
action = GeneralAction. ERROR ,
message = "Ocurrió un error interno en el servidor."
)
return JSONResponse(
status_code = 500 ,
content = response.model_dump()
)
Configuration Management
Configuration is managed via Pydantic Settings with environment variable support:
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings ( BaseSettings ):
model_config = SettingsConfigDict( env_file = ".env" , case_sensitive = True )
# WispHub Net authentication
WISPHUB_NET_KEY : str
WISPHUB_NET_HOST : str
# Business rules
MAX_ACTIVE_TICKETS_PER_ZONE : int = 3
ACTIVE_TICKET_STATES : Tuple[ int , ... ] = ( 1 ,)
DEFAULT_TICKET_STATUS : int = 1
MAX_TICKET_RESOLUTION_DAYS : int = 3
@ property
def CLIENTS_URL ( self ) -> str :
return f " { self . WISPHUB_NET_HOST } /api/clientes/"
@ property
def PLANS_URL ( self ) -> str :
return f " { self . WISPHUB_NET_HOST } /api/plan-internet/"
settings = Settings()
See Environment Configuration for complete details.
Data Flow: Client Search Example
1. Bot sends search request
↓
2. FastAPI receives at /api/v1/clients/search?q=Esperanza
↓
3. Route handler calls fetch_clients_by_query()
↓
4. Service calls get_clients() (cached)
↓
5. If cache miss: Fetch from WispHub Net with pagination
If cache hit: Return from memory (under 5ms)
↓
6. Filter results locally by query string
↓
7. Parse into ClientResponse Pydantic models
↓
8. Wrap in BackendResponse
↓
9. Return standardized JSON to bot
Sustained RPS : Over 40 requests per second
Failure Rate : 0.00% on cached routes
P95 Latency (cached): Under 10ms
P95 Latency (uncached): ~800ms
Memory Usage : ~150MB with full client cache
Worker Processes : 4 (Gunicorn configuration)
Asynchronous Architecture
All I/O operations are asynchronous using:
HTTPX AsyncClient : Non-blocking HTTP requests to WispHub Net
FastAPI async routes : Concurrent request handling
Uvicorn workers : True async ASGI server
async def fetch_client ( params : Dict[ str , str ]) -> Optional[ClientResponse]:
async with httpx.AsyncClient( timeout = 10 ) as client:
response = await client.get(
settings. CLIENTS_URL ,
headers = HEADERS ,
params = params
)
# ... processing
This allows the API to handle multiple concurrent requests efficiently without blocking.
Next Steps