2
2
3
3
from dataclasses import dataclass , field
4
4
from enum import Enum
5
- from typing import Any , Dict , List , Optional , Set
5
+ from typing import Any , Dict , List , Optional , Set , Union
6
+
7
+ import openapi_schema_pydantic as oai
6
8
7
9
from .errors import ParseError
8
- from .properties import EnumProperty , Property , property_from_dict
10
+ from .properties import EnumProperty , Property , property_from_data
9
11
from .reference import Reference
10
- from .responses import ListRefResponse , RefResponse , Response , response_from_dict
12
+ from .responses import ListRefResponse , RefResponse , Response , response_from_data
11
13
12
14
13
15
class ParameterLocation (str , Enum ):
@@ -32,16 +34,21 @@ class EndpointCollection:
32
34
parse_errors : List [ParseError ] = field (default_factory = list )
33
35
34
36
@staticmethod
35
- def from_dict ( d : Dict [ str , Dict [ str , Dict [str , Any ]] ]) -> Dict [str , EndpointCollection ]:
37
+ def from_data ( * , data : Dict [str , oai . PathItem ]) -> Dict [str , EndpointCollection ]:
36
38
""" Parse the openapi paths data to get EndpointCollections by tag """
37
39
endpoints_by_tag : Dict [str , EndpointCollection ] = {}
38
40
39
- for path , path_data in d .items ():
40
- for method , method_data in path_data .items ():
41
- tag = method_data .get ("tags" , ["default" ])[0 ]
41
+ methods = ["get" , "put" , "post" , "delete" , "options" , "head" , "patch" , "trace" ]
42
+
43
+ for path , path_data in data .items ():
44
+ for method in methods :
45
+ operation : Optional [oai .Operation ] = getattr (path_data , method )
46
+ if operation is None :
47
+ continue
48
+ tag = (operation .tags or ["default" ])[0 ]
42
49
collection = endpoints_by_tag .setdefault (tag , EndpointCollection (tag = tag ))
43
50
try :
44
- endpoint = Endpoint .from_data (data = method_data , path = path , method = method , tag = tag )
51
+ endpoint = Endpoint .from_data (data = operation , path = path , method = method , tag = tag )
45
52
collection .endpoints .append (endpoint )
46
53
collection .relative_imports .update (endpoint .relative_imports )
47
54
except ParseError as e :
@@ -72,40 +79,40 @@ class Endpoint:
72
79
multipart_body_reference : Optional [Reference ] = None
73
80
74
81
@staticmethod
75
- def parse_request_form_body (body : Dict [ str , Any ] ) -> Optional [Reference ]:
82
+ def parse_request_form_body (body : oai . RequestBody ) -> Optional [Reference ]:
76
83
""" Return form_body_reference """
77
- body_content = body [ " content" ]
84
+ body_content = body . content
78
85
form_body = body_content .get ("application/x-www-form-urlencoded" )
79
- if form_body :
80
- return Reference .from_ref (form_body [ "schema" ][ "$ ref" ] )
86
+ if form_body is not None and isinstance ( form_body . media_type_schema , oai . Reference ) :
87
+ return Reference .from_ref (form_body . media_type_schema . ref )
81
88
return None
82
89
83
90
@staticmethod
84
- def parse_multipart_body (body : Dict [ str , Any ] ) -> Optional [Reference ]:
91
+ def parse_multipart_body (body : oai . RequestBody ) -> Optional [Reference ]:
85
92
""" Return form_body_reference """
86
- body_content = body [ " content" ]
87
- body = body_content .get ("multipart/form-data" )
88
- if body :
89
- return Reference .from_ref (body [ "schema" ][ "$ ref" ] )
93
+ body_content = body . content
94
+ json_body = body_content .get ("multipart/form-data" )
95
+ if json_body is not None and isinstance ( json_body . media_type_schema , oai . Reference ) :
96
+ return Reference .from_ref (json_body . media_type_schema . ref )
90
97
return None
91
98
92
99
@staticmethod
93
- def parse_request_json_body (body : Dict [ str , Any ] ) -> Optional [Property ]:
100
+ def parse_request_json_body (body : oai . RequestBody ) -> Optional [Property ]:
94
101
""" Return json_body """
95
- body_content = body [ " content" ]
102
+ body_content = body . content
96
103
json_body = body_content .get ("application/json" )
97
- if json_body :
98
- return property_from_dict ("json_body" , required = True , data = json_body [ "schema" ] )
104
+ if json_body is not None and json_body . media_type_schema is not None :
105
+ return property_from_data ("json_body" , required = True , data = json_body . media_type_schema )
99
106
return None
100
107
101
- def _add_body (self , data : Dict [ str , Any ] ) -> None :
108
+ def _add_body (self , data : oai . Operation ) -> None :
102
109
""" Adds form or JSON body to Endpoint if included in data """
103
- if " requestBody" not in data :
110
+ if data . requestBody is None or isinstance ( data . requestBody , oai . Reference ) :
104
111
return
105
112
106
- self .form_body_reference = Endpoint .parse_request_form_body (data [ " requestBody" ] )
107
- self .json_body = Endpoint .parse_request_json_body (data [ " requestBody" ] )
108
- self .multipart_body_reference = Endpoint .parse_multipart_body (data [ " requestBody" ] )
113
+ self .form_body_reference = Endpoint .parse_request_form_body (data . requestBody )
114
+ self .json_body = Endpoint .parse_request_json_body (data . requestBody )
115
+ self .multipart_body_reference = Endpoint .parse_multipart_body (data . requestBody )
109
116
110
117
if self .form_body_reference :
111
118
self .relative_imports .add (import_string_from_reference (self .form_body_reference , prefix = "..models" ))
@@ -114,41 +121,46 @@ def _add_body(self, data: Dict[str, Any]) -> None:
114
121
if self .json_body is not None :
115
122
self .relative_imports .update (self .json_body .get_imports (prefix = "..models" ))
116
123
117
- def _add_responses (self , data : Dict [ str , Any ] ) -> None :
118
- for code , response_dict in data [ "responses" ] .items ():
119
- response = response_from_dict (status_code = int (code ), data = response_dict )
124
+ def _add_responses (self , data : oai . Responses ) -> None :
125
+ for code , response_data in data .items ():
126
+ response = response_from_data (status_code = int (code ), data = response_data )
120
127
if isinstance (response , (RefResponse , ListRefResponse )):
121
128
self .relative_imports .add (import_string_from_reference (response .reference , prefix = "..models" ))
122
129
self .responses .append (response )
123
130
124
- def _add_parameters (self , data : Dict [str , Any ]) -> None :
125
- for param_dict in data .get ("parameters" , []):
126
- prop = property_from_dict (
127
- name = param_dict ["name" ], required = param_dict ["required" ], data = param_dict ["schema" ]
128
- )
131
+ def _add_parameters (self , data : oai .Operation ) -> None :
132
+ if data .parameters is None :
133
+ return
134
+ for param in data .parameters :
135
+ if isinstance (param , oai .Reference ) or param .param_schema is None :
136
+ continue
137
+ prop = property_from_data (name = param .name , required = param .required , data = param .param_schema )
129
138
self .relative_imports .update (prop .get_imports (prefix = "..models" ))
130
139
131
- if param_dict [ "in" ] == ParameterLocation .QUERY :
140
+ if param . param_in == ParameterLocation .QUERY :
132
141
self .query_parameters .append (prop )
133
- elif param_dict [ "in" ] == ParameterLocation .PATH :
142
+ elif param . param_in == ParameterLocation .PATH :
134
143
self .path_parameters .append (prop )
135
144
else :
136
- raise ValueError (f"Don't know where to put this parameter: { param_dict } " )
145
+ raise ValueError (f"Don't know where to put this parameter: { param . dict () } " )
137
146
138
147
@staticmethod
139
- def from_data (* , data : Dict [ str , Any ] , path : str , method : str , tag : str ) -> Endpoint :
148
+ def from_data (* , data : oai . Operation , path : str , method : str , tag : str ) -> Endpoint :
140
149
""" Construct an endpoint from the OpenAPI data """
141
150
151
+ if data .operationId is None :
152
+ raise ParseError (data = data , message = "Path operations with operationId are not yet supported" )
153
+
142
154
endpoint = Endpoint (
143
155
path = path ,
144
156
method = method ,
145
- description = data .get ( " description" ) ,
146
- name = data [ " operationId" ] ,
147
- requires_security = bool (data .get ( " security" ) ),
157
+ description = data .description ,
158
+ name = data . operationId ,
159
+ requires_security = bool (data .security ),
148
160
tag = tag ,
149
161
)
150
162
endpoint ._add_parameters (data )
151
- endpoint ._add_responses (data )
163
+ endpoint ._add_responses (data . responses )
152
164
endpoint ._add_body (data )
153
165
154
166
return endpoint
@@ -169,24 +181,26 @@ class Schema:
169
181
relative_imports : Set [str ]
170
182
171
183
@staticmethod
172
- def from_dict ( d : Dict [ str , Any ], name : str ) -> Schema :
173
- """ A single Schema from its dict representation
184
+ def from_data ( * , data : Union [ oai . Reference , oai . Schema ], name : str ) -> Schema :
185
+ """ A single Schema from its OAI data
174
186
175
187
Args:
176
- d: Dict representation of the schema
188
+ data: Data of a single Schema
177
189
name: Name by which the schema is referenced, such as a model name.
178
190
Used to infer the type name if a `title` property is not available.
179
191
"""
180
- required_set = set (d .get ("required" , []))
192
+ if isinstance (data , oai .Reference ):
193
+ raise ParseError ("Reference schemas are not supported." )
194
+ required_set = set (data .required or [])
181
195
required_properties : List [Property ] = []
182
196
optional_properties : List [Property ] = []
183
197
relative_imports : Set [str ] = set ()
184
198
185
- ref = Reference .from_ref (d . get ( " title" , name ) )
199
+ ref = Reference .from_ref (data . title or name )
186
200
187
- for key , value in d . get ( " properties" , {}).items ():
201
+ for key , value in ( data . properties or {}).items ():
188
202
required = key in required_set
189
- p = property_from_dict (name = key , required = required , data = value )
203
+ p = property_from_data (name = key , required = required , data = value )
190
204
if required :
191
205
required_properties .append (p )
192
206
else :
@@ -198,23 +212,23 @@ def from_dict(d: Dict[str, Any], name: str) -> Schema:
198
212
required_properties = required_properties ,
199
213
optional_properties = optional_properties ,
200
214
relative_imports = relative_imports ,
201
- description = d . get ( " description" , "" ) ,
215
+ description = data . description or "" ,
202
216
)
203
217
return schema
204
218
205
219
@staticmethod
206
- def dict ( d : Dict [str , Dict [ str , Any ]]) -> Dict [str , Schema ]:
220
+ def build ( * , schemas : Dict [str , Union [ oai . Reference , oai . Schema ]]) -> Dict [str , Schema ]:
207
221
""" Get a list of Schemas from an OpenAPI dict """
208
222
result = {}
209
- for name , data in d .items ():
210
- s = Schema .from_dict ( data , name = name )
223
+ for name , data in schemas .items ():
224
+ s = Schema .from_data ( data = data , name = name )
211
225
result [s .reference .class_name ] = s
212
226
return result
213
227
214
228
215
229
@dataclass
216
- class OpenAPI :
217
- """ Top level OpenAPI document """
230
+ class GeneratorData :
231
+ """ All the data needed to generate a client """
218
232
219
233
title : str
220
234
description : Optional [str ]
@@ -224,16 +238,20 @@ class OpenAPI:
224
238
enums : Dict [str , EnumProperty ]
225
239
226
240
@staticmethod
227
- def from_dict (d : Dict [str , Dict [str , Any ]]) -> OpenAPI :
241
+ def from_dict (d : Dict [str , Dict [str , Any ]]) -> GeneratorData :
228
242
""" Create an OpenAPI from dict """
229
- schemas = Schema .dict (d ["components" ]["schemas" ])
230
- endpoint_collections_by_tag = EndpointCollection .from_dict (d ["paths" ])
243
+ openapi = oai .OpenAPI .parse_obj (d )
244
+ if openapi .components is None or openapi .components .schemas is None :
245
+ schemas = {}
246
+ else :
247
+ schemas = Schema .build (schemas = openapi .components .schemas )
248
+ endpoint_collections_by_tag = EndpointCollection .from_data (data = openapi .paths )
231
249
enums = EnumProperty .get_all_enums ()
232
250
233
- return OpenAPI (
234
- title = d [ " info" ][ " title" ] ,
235
- description = d [ " info" ]. get ( " description" ) ,
236
- version = d [ " info" ][ " version" ] ,
251
+ return GeneratorData (
252
+ title = openapi . info . title ,
253
+ description = openapi . info . description ,
254
+ version = openapi . info . version ,
237
255
endpoint_collections_by_tag = endpoint_collections_by_tag ,
238
256
schemas = schemas ,
239
257
enums = enums ,
0 commit comments