tennis-api / src /api_factory.py
SlimG's picture
improve forwarded endpoint resilience
6a8befc
from fastapi import Query
from typing import Dict, Literal
import httpx
import os
def extract_type(p):
schema = p.get("schema", {})
if "$ref" in schema:
return str # fallback
if "enum" in schema:
return Literal[tuple(schema["enum"])]
return openapi_type_to_python(schema.get("type", "string"))
def get_param_metadata(p):
schema = p.get("schema", {})
return {
"name": p["name"],
"type": extract_type(p),
"required": p.get("required", False),
"description": p.get("description", ""),
"default": schema.get("default", ...), # `...` => required if undefined
"enum": schema.get("enum", None),
}
async def get_remote_params(base_url: str,
endpoint: str,
method: str = 'get') -> Dict:
async with httpx.AsyncClient() as client:
headers = {}
if TENNIS_ML_API_KEY := os.getenv('TENNIS_ML_API_KEY'):
headers["Authorization"] = TENNIS_ML_API_KEY
response = await client.get(f"{base_url}openapi.json", headers=headers)
spec = response.json()
# Extraction of the parameters from the OpenAPI spec
path_def = spec["paths"].get(f'/{endpoint}', {})
method_def = path_def.get(method, {})
params = method_def.get("parameters", [])
return {
"found": bool(path_def),
"general": {
"description": method_def.get("description", ""),
"summary": method_def.get("summary", ""),
"tags": method_def.get("tags", []),
},
"params": [
get_param_metadata(p)
for p in params if p["in"] == "query"
]
}
def openapi_type_to_python(t: str):
return {
"string": str,
"integer": int,
"number": float,
"boolean": bool,
"array": list,
}.get(t, str) # fallback = str
def create_forward_endpoint(base_url: str, _endpoint: str, param_defs: Dict):
# Dynamic endpoint creation
def endpoint_factory():
async def endpoint(**kwargs):
headers = {}
if TENNIS_ML_API_KEY := os.getenv('TENNIS_ML_API_KEY'):
headers["Authorization"] = TENNIS_ML_API_KEY
async with httpx.AsyncClient() as client:
response = await client.get(
f"{base_url}{_endpoint}",
params=kwargs,
headers=headers,
)
response.raise_for_status()
return response.json()
return endpoint
endpoint = endpoint_factory()
# Add the parameters to the endpoint
endpoint.__annotations__ = {
p["name"]: openapi_type_to_python(p["type"]) for p in param_defs
}
# Add dependencies to the endpoint
from inspect import Parameter, Signature
parameters = []
for p in param_defs:
annotation = p["type"]
default = p.get("default", ...) if p.get("required", False) else p.get("default", None)
query = Query(
default=default,
description=p.get("description", "")
)
param = Parameter(
name=p["name"],
kind=Parameter.KEYWORD_ONLY,
default=query,
annotation=annotation
)
parameters.append(param)
endpoint.__signature__ = Signature(parameters)
return endpoint