22
33from dataclasses import dataclass , field
44from 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
68
79from .errors import ParseError
8- from .properties import EnumProperty , Property , property_from_dict
10+ from .properties import EnumProperty , Property , property_from_data
911from .reference import Reference
10- from .responses import ListRefResponse , RefResponse , Response , response_from_dict
12+ from .responses import ListRefResponse , RefResponse , Response , response_from_data
1113
1214
1315class ParameterLocation (str , Enum ):
@@ -32,16 +34,21 @@ class EndpointCollection:
3234 parse_errors : List [ParseError ] = field (default_factory = list )
3335
3436 @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 ]:
3638 """ Parse the openapi paths data to get EndpointCollections by tag """
3739 endpoints_by_tag : Dict [str , EndpointCollection ] = {}
3840
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 ]
4249 collection = endpoints_by_tag .setdefault (tag , EndpointCollection (tag = tag ))
4350 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 )
4552 collection .endpoints .append (endpoint )
4653 collection .relative_imports .update (endpoint .relative_imports )
4754 except ParseError as e :
@@ -72,40 +79,40 @@ class Endpoint:
7279 multipart_body_reference : Optional [Reference ] = None
7380
7481 @staticmethod
75- def parse_request_form_body (body : Dict [ str , Any ] ) -> Optional [Reference ]:
82+ def parse_request_form_body (body : oai . RequestBody ) -> Optional [Reference ]:
7683 """ Return form_body_reference """
77- body_content = body [ " content" ]
84+ body_content = body . content
7885 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 )
8188 return None
8289
8390 @staticmethod
84- def parse_multipart_body (body : Dict [ str , Any ] ) -> Optional [Reference ]:
91+ def parse_multipart_body (body : oai . RequestBody ) -> Optional [Reference ]:
8592 """ 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 )
9097 return None
9198
9299 @staticmethod
93- def parse_request_json_body (body : Dict [ str , Any ] ) -> Optional [Property ]:
100+ def parse_request_json_body (body : oai . RequestBody ) -> Optional [Property ]:
94101 """ Return json_body """
95- body_content = body [ " content" ]
102+ body_content = body . content
96103 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 )
99106 return None
100107
101- def _add_body (self , data : Dict [ str , Any ] ) -> None :
108+ def _add_body (self , data : oai . Operation ) -> None :
102109 """ 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 ) :
104111 return
105112
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 )
109116
110117 if self .form_body_reference :
111118 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:
114121 if self .json_body is not None :
115122 self .relative_imports .update (self .json_body .get_imports (prefix = "..models" ))
116123
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 )
120127 if isinstance (response , (RefResponse , ListRefResponse )):
121128 self .relative_imports .add (import_string_from_reference (response .reference , prefix = "..models" ))
122129 self .responses .append (response )
123130
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 )
129138 self .relative_imports .update (prop .get_imports (prefix = "..models" ))
130139
131- if param_dict [ "in" ] == ParameterLocation .QUERY :
140+ if param . param_in == ParameterLocation .QUERY :
132141 self .query_parameters .append (prop )
133- elif param_dict [ "in" ] == ParameterLocation .PATH :
142+ elif param . param_in == ParameterLocation .PATH :
134143 self .path_parameters .append (prop )
135144 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 () } " )
137146
138147 @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 :
140149 """ Construct an endpoint from the OpenAPI data """
141150
151+ if data .operationId is None :
152+ raise ParseError (data = data , message = "Path operations with operationId are not yet supported" )
153+
142154 endpoint = Endpoint (
143155 path = path ,
144156 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 ),
148160 tag = tag ,
149161 )
150162 endpoint ._add_parameters (data )
151- endpoint ._add_responses (data )
163+ endpoint ._add_responses (data . responses )
152164 endpoint ._add_body (data )
153165
154166 return endpoint
@@ -169,24 +181,26 @@ class Schema:
169181 relative_imports : Set [str ]
170182
171183 @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
174186
175187 Args:
176- d: Dict representation of the schema
188+ data: Data of a single Schema
177189 name: Name by which the schema is referenced, such as a model name.
178190 Used to infer the type name if a `title` property is not available.
179191 """
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 [])
181195 required_properties : List [Property ] = []
182196 optional_properties : List [Property ] = []
183197 relative_imports : Set [str ] = set ()
184198
185- ref = Reference .from_ref (d . get ( " title" , name ) )
199+ ref = Reference .from_ref (data . title or name )
186200
187- for key , value in d . get ( " properties" , {}).items ():
201+ for key , value in ( data . properties or {}).items ():
188202 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 )
190204 if required :
191205 required_properties .append (p )
192206 else :
@@ -198,23 +212,23 @@ def from_dict(d: Dict[str, Any], name: str) -> Schema:
198212 required_properties = required_properties ,
199213 optional_properties = optional_properties ,
200214 relative_imports = relative_imports ,
201- description = d . get ( " description" , "" ) ,
215+ description = data . description or "" ,
202216 )
203217 return schema
204218
205219 @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 ]:
207221 """ Get a list of Schemas from an OpenAPI dict """
208222 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 )
211225 result [s .reference .class_name ] = s
212226 return result
213227
214228
215229@dataclass
216- class OpenAPI :
217- """ Top level OpenAPI document """
230+ class GeneratorData :
231+ """ All the data needed to generate a client """
218232
219233 title : str
220234 description : Optional [str ]
@@ -224,16 +238,20 @@ class OpenAPI:
224238 enums : Dict [str , EnumProperty ]
225239
226240 @staticmethod
227- def from_dict (d : Dict [str , Dict [str , Any ]]) -> OpenAPI :
241+ def from_dict (d : Dict [str , Dict [str , Any ]]) -> GeneratorData :
228242 """ 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 )
231249 enums = EnumProperty .get_all_enums ()
232250
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 ,
237255 endpoint_collections_by_tag = endpoint_collections_by_tag ,
238256 schemas = schemas ,
239257 enums = enums ,
0 commit comments