Skip to content

Commit f469553

Browse files
Cherry-pick optional params
1 parent b0109cd commit f469553

File tree

18 files changed

+254
-464
lines changed

18 files changed

+254
-464
lines changed

end_to_end_tests/openapi.json

Lines changed: 40 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -290,12 +290,49 @@
290290
{
291291
"required": false,
292292
"schema": {
293-
"title": "Datetime Prop",
293+
"title": "Not Required, Not Nullable Datetime Prop",
294+
"nullable": false,
294295
"type": "string",
295296
"format": "date-time",
296297
"default": "1010-10-10T00:00:00"
297298
},
298-
"name": "datetime_prop",
299+
"name": "not_required_not_nullable_datetime_prop",
300+
"in": "query"
301+
},
302+
{
303+
"required": false,
304+
"schema": {
305+
"title": "Not Required, Nullable Datetime Prop",
306+
"nullable": true,
307+
"type": "string",
308+
"format": "date-time",
309+
"default": "1010-10-10T00:00:00"
310+
},
311+
"name": "not_required_nullable_datetime_prop",
312+
"in": "query"
313+
},
314+
{
315+
"required": true,
316+
"schema": {
317+
"title": "Required, Not Nullable Datetime Prop",
318+
"nullable": false,
319+
"type": "string",
320+
"format": "date-time",
321+
"default": "1010-10-10T00:00:00"
322+
},
323+
"name": "required_not_nullable_datetime_prop",
324+
"in": "query"
325+
},
326+
{
327+
"required": true,
328+
"schema": {
329+
"title": "Required, Nullable Datetime Prop",
330+
"nullable": true,
331+
"type": "string",
332+
"format": "date-time",
333+
"default": "1010-10-10T00:00:00"
334+
},
335+
"name": "required_nullable_datetime_prop",
299336
"in": "query"
300337
},
301338
{
@@ -396,14 +433,6 @@
396433
},
397434
"name": "enum_prop",
398435
"in": "query"
399-
},
400-
{
401-
"required": false,
402-
"schema": {
403-
"$ref": "#/components/schemas/ModelWithUnionProperty"
404-
},
405-
"name": "model_prop",
406-
"in": "query"
407436
}
408437
],
409438
"responses": {
@@ -630,7 +659,7 @@
630659
"schemas": {
631660
"AModel": {
632661
"title": "AModel",
633-
"required": ["an_enum_value", "aCamelDateTime", "a_date", "a_nullable_date", "required_nullable", "required_not_nullable", "model", "nullable_model"],
662+
"required": ["an_enum_value", "aCamelDateTime", "a_date", "a_nullable_date", "required_nullable", "required_not_nullable"],
634663
"type": "object",
635664
"properties": {
636665
"an_enum_value": {
@@ -694,42 +723,6 @@
694723
"title": "NOT Required AND NOT Nullable",
695724
"type": "string",
696725
"nullable": false
697-
},
698-
"model": {
699-
"type": "object",
700-
"allOf": [
701-
{
702-
"ref": "#/components/schemas/ModelWithUnionProperty"
703-
}
704-
],
705-
"nullable": false
706-
},
707-
"nullable_model": {
708-
"type": "object",
709-
"allOf": [
710-
{
711-
"ref": "#/components/schemas/ModelWithUnionProperty"
712-
}
713-
],
714-
"nullable": true
715-
},
716-
"not_required_model": {
717-
"type": "object",
718-
"allOf": [
719-
{
720-
"ref": "#/components/schemas/ModelWithUnionProperty"
721-
}
722-
],
723-
"nullable": false
724-
},
725-
"not_required_nullable_model": {
726-
"type": "object",
727-
"allOf": [
728-
{
729-
"ref": "#/components/schemas/ModelWithUnionProperty"
730-
}
731-
],
732-
"nullable": true
733726
}
734727
},
735728
"description": "A Model for testing all the ways custom objects can be used ",

openapi_python_client/parser/properties/__init__.py

Lines changed: 48 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ class NoneProperty(Property):
1919
""" A property that is always None (used for empty schemas) """
2020

2121
_type_string: ClassVar[str] = "None"
22+
_json_type_string: ClassVar[str] = "None"
2223
template: ClassVar[Optional[str]] = "none_property.pyi"
2324

2425

@@ -29,6 +30,7 @@ class StringProperty(Property):
2930
max_length: Optional[int] = None
3031
pattern: Optional[str] = None
3132
_type_string: ClassVar[str] = "str"
33+
_json_type_string: ClassVar[str] = "str"
3234

3335

3436
@attr.s(auto_attribs=True, frozen=True)
@@ -38,6 +40,7 @@ class DateTimeProperty(Property):
3840
"""
3941

4042
_type_string: ClassVar[str] = "datetime.datetime"
43+
_json_type_string: ClassVar[str] = "str"
4144
template: ClassVar[str] = "datetime_property.pyi"
4245

4346
def get_imports(self, *, prefix: str) -> Set[str]:
@@ -58,6 +61,7 @@ class DateProperty(Property):
5861
""" A property of type datetime.date """
5962

6063
_type_string: ClassVar[str] = "datetime.date"
64+
_json_type_string: ClassVar[str] = "str"
6165
template: ClassVar[str] = "date_property.pyi"
6266

6367
def get_imports(self, *, prefix: str) -> Set[str]:
@@ -78,6 +82,8 @@ class FileProperty(Property):
7882
""" A property used for uploading files """
7983

8084
_type_string: ClassVar[str] = "File"
85+
# Return type of File.to_tuple()
86+
_json_type_string: ClassVar[str] = "Tuple[Optional[str], Union[BinaryIO, TextIO], Optional[str]]"
8187
template: ClassVar[str] = "file_property.pyi"
8288

8389
def get_imports(self, *, prefix: str) -> Set[str]:
@@ -98,20 +104,23 @@ class FloatProperty(Property):
98104
""" A property of type float """
99105

100106
_type_string: ClassVar[str] = "float"
107+
_json_type_string: ClassVar[str] = "float"
101108

102109

103110
@attr.s(auto_attribs=True, frozen=True)
104111
class IntProperty(Property):
105112
""" A property of type int """
106113

107114
_type_string: ClassVar[str] = "int"
115+
_json_type_string: ClassVar[str] = "int"
108116

109117

110118
@attr.s(auto_attribs=True, frozen=True)
111119
class BooleanProperty(Property):
112120
""" Property for bool """
113121

114122
_type_string: ClassVar[str] = "bool"
123+
_json_type_string: ClassVar[str] = "bool"
115124

116125

117126
InnerProp = TypeVar("InnerProp", bound=Property)
@@ -122,18 +131,11 @@ class ListProperty(Property, Generic[InnerProp]):
122131
""" A property representing a list (array) of other properties """
123132

124133
inner_property: InnerProp
134+
_json_type_string: ClassVar[str] = "List[Any]"
125135
template: ClassVar[str] = "list_property.pyi"
126136

127-
def get_type_string(self, no_optional: bool = False) -> str:
128-
""" Get a string representation of type that should be used when declaring this property """
129-
type_string = f"List[{self.inner_property.get_type_string()}]"
130-
if no_optional:
131-
return type_string
132-
if self.nullable:
133-
type_string = f"Optional[{type_string}]"
134-
if not self.required:
135-
type_string = f"Union[Unset, {type_string}]"
136-
return type_string
137+
def get_base_type_string(self) -> str:
138+
return f"List[{self.inner_property.get_type_string()}]"
137139

138140
def get_instance_type_string(self) -> str:
139141
"""Get a string representation of runtime type that should be used for `isinstance` checks"""
@@ -167,18 +169,38 @@ def __attrs_post_init__(self) -> None:
167169
self, "has_properties_without_templates", any(prop.template is None for prop in self.inner_properties)
168170
)
169171

170-
def get_type_string(self, no_optional: bool = False) -> str:
171-
""" Get a string representation of type that should be used when declaring this property """
172-
inner_types = [p.get_type_string(no_optional=True) for p in self.inner_properties]
173-
inner_prop_string = ", ".join(inner_types)
174-
type_string = f"Union[{inner_prop_string}]"
172+
def _get_inner_prop_string(self, json: bool = False) -> str:
173+
inner_types = [p.get_type_string(no_optional=True, json=json) for p in self.inner_properties]
174+
unique_inner_types = list(dict.fromkeys(inner_types))
175+
return ", ".join(unique_inner_types)
176+
177+
def get_base_type_string(self, json: bool = False) -> str:
178+
return f"Union[{self._get_inner_prop_string(json=json)}]"
179+
180+
def get_type_string(self, no_optional: bool = False, query_parameter: bool = False, json: bool = False) -> str:
181+
"""
182+
Get a string representation of type that should be used when declaring this property.
183+
184+
This implementation differs slightly from `Property.get_type_string` in order to collapse
185+
nested union types.
186+
"""
187+
type_string = self.get_base_type_string(json=json)
175188
if no_optional:
176189
return type_string
177-
if not self.required:
178-
type_string = f"Union[Unset, {inner_prop_string}]"
179-
if self.nullable:
180-
type_string = f"Optional[{type_string}]"
181-
return type_string
190+
if self.required:
191+
if self.nullable:
192+
return f"Union[None, {self._get_inner_prop_string(json=json)}]"
193+
else:
194+
return type_string
195+
else:
196+
if self.nullable:
197+
return f"Union[Unset, None, {self._get_inner_prop_string(json=json)}]"
198+
else:
199+
if query_parameter:
200+
# For query parameters, None has the same meaning as Unset
201+
return f"Union[Unset, None, {self._get_inner_prop_string(json=json)}]"
202+
else:
203+
return f"Union[Unset, {self._get_inner_prop_string(json=json)}]"
182204

183205
def get_imports(self, *, prefix: str) -> Set[str]:
184206
"""
@@ -250,23 +272,13 @@ def build_model_property(
250272
required_properties: List[Property] = []
251273
optional_properties: List[Property] = []
252274
relative_imports: Set[str] = set()
253-
references: List[oai.Reference] = []
254275

255276
class_name = data.title or name
256277
if parent_name:
257278
class_name = f"{utils.pascal_case(parent_name)}{utils.pascal_case(class_name)}"
258279
ref = Reference.from_ref(class_name)
259280

260-
all_props = data.properties or {}
261-
if not isinstance(data, oai.Reference) and data.allOf:
262-
for sub_prop in data.allOf:
263-
if isinstance(sub_prop, oai.Reference):
264-
references += [sub_prop]
265-
else:
266-
all_props.update(sub_prop.properties or {})
267-
required_set.update(sub_prop.required or [])
268-
269-
for key, value in all_props.items():
281+
for key, value in (data.properties or {}).items():
270282
prop_required = key in required_set
271283
prop, schemas = property_from_data(
272284
name=key, required=prop_required, data=value, schemas=schemas, parent_name=class_name
@@ -302,7 +314,6 @@ def build_model_property(
302314

303315
prop = ModelProperty(
304316
reference=ref,
305-
references=references,
306317
required_properties=required_properties,
307318
optional_properties=optional_properties,
308319
relative_imports=relative_imports,
@@ -460,6 +471,9 @@ def _property_from_data(
460471
)
461472
if data.anyOf or data.oneOf:
462473
return build_union_property(data=data, name=name, required=required, schemas=schemas, parent_name=parent_name)
474+
if not data.type:
475+
return NoneProperty(name=name, required=required, nullable=False, default=None), schemas
476+
463477
if data.type == "string":
464478
return _string_based_property(name=name, required=required, data=data), schemas
465479
elif data.type == "number":
@@ -494,10 +508,8 @@ def _property_from_data(
494508
)
495509
elif data.type == "array":
496510
return build_list_property(data=data, name=name, required=required, schemas=schemas, parent_name=parent_name)
497-
elif data.type == "object" or data.allOf:
511+
elif data.type == "object":
498512
return build_model_property(data=data, name=name, schemas=schemas, required=required, parent_name=parent_name)
499-
elif not data.type:
500-
return NoneProperty(name=name, required=required, nullable=False, default=None), schemas
501513
return PropertyError(data=data, detail=f"unknown type {data.type}"), schemas
502514

503515

@@ -554,16 +566,6 @@ def build_schemas(*, components: Dict[str, Union[oai.Reference, oai.Schema]]) ->
554566
schemas = schemas_or_err
555567
processing = True # We made some progress this round, do another after it's done
556568
to_process = next_round
557-
558-
resolve_errors: List[PropertyError] = []
559-
models = list(schemas.models.values())
560-
for model in models:
561-
schemas_or_err = model.resolve_references(components=components, schemas=schemas)
562-
if isinstance(schemas_or_err, PropertyError):
563-
resolve_errors.append(schemas_or_err)
564-
else:
565-
schemas = schemas_or_err
566-
567569
schemas.errors.extend(errors)
568-
schemas.errors.extend(resolve_errors)
570+
569571
return schemas

openapi_python_client/parser/properties/enum_property.py

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,21 +18,13 @@ class EnumProperty(Property):
1818
values: Dict[str, ValueType]
1919
reference: Reference
2020
value_type: Type[ValueType]
21+
_json_type_string: ClassVar[str] = "int"
2122
default: Optional[Any] = attr.ib()
2223

2324
template: ClassVar[str] = "enum_property.pyi"
2425

25-
def get_type_string(self, no_optional: bool = False) -> str:
26-
""" Get a string representation of type that should be used when declaring this property """
27-
28-
type_string = self.reference.class_name
29-
if no_optional:
30-
return type_string
31-
if self.nullable:
32-
type_string = f"Optional[{type_string}]"
33-
if not self.required:
34-
type_string = f"Union[Unset, {type_string}]"
35-
return type_string
26+
def get_base_type_string(self) -> str:
27+
return self.reference.class_name
3628

3729
def get_imports(self, *, prefix: str) -> Set[str]:
3830
"""

0 commit comments

Comments
 (0)