1
- __all__ = ["Response" , "response_from_data" ]
1
+ __all__ = ["HTTPStatusPattern" , " Response" , "response_from_data" ]
2
2
3
- from http import HTTPStatus
4
3
from typing import Optional , TypedDict , Union
5
4
6
5
from attrs import define
@@ -28,15 +27,85 @@ class _ResponseSource(TypedDict):
28
27
NONE_SOURCE = _ResponseSource (attribute = "None" , return_type = "None" )
29
28
30
29
31
- @define
30
+ class HTTPStatusPattern :
31
+ """Status code patterns come in three flavors, in order of precedence:
32
+ 1. Specific status codes, such as 200. This is represented by `min` and `max` being the same.
33
+ 2. Ranges of status codes, such as 2XX. This is represented by `min` and `max` being different.
34
+ 3. The special `default` status code, which is used when no other status codes match. `range` is `None` in this case.
35
+
36
+ https://github.com/openapi-generators/openapi-python-client/blob/61b6c54994e2a6285bb422ee3b864c45b5d88c15/openapi_python_client/schema/3.1.0.md#responses-object
37
+ """
38
+
39
+ pattern : str
40
+ range : Optional [tuple [int , int ]]
41
+
42
+ def __init__ (self , * , pattern : str , code_range : Optional [tuple [int , int ]]):
43
+ """Initialize with a range of status codes or None for the default case."""
44
+ self .pattern = pattern
45
+ self .range = code_range
46
+
47
+ @staticmethod
48
+ def parse (pattern : str ) -> Union ["HTTPStatusPattern" , ParseError ]:
49
+ """Parse a status code pattern such as 2XX or 404"""
50
+ if pattern == "default" :
51
+ return HTTPStatusPattern (pattern = pattern , code_range = None )
52
+
53
+ if pattern .endswith ("XX" ) and pattern [0 ].isdigit ():
54
+ first_digit = int (pattern [0 ])
55
+ return HTTPStatusPattern (pattern = pattern , code_range = (first_digit * 100 , first_digit * 100 + 99 ))
56
+
57
+ try :
58
+ code = int (pattern )
59
+ return HTTPStatusPattern (pattern = pattern , code_range = (code , code ))
60
+ except ValueError :
61
+ return ParseError (
62
+ detail = (
63
+ f"Invalid response status code pattern: { pattern } , response will be omitted from generated client"
64
+ )
65
+ )
66
+
67
+ def is_range (self ) -> bool :
68
+ """Check if this is a range of status codes, such as 2XX"""
69
+ return self .range is not None and self .range [0 ] != self .range [1 ]
70
+
71
+ def __lt__ (self , other : "HTTPStatusPattern" ) -> bool :
72
+ """Compare two HTTPStatusPattern objects based on the order they should be applied in"""
73
+ if self .range is None :
74
+ return False # Default gets applied last
75
+ if other .range is None :
76
+ return True # Other is default, so this one gets applied first
77
+
78
+ # Specific codes appear before ranges
79
+ if self .is_range () and not other .is_range ():
80
+ return False
81
+ if not self .is_range () and other .is_range ():
82
+ return True
83
+
84
+ # Order specific codes numerically
85
+ return self .range [0 ] < other .range [0 ]
86
+
87
+ def __eq__ (self , other : object ) -> bool :
88
+ if not isinstance (other , HTTPStatusPattern ):
89
+ return False
90
+ return self .range == other .range
91
+
92
+ def __hash__ (self ) -> int :
93
+ return hash (self .range )
94
+
95
+
96
+ @define (order = False )
32
97
class Response :
33
98
"""Describes a single response for an endpoint"""
34
99
35
- status_code : HTTPStatus
100
+ status_code : HTTPStatusPattern
36
101
prop : Property
37
102
source : _ResponseSource
38
103
data : Union [oai .Response , oai .Reference ] # Original data which created this response, useful for custom templates
39
104
105
+ def __lt__ (self , other : "Response" ) -> bool :
106
+ """Compare two responses based on the order in which they should be applied in"""
107
+ return self .status_code < other .status_code
108
+
40
109
41
110
def _source_by_content_type (content_type : str , config : Config ) -> Optional [_ResponseSource ]:
42
111
parsed_content_type = utils .get_content_type (content_type , config )
@@ -59,7 +128,7 @@ def _source_by_content_type(content_type: str, config: Config) -> Optional[_Resp
59
128
60
129
def empty_response (
61
130
* ,
62
- status_code : HTTPStatus ,
131
+ status_code : HTTPStatusPattern ,
63
132
response_name : str ,
64
133
config : Config ,
65
134
data : Union [oai .Response , oai .Reference ],
@@ -82,7 +151,7 @@ def empty_response(
82
151
83
152
def response_from_data ( # noqa: PLR0911
84
153
* ,
85
- status_code : HTTPStatus ,
154
+ status_code : HTTPStatusPattern ,
86
155
data : Union [oai .Response , oai .Reference ],
87
156
schemas : Schemas ,
88
157
responses : dict [str , Union [oai .Response , oai .Reference ]],
@@ -91,7 +160,7 @@ def response_from_data( # noqa: PLR0911
91
160
) -> tuple [Union [Response , ParseError ], Schemas ]:
92
161
"""Generate a Response from the OpenAPI dictionary representation of it"""
93
162
94
- response_name = f"response_{ status_code } "
163
+ response_name = f"response_{ status_code . pattern } "
95
164
if isinstance (data , oai .Reference ):
96
165
ref_path = parse_reference_path (data .ref )
97
166
if isinstance (ref_path , ParseError ):
0 commit comments