Skip to content

Fix nullable & required properties in multipart bodies #995

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
May 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
default: patch
---

# Fix nullable and required properties in multipart bodies

Fixes #926.

> [!WARNING]
> This change is likely to break custom templates. Multipart body handling has been completely split from JSON bodies.
46 changes: 30 additions & 16 deletions end_to_end_tests/baseline_openapi_3.0.json
Original file line number Diff line number Diff line change
Expand Up @@ -1415,7 +1415,9 @@
},
"/naming/mixed-case": {
"get": {
"tags": ["naming"],
"tags": [
"naming"
],
"operationId": "mixed_case",
"parameters": [
{
Expand All @@ -1436,30 +1438,32 @@
}
],
"responses": {
"200": {
"description": "Successful response",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"mixed_case": {
"type": "string"
},
"mixedCase": {
"type": "string"
}
"200": {
"description": "Successful response",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"mixed_case": {
"type": "string"
},
"mixedCase": {
"type": "string"
}
}
}
}
}
}
}
}
},
"/naming/{hyphen-in-path}": {
"get": {
"tags": ["naming"],
"tags": [
"naming"
],
"operationId": "hyphen_in_path",
"parameters": [
{
Expand Down Expand Up @@ -1863,7 +1867,8 @@
"required": [
"some_file",
"some_object",
"some_nullable_object"
"some_nullable_object",
"some_required_number"
],
"type": "object",
"properties": {
Expand Down Expand Up @@ -1896,6 +1901,15 @@
"title": "Some Number",
"type": "number"
},
"some_nullable_number": {
"title": "Some Nullable Number",
"type": "number",
"nullable": true
},
"some_required_number": {
"title": "Some Required Number",
"type": "number"
},
"some_array": {
"title": "Some Array",
"nullable": true,
Expand Down
11 changes: 10 additions & 1 deletion end_to_end_tests/baseline_openapi_3.1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1877,7 +1877,8 @@ info:
"required": [
"some_file",
"some_object",
"some_nullable_object"
"some_nullable_object",
"some_required_number"
],
"type": "object",
"properties": {
Expand Down Expand Up @@ -1910,6 +1911,14 @@ info:
"title": "Some Number",
"type": "number"
},
"some_nullable_number": {
"title": "Some Nullable Number",
"type": [ "number", "null" ]
},
"some_required_number": {
"title": "Some Number",
"type": "number"
},
"some_array": {
"title": "Some Array",
"type": [ "array", "null" ],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,26 +32,30 @@ class BodyUploadFileTestsUploadPost:
"""
Attributes:
some_file (File):
some_required_number (float):
some_object (BodyUploadFileTestsUploadPostSomeObject):
some_nullable_object (Union['BodyUploadFileTestsUploadPostSomeNullableObject', None]):
some_optional_file (Union[Unset, File]):
some_string (Union[Unset, str]): Default: 'some_default_string'.
a_datetime (Union[Unset, datetime.datetime]):
a_date (Union[Unset, datetime.date]):
some_number (Union[Unset, float]):
some_nullable_number (Union[None, Unset, float]):
some_array (Union[List['AFormData'], None, Unset]):
some_optional_object (Union[Unset, BodyUploadFileTestsUploadPostSomeOptionalObject]):
some_enum (Union[Unset, DifferentEnum]): An enumeration.
"""

some_file: File
some_required_number: float
some_object: "BodyUploadFileTestsUploadPostSomeObject"
some_nullable_object: Union["BodyUploadFileTestsUploadPostSomeNullableObject", None]
some_optional_file: Union[Unset, File] = UNSET
some_string: Union[Unset, str] = "some_default_string"
a_datetime: Union[Unset, datetime.datetime] = UNSET
a_date: Union[Unset, datetime.date] = UNSET
some_number: Union[Unset, float] = UNSET
some_nullable_number: Union[None, Unset, float] = UNSET
some_array: Union[List["AFormData"], None, Unset] = UNSET
some_optional_object: Union[Unset, "BodyUploadFileTestsUploadPostSomeOptionalObject"] = UNSET
some_enum: Union[Unset, DifferentEnum] = UNSET
Expand All @@ -66,6 +70,8 @@ def to_dict(self) -> Dict[str, Any]:

some_file = self.some_file.to_tuple()

some_required_number = self.some_required_number

some_object = self.some_object.to_dict()

some_nullable_object: Union[Dict[str, Any], None]
Expand All @@ -90,6 +96,12 @@ def to_dict(self) -> Dict[str, Any]:

some_number = self.some_number

some_nullable_number: Union[None, Unset, float]
if isinstance(self.some_nullable_number, Unset):
some_nullable_number = UNSET
else:
some_nullable_number = self.some_nullable_number

some_array: Union[List[Dict[str, Any]], None, Unset]
if isinstance(self.some_array, Unset):
some_array = UNSET
Expand All @@ -116,6 +128,7 @@ def to_dict(self) -> Dict[str, Any]:
field_dict.update(
{
"some_file": some_file,
"some_required_number": some_required_number,
"some_object": some_object,
"some_nullable_object": some_nullable_object,
}
Expand All @@ -130,6 +143,8 @@ def to_dict(self) -> Dict[str, Any]:
field_dict["a_date"] = a_date
if some_number is not UNSET:
field_dict["some_number"] = some_number
if some_nullable_number is not UNSET:
field_dict["some_nullable_number"] = some_nullable_number
if some_array is not UNSET:
field_dict["some_array"] = some_array
if some_optional_object is not UNSET:
Expand All @@ -142,13 +157,16 @@ def to_dict(self) -> Dict[str, Any]:
def to_multipart(self) -> Dict[str, Any]:
some_file = self.some_file.to_tuple()

some_required_number = (None, str(self.some_required_number).encode(), "text/plain")

some_object = (None, json.dumps(self.some_object.to_dict()).encode(), "application/json")

some_nullable_object: Union[None, Tuple[None, bytes, str]]
some_nullable_object: Tuple[None, bytes, str]

if isinstance(self.some_nullable_object, BodyUploadFileTestsUploadPostSomeNullableObject):
some_nullable_object = (None, json.dumps(self.some_nullable_object.to_dict()).encode(), "application/json")
else:
some_nullable_object = self.some_nullable_object
some_nullable_object = (None, str(self.some_nullable_object).encode(), "text/plain")

some_optional_file: Union[Unset, FileJsonType] = UNSET
if not isinstance(self.some_optional_file, Unset):
Expand All @@ -174,7 +192,17 @@ def to_multipart(self) -> Dict[str, Any]:
else (None, str(self.some_number).encode(), "text/plain")
)

some_array: Union[None, Tuple[None, bytes, str], Unset]
some_nullable_number: Union[Tuple[None, bytes, str], Unset]

if isinstance(self.some_nullable_number, Unset):
some_nullable_number = UNSET
elif isinstance(self.some_nullable_number, float):
some_nullable_number = (None, str(self.some_nullable_number).encode(), "text/plain")
else:
some_nullable_number = (None, str(self.some_nullable_number).encode(), "text/plain")

some_array: Union[Tuple[None, bytes, str], Unset]

if isinstance(self.some_array, Unset):
some_array = UNSET
elif isinstance(self.some_array, list):
Expand All @@ -183,9 +211,8 @@ def to_multipart(self) -> Dict[str, Any]:
some_array_type_0_item = some_array_type_0_item_data.to_dict()
_temp_some_array.append(some_array_type_0_item)
some_array = (None, json.dumps(_temp_some_array).encode(), "application/json")

else:
some_array = self.some_array
some_array = (None, str(self.some_array).encode(), "text/plain")

some_optional_object: Union[Unset, Tuple[None, bytes, str]] = UNSET
if not isinstance(self.some_optional_object, Unset):
Expand All @@ -201,6 +228,7 @@ def to_multipart(self) -> Dict[str, Any]:
field_dict.update(
{
"some_file": some_file,
"some_required_number": some_required_number,
"some_object": some_object,
"some_nullable_object": some_nullable_object,
}
Expand All @@ -215,6 +243,8 @@ def to_multipart(self) -> Dict[str, Any]:
field_dict["a_date"] = a_date
if some_number is not UNSET:
field_dict["some_number"] = some_number
if some_nullable_number is not UNSET:
field_dict["some_nullable_number"] = some_nullable_number
if some_array is not UNSET:
field_dict["some_array"] = some_array
if some_optional_object is not UNSET:
Expand All @@ -241,6 +271,8 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
d = src_dict.copy()
some_file = File(payload=BytesIO(d.pop("some_file")))

some_required_number = d.pop("some_required_number")

some_object = BodyUploadFileTestsUploadPostSomeObject.from_dict(d.pop("some_object"))

def _parse_some_nullable_object(data: object) -> Union["BodyUploadFileTestsUploadPostSomeNullableObject", None]:
Expand Down Expand Up @@ -283,6 +315,15 @@ def _parse_some_nullable_object(data: object) -> Union["BodyUploadFileTestsUploa

some_number = d.pop("some_number", UNSET)

def _parse_some_nullable_number(data: object) -> Union[None, Unset, float]:
if data is None:
return data
if isinstance(data, Unset):
return data
return cast(Union[None, Unset, float], data)

some_nullable_number = _parse_some_nullable_number(d.pop("some_nullable_number", UNSET))

def _parse_some_array(data: object) -> Union[List["AFormData"], None, Unset]:
if data is None:
return data
Expand Down Expand Up @@ -321,13 +362,15 @@ def _parse_some_array(data: object) -> Union[List["AFormData"], None, Unset]:

body_upload_file_tests_upload_post = cls(
some_file=some_file,
some_required_number=some_required_number,
some_object=some_object,
some_nullable_object=some_nullable_object,
some_optional_file=some_optional_file,
some_string=some_string,
a_datetime=a_datetime,
a_date=a_date,
some_number=some_number,
some_nullable_number=some_nullable_number,
some_array=some_array,
some_optional_object=some_optional_object,
some_enum=some_enum,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ def to_multipart(self) -> Dict[str, Any]:
a = self.a if isinstance(self.a, Unset) else (None, str(self.a).encode(), "text/plain")

field_dict: Dict[str, Any] = {}
field_dict.update(
{key: (None, str(value).encode(), "text/plain") for key, value in self.additional_properties.items()}
)
for prop_name, prop in self.additional_properties.items():
field_dict[prop_name] = (None, str(prop).encode(), "text/plain")

field_dict.update({})
if a is not UNSET:
field_dict["a"] = a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,7 @@ def to_dict(self) -> Dict[str, Any]:
return field_dict

def to_multipart(self) -> Dict[str, Any]:
a_string = (
self.a_string if isinstance(self.a_string, Unset) else (None, str(self.a_string).encode(), "text/plain")
)
a_string = (None, str(self.a_string).encode(), "text/plain")

file = self.file.to_tuple()

Expand All @@ -58,9 +56,9 @@ def to_multipart(self) -> Dict[str, Any]:
)

field_dict: Dict[str, Any] = {}
field_dict.update(
{key: (None, str(value).encode(), "text/plain") for key, value in self.additional_properties.items()}
)
for prop_name, prop in self.additional_properties.items():
field_dict[prop_name] = (None, str(prop).encode(), "text/plain")

field_dict.update(
{
"a_string": a_string,
Expand Down
Loading
Loading