Coverage for .venv/lib/python3.13/site-packages/litellm/proxy/common_utils/openapi_schema_compat.py: 54%
31 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 12:01 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 12:01 +0000
1"""
2Utility module for handling OpenAPI schema generation compatibility with FastAPI 0.120+.
4FastAPI 0.120+ has stricter schema generation that fails on certain types like openai.Timeout.
5This module provides a compatibility layer to handle these cases gracefully.
6"""
8from typing import Any, Final
10from litellm._logging import verbose_proxy_logger
13def get_openapi_schema_with_compat(
14 get_openapi_func,
15 title: str,
16 version: str,
17 description: str,
18 routes: list,
19) -> dict[str, Any]:
20 """
21 Generate OpenAPI schema with compatibility handling for FastAPI 0.120+.
23 This function patches Pydantic's schema generation to handle non-serializable types
24 like openai.Timeout that cause PydanticSchemaGenerationError in FastAPI 0.120+.
26 Args:
27 get_openapi_func: The FastAPI get_openapi function
28 title: API title
29 version: API version
30 description: API description
31 routes: List of routes
33 Returns:
34 OpenAPI schema dictionary
35 """
36 # FastAPI 0.120+ may fail schema generation for certain types (e.g., openai.Timeout)
37 # Patch Pydantic's schema generation to handle unknown types gracefully
38 try:
39 from pydantic._internal._generate_schema import GenerateSchema
40 from pydantic_core import core_schema
42 # Store original method
43 original_unknown_type_schema: Final = GenerateSchema._unknown_type_schema
45 def patched_unknown_type_schema(self, obj):
46 """Patch to handle openai.Timeout and other non-serializable types"""
47 # Check if it's openai.Timeout or similar types
48 obj_str: Final = str(obj)
49 obj_module: Final = getattr(obj, "__module__", "")
51 if (obj_module == "openai" and "Timeout" in obj_str) or ( 51 ↛ 55line 51 didn't jump to line 55 because the condition on line 51 was never true
52 hasattr(obj, "__name__") and obj.__name__ == "Timeout" and obj_module == "openai"
53 ):
54 # Return a simple string schema for Timeout types
55 return core_schema.str_schema()
57 # For other unknown types, try to return a default schema
58 # This prevents the error from propagating
59 try:
60 return core_schema.any_schema()
61 except Exception:
62 # Last resort: return string schema
63 return core_schema.str_schema()
65 # Apply patch
66 setattr(GenerateSchema, "_unknown_type_schema", patched_unknown_type_schema)
68 try:
69 openapi_schema: Final = get_openapi_func(
70 title=title,
71 version=version,
72 description=description,
73 routes=routes,
74 )
75 finally:
76 # Restore original method
77 setattr(GenerateSchema, "_unknown_type_schema", original_unknown_type_schema)
79 return openapi_schema
81 except (ImportError, AttributeError) as e:
82 # If patching fails, try normal generation with error handling
83 verbose_proxy_logger.debug("Could not patch Pydantic schema generation: %s. Trying normal generation.", e)
84 try:
85 return get_openapi_func(
86 title=title,
87 version=version,
88 description=description,
89 routes=routes,
90 )
91 except Exception as pydantic_error:
92 # Check if it's a PydanticSchemaGenerationError by checking the error type name
93 # This avoids import issues if PydanticSchemaGenerationError is not available
94 error_type_name: Final = type(pydantic_error).__name__
95 if error_type_name == "PydanticSchemaGenerationError" or "PydanticSchemaGenerationError" in str(
96 type(pydantic_error)
97 ):
98 # If we still get the error, log it and return minimal schema
99 verbose_proxy_logger.warning(
100 "PydanticSchemaGenerationError during schema generation: %s", pydantic_error
101 )
102 return {
103 "openapi": "3.0.0",
104 "info": {
105 "title": title,
106 "version": version,
107 "description": description or "",
108 },
109 "paths": {},
110 "components": {"schemas": {}},
111 }
112 else:
113 # Re-raise if it's a different error
114 raise