Browse Source
- Added "infer_response_model_from_ast" function to analyze endpoint functions and infer Pydantic models from returned dictionary literals or variable assignments. - Updated "APIRoute" to utilize the new inference method when the specified response model is not a subclass of "BaseModel"pull/14446/head
3 changed files with 282 additions and 0 deletions
@ -0,0 +1,114 @@ |
|||||
|
from typing import Any, Dict, List |
||||
|
from fastapi import FastAPI |
||||
|
from fastapi.testclient import TestClient |
||||
|
|
||||
|
app = FastAPI() |
||||
|
|
||||
|
@app.get("/users/{user_id}") |
||||
|
async def get_user(user_id: int) -> Dict[str, Any]: |
||||
|
user: Dict[str, Any] = { |
||||
|
"id": user_id, |
||||
|
"username": "example", |
||||
|
"email": "[email protected]", |
||||
|
"age": 25, |
||||
|
"is_active": True, |
||||
|
} |
||||
|
return user |
||||
|
|
||||
|
@app.get("/orders/{order_id}") |
||||
|
async def get_order_details(order_id: str) -> Dict[str, Any]: |
||||
|
order_data: Dict[str, Any] = { |
||||
|
"order_id": order_id, |
||||
|
"status": "processing", |
||||
|
"total_amount": 150.50, |
||||
|
"tags": ["urgent", "new_customer"], |
||||
|
"customer_info": { |
||||
|
"name": "John Doe", |
||||
|
"vip_status": False, |
||||
|
"preferences": {"notifications": True, "theme": "dark"}, |
||||
|
}, |
||||
|
"items": [ |
||||
|
{ |
||||
|
"item_id": 1, |
||||
|
"name": "Laptop Stand", |
||||
|
"price": 45.00, |
||||
|
"in_stock": True, |
||||
|
}, |
||||
|
], |
||||
|
"metadata": None, |
||||
|
} |
||||
|
return order_data |
||||
|
|
||||
|
@app.get("/edge_cases/mixed_types") |
||||
|
async def get_mixed_types() -> Dict[str, Any]: |
||||
|
return { |
||||
|
"mixed_list": [1, "two", 3.0], |
||||
|
"description": "List starting with int" |
||||
|
} |
||||
|
|
||||
|
@app.get("/edge_cases/expressions") |
||||
|
async def get_expressions() -> Dict[str, Any]: |
||||
|
return { |
||||
|
"calc_int": 10 + 5, |
||||
|
"calc_str": "foo" + "bar", |
||||
|
"calc_bool": 5 > 3 |
||||
|
} |
||||
|
|
||||
|
@app.get("/edge_cases/empty_structures") |
||||
|
async def get_empty_structures() -> Dict[str, Any]: |
||||
|
return { |
||||
|
"empty_list": [], |
||||
|
"empty_dict": {} |
||||
|
} |
||||
|
|
||||
|
@app.get("/edge_cases/local_variable") |
||||
|
async def get_local_variable() -> Dict[str, Any]: |
||||
|
response_data = { |
||||
|
"status": "ok", |
||||
|
"nested": { |
||||
|
"check": True |
||||
|
} |
||||
|
} |
||||
|
return response_data |
||||
|
|
||||
|
client = TestClient(app) |
||||
|
|
||||
|
def test_openapi_schema_ast_inference(): |
||||
|
response = client.get("/openapi.json") |
||||
|
assert response.status_code == 200 |
||||
|
schema = response.json() |
||||
|
paths = schema["paths"] |
||||
|
|
||||
|
user_schema = paths["/users/{user_id}"]["get"]["responses"]["200"]["content"]["application/json"]["schema"] |
||||
|
assert "$ref" in user_schema |
||||
|
ref_name = user_schema["$ref"].split("/")[-1] |
||||
|
user_props = schema["components"]["schemas"][ref_name]["properties"] |
||||
|
|
||||
|
assert user_props["id"]["type"] == "integer" |
||||
|
assert user_props["username"]["type"] == "string" |
||||
|
assert user_props["is_active"]["type"] == "boolean" |
||||
|
|
||||
|
order_schema = paths["/orders/{order_id}"]["get"]["responses"]["200"]["content"]["application/json"]["schema"] |
||||
|
assert "$ref" in order_schema |
||||
|
order_ref = order_schema["$ref"].split("/")[-1] |
||||
|
order_props = schema["components"]["schemas"][order_ref]["properties"] |
||||
|
|
||||
|
items_prop = order_props["items"] |
||||
|
assert items_prop["type"] == "array" |
||||
|
assert "$ref" in items_prop["items"] |
||||
|
|
||||
|
customer_prop = order_props["customer_info"] |
||||
|
assert "$ref" in customer_prop |
||||
|
|
||||
|
mixed_schema = paths["/edge_cases/mixed_types"]["get"]["responses"]["200"]["content"]["application/json"]["schema"] |
||||
|
mixed_ref = mixed_schema["$ref"].split("/")[-1] |
||||
|
mixed_props = schema["components"]["schemas"][mixed_ref]["properties"] |
||||
|
assert mixed_props["mixed_list"]["type"] == "array" |
||||
|
|
||||
|
expr_schema = paths["/edge_cases/expressions"]["get"]["responses"]["200"]["content"]["application/json"]["schema"] |
||||
|
expr_ref = expr_schema["$ref"].split("/")[-1] |
||||
|
expr_props = schema["components"]["schemas"][expr_ref]["properties"] |
||||
|
|
||||
|
assert expr_props["calc_int"]["type"] == "integer" |
||||
|
assert expr_props["calc_bool"]["type"] == "boolean" |
||||
|
|
||||
Loading…
Reference in new issue