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

1""" 

2Utility module for handling OpenAPI schema generation compatibility with FastAPI 0.120+. 

3 

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""" 

7 

8from typing import Any, Final 

9 

10from litellm._logging import verbose_proxy_logger 

11 

12 

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+. 

22 

23 This function patches Pydantic's schema generation to handle non-serializable types 

24 like openai.Timeout that cause PydanticSchemaGenerationError in FastAPI 0.120+. 

25 

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 

32 

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 

41 

42 # Store original method 

43 original_unknown_type_schema: Final = GenerateSchema._unknown_type_schema 

44 

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__", "") 

50 

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() 

56 

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() 

64 

65 # Apply patch 

66 setattr(GenerateSchema, "_unknown_type_schema", patched_unknown_type_schema) 

67 

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) 

78 

79 return openapi_schema 

80 

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