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