pythonasyncioapiasyncfastapiframeworkjsonjson-schemaopenapiopenapi3pydanticpython-typespython3redocreststarletteswaggerswagger-uiuvicornweb
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1019 lines
28 KiB
1019 lines
28 KiB
"""
|
|
Benchmark tests for OpenAPI schema generation performance.
|
|
|
|
This module tests OpenAPI schema generation with large numbers of routes
|
|
and Pydantic models to identify performance bottlenecks and verify
|
|
optimizations.
|
|
|
|
Run with: pytest tests/benchmarks/test_openapi_performance.py -v
|
|
Or with profiling: pytest tests/benchmarks/test_openapi_performance.py -v -s
|
|
"""
|
|
|
|
import time
|
|
from collections.abc import Iterator
|
|
from datetime import datetime
|
|
from enum import Enum
|
|
from typing import Annotated, Any, Optional, Union
|
|
from uuid import UUID
|
|
|
|
import pytest
|
|
from fastapi import Depends, FastAPI, Path, Query
|
|
from fastapi.openapi._profiling import ProfilingContext
|
|
from fastapi.testclient import TestClient
|
|
from pydantic import BaseModel, Field
|
|
|
|
|
|
# =============================================================================
|
|
# Pydantic Models (50+ unique models with nested structures)
|
|
# =============================================================================
|
|
|
|
|
|
class StatusEnum(str, Enum):
|
|
ACTIVE = "active"
|
|
INACTIVE = "inactive"
|
|
PENDING = "pending"
|
|
ARCHIVED = "archived"
|
|
|
|
|
|
class PriorityEnum(str, Enum):
|
|
LOW = "low"
|
|
MEDIUM = "medium"
|
|
HIGH = "high"
|
|
CRITICAL = "critical"
|
|
|
|
|
|
class Address(BaseModel):
|
|
street: str
|
|
city: str
|
|
state: str
|
|
zip_code: str
|
|
country: str = "USA"
|
|
|
|
|
|
class ContactInfo(BaseModel):
|
|
email: str
|
|
phone: Optional[str] = None
|
|
address: Optional[Address] = None
|
|
|
|
|
|
class BaseEntity(BaseModel):
|
|
id: UUID
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
|
|
|
|
class UserBase(BaseModel):
|
|
username: str = Field(..., min_length=3, max_length=50)
|
|
email: str
|
|
full_name: Optional[str] = None
|
|
|
|
|
|
class UserCreate(UserBase):
|
|
password: str = Field(..., min_length=8)
|
|
|
|
|
|
class UserUpdate(BaseModel):
|
|
username: Optional[str] = None
|
|
email: Optional[str] = None
|
|
full_name: Optional[str] = None
|
|
|
|
|
|
class User(UserBase, BaseEntity):
|
|
is_active: bool = True
|
|
role: str = "user"
|
|
contact: Optional[ContactInfo] = None
|
|
|
|
|
|
class UserWithProfile(User):
|
|
profile_picture_url: Optional[str] = None
|
|
bio: Optional[str] = None
|
|
social_links: dict[str, str] = Field(default_factory=dict)
|
|
|
|
|
|
class Tag(BaseModel):
|
|
name: str
|
|
color: Optional[str] = None
|
|
|
|
|
|
class Category(BaseModel):
|
|
id: int
|
|
name: str
|
|
description: Optional[str] = None
|
|
parent_id: Optional[int] = None
|
|
|
|
|
|
class ProductBase(BaseModel):
|
|
name: str
|
|
description: Optional[str] = None
|
|
price: float = Field(..., ge=0)
|
|
sku: str
|
|
|
|
|
|
class ProductCreate(ProductBase):
|
|
category_id: int
|
|
tags: list[str] = Field(default_factory=list)
|
|
|
|
|
|
class ProductUpdate(BaseModel):
|
|
name: Optional[str] = None
|
|
description: Optional[str] = None
|
|
price: Optional[float] = None
|
|
|
|
|
|
class Product(ProductBase, BaseEntity):
|
|
category: Optional[Category] = None
|
|
tags: list[Tag] = Field(default_factory=list)
|
|
status: StatusEnum = StatusEnum.ACTIVE
|
|
inventory_count: int = 0
|
|
|
|
|
|
class OrderItem(BaseModel):
|
|
product_id: UUID
|
|
quantity: int = Field(..., ge=1)
|
|
unit_price: float
|
|
discount: float = 0.0
|
|
|
|
|
|
class OrderBase(BaseModel):
|
|
customer_id: UUID
|
|
shipping_address: Address
|
|
billing_address: Optional[Address] = None
|
|
|
|
|
|
class OrderCreate(OrderBase):
|
|
items: list[OrderItem]
|
|
notes: Optional[str] = None
|
|
|
|
|
|
class Order(OrderBase, BaseEntity):
|
|
items: list[OrderItem]
|
|
status: StatusEnum = StatusEnum.PENDING
|
|
total_amount: float
|
|
tax_amount: float
|
|
shipping_cost: float
|
|
|
|
|
|
class PaymentMethod(BaseModel):
|
|
type: str
|
|
last_four: str
|
|
expiry_month: int
|
|
expiry_year: int
|
|
|
|
|
|
class Payment(BaseEntity):
|
|
order_id: UUID
|
|
amount: float
|
|
method: PaymentMethod
|
|
status: StatusEnum
|
|
|
|
|
|
class Review(BaseEntity):
|
|
product_id: UUID
|
|
user_id: UUID
|
|
rating: int = Field(..., ge=1, le=5)
|
|
title: str
|
|
content: str
|
|
helpful_count: int = 0
|
|
|
|
|
|
class Notification(BaseEntity):
|
|
user_id: UUID
|
|
title: str
|
|
message: str
|
|
is_read: bool = False
|
|
priority: PriorityEnum = PriorityEnum.MEDIUM
|
|
|
|
|
|
class AuditLog(BaseEntity):
|
|
action: str
|
|
entity_type: str
|
|
entity_id: UUID
|
|
user_id: UUID
|
|
changes: dict[str, Any]
|
|
|
|
|
|
class Settings(BaseModel):
|
|
theme: str = "light"
|
|
language: str = "en"
|
|
notifications_enabled: bool = True
|
|
email_preferences: dict[str, bool] = Field(default_factory=dict)
|
|
|
|
|
|
class Analytics(BaseModel):
|
|
page_views: int
|
|
unique_visitors: int
|
|
bounce_rate: float
|
|
avg_session_duration: float
|
|
top_pages: list[str]
|
|
|
|
|
|
class Report(BaseEntity):
|
|
name: str
|
|
type: str
|
|
parameters: dict[str, Any]
|
|
data: Analytics
|
|
generated_by: UUID
|
|
|
|
|
|
class Webhook(BaseEntity):
|
|
url: str
|
|
events: list[str]
|
|
is_active: bool = True
|
|
secret: Optional[str] = None
|
|
|
|
|
|
class ApiKey(BaseEntity):
|
|
name: str
|
|
key_prefix: str
|
|
scopes: list[str]
|
|
expires_at: Optional[datetime] = None
|
|
|
|
|
|
class RateLimit(BaseModel):
|
|
requests_per_minute: int
|
|
requests_per_hour: int
|
|
requests_per_day: int
|
|
|
|
|
|
class Subscription(BaseEntity):
|
|
user_id: UUID
|
|
plan: str
|
|
status: StatusEnum
|
|
rate_limits: RateLimit
|
|
features: list[str]
|
|
|
|
|
|
class Invoice(BaseEntity):
|
|
subscription_id: UUID
|
|
amount: float
|
|
status: StatusEnum
|
|
due_date: datetime
|
|
paid_at: Optional[datetime] = None
|
|
|
|
|
|
class Team(BaseEntity):
|
|
name: str
|
|
description: Optional[str] = None
|
|
members: list[UUID]
|
|
owner_id: UUID
|
|
|
|
|
|
class Project(BaseEntity):
|
|
name: str
|
|
description: Optional[str] = None
|
|
team_id: UUID
|
|
status: StatusEnum
|
|
priority: PriorityEnum
|
|
|
|
|
|
class Task(BaseEntity):
|
|
title: str
|
|
description: Optional[str] = None
|
|
project_id: UUID
|
|
assignee_id: Optional[UUID] = None
|
|
status: StatusEnum
|
|
priority: PriorityEnum
|
|
due_date: Optional[datetime] = None
|
|
|
|
|
|
class Comment(BaseEntity):
|
|
task_id: UUID
|
|
user_id: UUID
|
|
content: str
|
|
parent_id: Optional[UUID] = None
|
|
|
|
|
|
class Attachment(BaseEntity):
|
|
task_id: UUID
|
|
filename: str
|
|
file_url: str
|
|
file_size: int
|
|
mime_type: str
|
|
|
|
|
|
class PaginatedResponse(BaseModel):
|
|
items: list[Any]
|
|
total: int
|
|
page: int
|
|
page_size: int
|
|
has_more: bool
|
|
|
|
|
|
class ErrorResponse(BaseModel):
|
|
error_code: str
|
|
message: str
|
|
details: Optional[dict[str, Any]] = None
|
|
|
|
|
|
class HealthCheck(BaseModel):
|
|
status: str
|
|
version: str
|
|
uptime: float
|
|
components: dict[str, str]
|
|
|
|
|
|
# =============================================================================
|
|
# Dependencies
|
|
# =============================================================================
|
|
|
|
|
|
def get_current_user() -> User:
|
|
return User(
|
|
id="00000000-0000-0000-0000-000000000001",
|
|
username="testuser",
|
|
email="[email protected]",
|
|
created_at=datetime.now(),
|
|
updated_at=datetime.now(),
|
|
)
|
|
|
|
|
|
def get_pagination(
|
|
page: int = Query(1, ge=1),
|
|
page_size: int = Query(20, ge=1, le=100),
|
|
) -> dict[str, int]:
|
|
return {"page": page, "page_size": page_size}
|
|
|
|
|
|
CurrentUser = Annotated[User, Depends(get_current_user)]
|
|
Pagination = Annotated[dict[str, int], Depends(get_pagination)]
|
|
|
|
|
|
# =============================================================================
|
|
# Create Large Application with 100+ Routes
|
|
# =============================================================================
|
|
|
|
|
|
def create_large_app() -> FastAPI:
|
|
"""Create a FastAPI app with 100+ routes and 50+ models."""
|
|
app = FastAPI(
|
|
title="Large Benchmark API",
|
|
description="API for benchmarking OpenAPI generation",
|
|
version="1.0.0",
|
|
)
|
|
|
|
# User routes (10 routes)
|
|
@app.get("/users", response_model=list[User], tags=["Users"])
|
|
def list_users(pagination: Pagination):
|
|
return []
|
|
|
|
@app.post("/users", response_model=User, tags=["Users"])
|
|
def create_user(user: UserCreate):
|
|
pass
|
|
|
|
@app.get("/users/{user_id}", response_model=UserWithProfile, tags=["Users"])
|
|
def get_user(user_id: UUID = Path(...)):
|
|
pass
|
|
|
|
@app.put("/users/{user_id}", response_model=User, tags=["Users"])
|
|
def update_user(user_id: UUID, user: UserUpdate):
|
|
pass
|
|
|
|
@app.delete("/users/{user_id}", tags=["Users"])
|
|
def delete_user(user_id: UUID):
|
|
pass
|
|
|
|
@app.get("/users/{user_id}/settings", response_model=Settings, tags=["Users"])
|
|
def get_user_settings(user_id: UUID):
|
|
pass
|
|
|
|
@app.put("/users/{user_id}/settings", response_model=Settings, tags=["Users"])
|
|
def update_user_settings(user_id: UUID, settings: Settings):
|
|
pass
|
|
|
|
@app.get(
|
|
"/users/{user_id}/notifications",
|
|
response_model=list[Notification],
|
|
tags=["Users"],
|
|
)
|
|
def get_user_notifications(user_id: UUID, pagination: Pagination):
|
|
return []
|
|
|
|
@app.get("/users/me", response_model=UserWithProfile, tags=["Users"])
|
|
def get_current_user_profile(current_user: CurrentUser):
|
|
return current_user
|
|
|
|
@app.get("/users/search", response_model=list[User], tags=["Users"])
|
|
def search_users(
|
|
q: str = Query(..., min_length=1),
|
|
pagination: Pagination = None,
|
|
):
|
|
return []
|
|
|
|
# Product routes (15 routes)
|
|
@app.get("/products", response_model=list[Product], tags=["Products"])
|
|
def list_products(
|
|
category_id: Optional[int] = None,
|
|
status: Optional[StatusEnum] = None,
|
|
pagination: Pagination = None,
|
|
):
|
|
return []
|
|
|
|
@app.post("/products", response_model=Product, tags=["Products"])
|
|
def create_product(product: ProductCreate, current_user: CurrentUser):
|
|
pass
|
|
|
|
@app.get("/products/{product_id}", response_model=Product, tags=["Products"])
|
|
def get_product(product_id: UUID):
|
|
pass
|
|
|
|
@app.put("/products/{product_id}", response_model=Product, tags=["Products"])
|
|
def update_product(product_id: UUID, product: ProductUpdate):
|
|
pass
|
|
|
|
@app.delete("/products/{product_id}", tags=["Products"])
|
|
def delete_product(product_id: UUID):
|
|
pass
|
|
|
|
@app.get(
|
|
"/products/{product_id}/reviews",
|
|
response_model=list[Review],
|
|
tags=["Products"],
|
|
)
|
|
def get_product_reviews(product_id: UUID, pagination: Pagination):
|
|
return []
|
|
|
|
@app.post(
|
|
"/products/{product_id}/reviews", response_model=Review, tags=["Products"]
|
|
)
|
|
def create_product_review(
|
|
product_id: UUID,
|
|
rating: int = Query(..., ge=1, le=5),
|
|
title: str = Query(...),
|
|
content: str = Query(...),
|
|
current_user: CurrentUser = None,
|
|
):
|
|
pass
|
|
|
|
@app.get("/products/featured", response_model=list[Product], tags=["Products"])
|
|
def get_featured_products():
|
|
return []
|
|
|
|
@app.get("/products/search", response_model=list[Product], tags=["Products"])
|
|
def search_products(
|
|
q: str = Query(...),
|
|
min_price: Optional[float] = None,
|
|
max_price: Optional[float] = None,
|
|
pagination: Pagination = None,
|
|
):
|
|
return []
|
|
|
|
@app.get(
|
|
"/products/categories", response_model=list[Category], tags=["Products"]
|
|
)
|
|
def list_categories():
|
|
return []
|
|
|
|
@app.post("/products/categories", response_model=Category, tags=["Products"])
|
|
def create_category(category: Category):
|
|
pass
|
|
|
|
@app.get(
|
|
"/products/categories/{category_id}",
|
|
response_model=Category,
|
|
tags=["Products"],
|
|
)
|
|
def get_category(category_id: int):
|
|
pass
|
|
|
|
@app.get("/products/tags", response_model=list[Tag], tags=["Products"])
|
|
def list_tags():
|
|
return []
|
|
|
|
@app.post("/products/tags", response_model=Tag, tags=["Products"])
|
|
def create_tag(tag: Tag):
|
|
pass
|
|
|
|
@app.get(
|
|
"/products/{product_id}/related",
|
|
response_model=list[Product],
|
|
tags=["Products"],
|
|
)
|
|
def get_related_products(product_id: UUID):
|
|
return []
|
|
|
|
# Order routes (12 routes)
|
|
@app.get("/orders", response_model=list[Order], tags=["Orders"])
|
|
def list_orders(
|
|
status: Optional[StatusEnum] = None,
|
|
pagination: Pagination = None,
|
|
current_user: CurrentUser = None,
|
|
):
|
|
return []
|
|
|
|
@app.post("/orders", response_model=Order, tags=["Orders"])
|
|
def create_order(order: OrderCreate, current_user: CurrentUser):
|
|
pass
|
|
|
|
@app.get("/orders/{order_id}", response_model=Order, tags=["Orders"])
|
|
def get_order(order_id: UUID):
|
|
pass
|
|
|
|
@app.put("/orders/{order_id}/status", response_model=Order, tags=["Orders"])
|
|
def update_order_status(order_id: UUID, status: StatusEnum):
|
|
pass
|
|
|
|
@app.delete("/orders/{order_id}", tags=["Orders"])
|
|
def cancel_order(order_id: UUID):
|
|
pass
|
|
|
|
@app.get(
|
|
"/orders/{order_id}/payments", response_model=list[Payment], tags=["Orders"]
|
|
)
|
|
def get_order_payments(order_id: UUID):
|
|
return []
|
|
|
|
@app.post("/orders/{order_id}/payments", response_model=Payment, tags=["Orders"])
|
|
def create_payment(order_id: UUID, method: PaymentMethod):
|
|
pass
|
|
|
|
@app.get(
|
|
"/orders/{order_id}/invoice", response_model=Invoice, tags=["Orders"]
|
|
)
|
|
def get_order_invoice(order_id: UUID):
|
|
pass
|
|
|
|
@app.get("/orders/stats", response_model=Analytics, tags=["Orders"])
|
|
def get_order_stats(
|
|
start_date: Optional[datetime] = None, end_date: Optional[datetime] = None
|
|
):
|
|
pass
|
|
|
|
@app.get("/orders/recent", response_model=list[Order], tags=["Orders"])
|
|
def get_recent_orders(limit: int = Query(10, ge=1, le=50)):
|
|
return []
|
|
|
|
@app.get(
|
|
"/orders/{order_id}/tracking", response_model=dict[str, Any], tags=["Orders"]
|
|
)
|
|
def get_order_tracking(order_id: UUID):
|
|
return {}
|
|
|
|
@app.post("/orders/{order_id}/refund", response_model=Payment, tags=["Orders"])
|
|
def request_refund(order_id: UUID, reason: str = Query(...)):
|
|
pass
|
|
|
|
# Team/Project routes (20 routes)
|
|
@app.get("/teams", response_model=list[Team], tags=["Teams"])
|
|
def list_teams(pagination: Pagination):
|
|
return []
|
|
|
|
@app.post("/teams", response_model=Team, tags=["Teams"])
|
|
def create_team(name: str, description: Optional[str] = None):
|
|
pass
|
|
|
|
@app.get("/teams/{team_id}", response_model=Team, tags=["Teams"])
|
|
def get_team(team_id: UUID):
|
|
pass
|
|
|
|
@app.put("/teams/{team_id}", response_model=Team, tags=["Teams"])
|
|
def update_team(team_id: UUID, name: Optional[str] = None):
|
|
pass
|
|
|
|
@app.delete("/teams/{team_id}", tags=["Teams"])
|
|
def delete_team(team_id: UUID):
|
|
pass
|
|
|
|
@app.get(
|
|
"/teams/{team_id}/members", response_model=list[User], tags=["Teams"]
|
|
)
|
|
def get_team_members(team_id: UUID):
|
|
return []
|
|
|
|
@app.post("/teams/{team_id}/members", tags=["Teams"])
|
|
def add_team_member(team_id: UUID, user_id: UUID):
|
|
pass
|
|
|
|
@app.delete("/teams/{team_id}/members/{user_id}", tags=["Teams"])
|
|
def remove_team_member(team_id: UUID, user_id: UUID):
|
|
pass
|
|
|
|
@app.get(
|
|
"/teams/{team_id}/projects", response_model=list[Project], tags=["Teams"]
|
|
)
|
|
def get_team_projects(team_id: UUID, pagination: Pagination):
|
|
return []
|
|
|
|
@app.post(
|
|
"/teams/{team_id}/projects", response_model=Project, tags=["Teams"]
|
|
)
|
|
def create_project(team_id: UUID, name: str, description: Optional[str] = None):
|
|
pass
|
|
|
|
@app.get("/projects/{project_id}", response_model=Project, tags=["Projects"])
|
|
def get_project(project_id: UUID):
|
|
pass
|
|
|
|
@app.put("/projects/{project_id}", response_model=Project, tags=["Projects"])
|
|
def update_project(
|
|
project_id: UUID,
|
|
name: Optional[str] = None,
|
|
status: Optional[StatusEnum] = None,
|
|
):
|
|
pass
|
|
|
|
@app.delete("/projects/{project_id}", tags=["Projects"])
|
|
def delete_project(project_id: UUID):
|
|
pass
|
|
|
|
@app.get(
|
|
"/projects/{project_id}/tasks", response_model=list[Task], tags=["Projects"]
|
|
)
|
|
def get_project_tasks(
|
|
project_id: UUID,
|
|
status: Optional[StatusEnum] = None,
|
|
pagination: Pagination = None,
|
|
):
|
|
return []
|
|
|
|
@app.post("/projects/{project_id}/tasks", response_model=Task, tags=["Projects"])
|
|
def create_task(
|
|
project_id: UUID,
|
|
title: str,
|
|
description: Optional[str] = None,
|
|
priority: PriorityEnum = PriorityEnum.MEDIUM,
|
|
):
|
|
pass
|
|
|
|
@app.get("/tasks/{task_id}", response_model=Task, tags=["Tasks"])
|
|
def get_task(task_id: UUID):
|
|
pass
|
|
|
|
@app.put("/tasks/{task_id}", response_model=Task, tags=["Tasks"])
|
|
def update_task(
|
|
task_id: UUID,
|
|
title: Optional[str] = None,
|
|
status: Optional[StatusEnum] = None,
|
|
priority: Optional[PriorityEnum] = None,
|
|
):
|
|
pass
|
|
|
|
@app.delete("/tasks/{task_id}", tags=["Tasks"])
|
|
def delete_task(task_id: UUID):
|
|
pass
|
|
|
|
@app.get(
|
|
"/tasks/{task_id}/comments", response_model=list[Comment], tags=["Tasks"]
|
|
)
|
|
def get_task_comments(task_id: UUID):
|
|
return []
|
|
|
|
@app.post("/tasks/{task_id}/comments", response_model=Comment, tags=["Tasks"])
|
|
def add_task_comment(task_id: UUID, content: str):
|
|
pass
|
|
|
|
# Subscription/Billing routes (10 routes)
|
|
@app.get("/subscriptions", response_model=list[Subscription], tags=["Billing"])
|
|
def list_subscriptions(current_user: CurrentUser):
|
|
return []
|
|
|
|
@app.post("/subscriptions", response_model=Subscription, tags=["Billing"])
|
|
def create_subscription(plan: str, current_user: CurrentUser):
|
|
pass
|
|
|
|
@app.get(
|
|
"/subscriptions/{subscription_id}",
|
|
response_model=Subscription,
|
|
tags=["Billing"],
|
|
)
|
|
def get_subscription(subscription_id: UUID):
|
|
pass
|
|
|
|
@app.put(
|
|
"/subscriptions/{subscription_id}",
|
|
response_model=Subscription,
|
|
tags=["Billing"],
|
|
)
|
|
def update_subscription(subscription_id: UUID, plan: str):
|
|
pass
|
|
|
|
@app.delete("/subscriptions/{subscription_id}", tags=["Billing"])
|
|
def cancel_subscription(subscription_id: UUID):
|
|
pass
|
|
|
|
@app.get(
|
|
"/subscriptions/{subscription_id}/invoices",
|
|
response_model=list[Invoice],
|
|
tags=["Billing"],
|
|
)
|
|
def get_subscription_invoices(subscription_id: UUID):
|
|
return []
|
|
|
|
@app.get("/invoices/{invoice_id}", response_model=Invoice, tags=["Billing"])
|
|
def get_invoice(invoice_id: UUID):
|
|
pass
|
|
|
|
@app.post("/invoices/{invoice_id}/pay", response_model=Invoice, tags=["Billing"])
|
|
def pay_invoice(invoice_id: UUID, method: PaymentMethod):
|
|
pass
|
|
|
|
@app.get("/billing/methods", response_model=list[PaymentMethod], tags=["Billing"])
|
|
def list_payment_methods(current_user: CurrentUser):
|
|
return []
|
|
|
|
@app.post("/billing/methods", response_model=PaymentMethod, tags=["Billing"])
|
|
def add_payment_method(method: PaymentMethod, current_user: CurrentUser):
|
|
pass
|
|
|
|
# API Management routes (10 routes)
|
|
@app.get("/api-keys", response_model=list[ApiKey], tags=["API"])
|
|
def list_api_keys(current_user: CurrentUser):
|
|
return []
|
|
|
|
@app.post("/api-keys", response_model=ApiKey, tags=["API"])
|
|
def create_api_key(name: str, scopes: list[str], current_user: CurrentUser):
|
|
pass
|
|
|
|
@app.delete("/api-keys/{key_id}", tags=["API"])
|
|
def revoke_api_key(key_id: UUID):
|
|
pass
|
|
|
|
@app.get("/webhooks", response_model=list[Webhook], tags=["API"])
|
|
def list_webhooks(current_user: CurrentUser):
|
|
return []
|
|
|
|
@app.post("/webhooks", response_model=Webhook, tags=["API"])
|
|
def create_webhook(url: str, events: list[str], current_user: CurrentUser):
|
|
pass
|
|
|
|
@app.get("/webhooks/{webhook_id}", response_model=Webhook, tags=["API"])
|
|
def get_webhook(webhook_id: UUID):
|
|
pass
|
|
|
|
@app.put("/webhooks/{webhook_id}", response_model=Webhook, tags=["API"])
|
|
def update_webhook(webhook_id: UUID, url: Optional[str] = None):
|
|
pass
|
|
|
|
@app.delete("/webhooks/{webhook_id}", tags=["API"])
|
|
def delete_webhook(webhook_id: UUID):
|
|
pass
|
|
|
|
@app.post("/webhooks/{webhook_id}/test", tags=["API"])
|
|
def test_webhook(webhook_id: UUID):
|
|
pass
|
|
|
|
@app.get("/rate-limits", response_model=RateLimit, tags=["API"])
|
|
def get_rate_limits(current_user: CurrentUser):
|
|
pass
|
|
|
|
# Admin/Analytics routes (15 routes)
|
|
@app.get("/admin/users", response_model=list[User], tags=["Admin"])
|
|
def admin_list_users(
|
|
status: Optional[str] = None, pagination: Pagination = None
|
|
):
|
|
return []
|
|
|
|
@app.get("/admin/audit-logs", response_model=list[AuditLog], tags=["Admin"])
|
|
def get_audit_logs(
|
|
entity_type: Optional[str] = None,
|
|
user_id: Optional[UUID] = None,
|
|
pagination: Pagination = None,
|
|
):
|
|
return []
|
|
|
|
@app.get("/admin/reports", response_model=list[Report], tags=["Admin"])
|
|
def list_reports(pagination: Pagination):
|
|
return []
|
|
|
|
@app.post("/admin/reports", response_model=Report, tags=["Admin"])
|
|
def create_report(name: str, type: str, parameters: dict[str, Any]):
|
|
pass
|
|
|
|
@app.get("/admin/reports/{report_id}", response_model=Report, tags=["Admin"])
|
|
def get_report(report_id: UUID):
|
|
pass
|
|
|
|
@app.get("/admin/analytics", response_model=Analytics, tags=["Admin"])
|
|
def get_analytics(
|
|
start_date: Optional[datetime] = None, end_date: Optional[datetime] = None
|
|
):
|
|
pass
|
|
|
|
@app.get(
|
|
"/admin/analytics/users", response_model=dict[str, Any], tags=["Admin"]
|
|
)
|
|
def get_user_analytics():
|
|
return {}
|
|
|
|
@app.get(
|
|
"/admin/analytics/products", response_model=dict[str, Any], tags=["Admin"]
|
|
)
|
|
def get_product_analytics():
|
|
return {}
|
|
|
|
@app.get(
|
|
"/admin/analytics/orders", response_model=dict[str, Any], tags=["Admin"]
|
|
)
|
|
def get_order_analytics():
|
|
return {}
|
|
|
|
@app.get(
|
|
"/admin/analytics/revenue", response_model=dict[str, Any], tags=["Admin"]
|
|
)
|
|
def get_revenue_analytics():
|
|
return {}
|
|
|
|
@app.get("/admin/settings", response_model=dict[str, Any], tags=["Admin"])
|
|
def get_admin_settings():
|
|
return {}
|
|
|
|
@app.put("/admin/settings", response_model=dict[str, Any], tags=["Admin"])
|
|
def update_admin_settings(settings: dict[str, Any]):
|
|
return {}
|
|
|
|
@app.get("/admin/notifications", response_model=list[Notification], tags=["Admin"])
|
|
def get_system_notifications():
|
|
return []
|
|
|
|
@app.post("/admin/notifications", response_model=Notification, tags=["Admin"])
|
|
def create_system_notification(title: str, message: str):
|
|
pass
|
|
|
|
@app.get(
|
|
"/admin/notifications/broadcast", tags=["Admin"]
|
|
)
|
|
def broadcast_notification(title: str, message: str):
|
|
pass
|
|
|
|
# Health/Misc routes (8 routes)
|
|
@app.get("/health", response_model=HealthCheck, tags=["System"])
|
|
def health_check():
|
|
pass
|
|
|
|
@app.get("/health/ready", tags=["System"])
|
|
def readiness_check():
|
|
return {"status": "ready"}
|
|
|
|
@app.get("/health/live", tags=["System"])
|
|
def liveness_check():
|
|
return {"status": "live"}
|
|
|
|
@app.get("/version", tags=["System"])
|
|
def get_version():
|
|
return {"version": "1.0.0"}
|
|
|
|
@app.get("/config", response_model=dict[str, Any], tags=["System"])
|
|
def get_public_config():
|
|
return {}
|
|
|
|
@app.get("/features", response_model=list[str], tags=["System"])
|
|
def get_feature_flags():
|
|
return []
|
|
|
|
@app.get("/metrics", tags=["System"])
|
|
def get_metrics():
|
|
return {}
|
|
|
|
@app.get("/error", response_model=ErrorResponse, tags=["System"])
|
|
def get_error_codes():
|
|
pass
|
|
|
|
return app
|
|
|
|
|
|
# =============================================================================
|
|
# Benchmark Tests
|
|
# =============================================================================
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def large_app() -> FastAPI:
|
|
"""Create the large app fixture."""
|
|
return create_large_app()
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def client(large_app: FastAPI) -> Iterator[TestClient]:
|
|
"""Create test client fixture."""
|
|
with TestClient(large_app) as c:
|
|
yield c
|
|
|
|
|
|
class TestOpenAPIGeneration:
|
|
"""Tests for OpenAPI schema generation performance."""
|
|
|
|
def test_large_app_route_count(self, large_app: FastAPI) -> None:
|
|
"""Verify the large app has 100+ routes."""
|
|
routes = [r for r in large_app.routes if hasattr(r, "methods")]
|
|
assert len(routes) >= 100, f"Expected 100+ routes, got {len(routes)}"
|
|
|
|
def test_openapi_generation_baseline(self, large_app: FastAPI) -> None:
|
|
"""Test baseline OpenAPI generation time without optimization."""
|
|
# Clear any cached schema
|
|
large_app.openapi_schema = None
|
|
|
|
# Measure generation time
|
|
start = time.perf_counter()
|
|
schema = large_app.openapi()
|
|
elapsed_ms = (time.perf_counter() - start) * 1000
|
|
|
|
# Verify schema is valid
|
|
assert "openapi" in schema
|
|
assert "paths" in schema
|
|
assert len(schema["paths"]) >= 50 # Many unique paths
|
|
|
|
# Log timing
|
|
print(f"\nOpenAPI generation time: {elapsed_ms:.2f}ms")
|
|
print(f"Number of paths: {len(schema['paths'])}")
|
|
print(f"Number of schemas: {len(schema.get('components', {}).get('schemas', {}))}")
|
|
|
|
def test_openapi_generation_with_profiling(self, large_app: FastAPI) -> None:
|
|
"""Test OpenAPI generation with detailed profiling."""
|
|
# Clear any cached schema
|
|
large_app.openapi_schema = None
|
|
|
|
with ProfilingContext() as ctx:
|
|
schema = large_app.openapi()
|
|
|
|
# Print profiling report
|
|
print("\n")
|
|
ctx.print_report()
|
|
|
|
# Verify schema
|
|
assert "openapi" in schema
|
|
assert "paths" in schema
|
|
|
|
# Get stats for analysis
|
|
stats = ctx.get_stats()
|
|
assert "get_openapi" in stats
|
|
assert stats["get_openapi"].call_count == 1
|
|
|
|
def test_openapi_caching_works(self, large_app: FastAPI) -> None:
|
|
"""Verify that OpenAPI caching prevents regeneration."""
|
|
# Clear any cached schema
|
|
large_app.openapi_schema = None
|
|
|
|
# First generation
|
|
start1 = time.perf_counter()
|
|
schema1 = large_app.openapi()
|
|
time1 = (time.perf_counter() - start1) * 1000
|
|
|
|
# Second call should be cached
|
|
start2 = time.perf_counter()
|
|
schema2 = large_app.openapi()
|
|
time2 = (time.perf_counter() - start2) * 1000
|
|
|
|
# Verify caching (second call should be nearly instant)
|
|
assert schema1 is schema2 # Same object
|
|
assert time2 < time1 / 10 # At least 10x faster
|
|
|
|
print(f"\nFirst call: {time1:.2f}ms")
|
|
print(f"Cached call: {time2:.4f}ms")
|
|
print(f"Speedup: {time1/time2:.1f}x")
|
|
|
|
def test_openapi_schema_correctness(self, large_app: FastAPI) -> None:
|
|
"""Verify the generated schema is correct."""
|
|
# Clear any cached schema
|
|
large_app.openapi_schema = None
|
|
schema = large_app.openapi()
|
|
|
|
# Check structure
|
|
assert schema["openapi"].startswith("3.")
|
|
assert schema["info"]["title"] == "Large Benchmark API"
|
|
assert schema["info"]["version"] == "1.0.0"
|
|
|
|
# Check paths exist
|
|
assert "/users" in schema["paths"]
|
|
assert "/products" in schema["paths"]
|
|
assert "/orders" in schema["paths"]
|
|
|
|
# Check components/schemas exist
|
|
schemas = schema.get("components", {}).get("schemas", {})
|
|
assert "User" in schemas or "UserBase" in schemas
|
|
assert "Product" in schemas or "ProductBase" in schemas
|
|
|
|
def test_openapi_endpoint_response(self, client: TestClient) -> None:
|
|
"""Test the /openapi.json endpoint returns valid schema."""
|
|
response = client.get("/openapi.json")
|
|
assert response.status_code == 200
|
|
|
|
schema = response.json()
|
|
assert "openapi" in schema
|
|
assert "paths" in schema
|
|
|
|
|
|
class TestOpenAPIPerformanceThresholds:
|
|
"""Tests with performance thresholds."""
|
|
|
|
def test_generation_under_threshold(self, large_app: FastAPI) -> None:
|
|
"""OpenAPI generation should complete under threshold."""
|
|
# Clear any cached schema
|
|
large_app.openapi_schema = None
|
|
|
|
# Multiple runs to get average
|
|
times = []
|
|
for _ in range(3):
|
|
large_app.openapi_schema = None
|
|
start = time.perf_counter()
|
|
large_app.openapi()
|
|
times.append((time.perf_counter() - start) * 1000)
|
|
|
|
avg_time = sum(times) / len(times)
|
|
min_time = min(times)
|
|
max_time = max(times)
|
|
|
|
print(f"\nGeneration times: {[f'{t:.2f}ms' for t in times]}")
|
|
print(f"Average: {avg_time:.2f}ms, Min: {min_time:.2f}ms, Max: {max_time:.2f}ms")
|
|
|
|
# Threshold: should complete in under 5 seconds for 100+ routes
|
|
# This is a baseline - optimizations should improve this
|
|
assert avg_time < 5000, f"Generation took {avg_time:.2f}ms, expected < 5000ms"
|
|
|