Skip to content

Commit

Permalink
fix(python,asyncio): multipart form data serialization (#19302)
Browse files Browse the repository at this point in the history
* fix: object serialization for multipart requests

This PR is essentially
<#18140> but for
the asyncio client.

* fix: int serialization for multipart requests

urllib3 handles serializing ints in post params (ref 1), while asyncio
explicitly does not (ref 2).

ref 1: <https://github.com/urllib3/urllib3/blob/9316764e90aea8d193cd8f03b0caccdf02af3ba0/src/urllib3/filepost.py#L75-L76>
ref 2: <aio-libs/aiohttp#920>

* test: new fake multipart endpoint with files and body

* test: regression test for stringified body params

* fix: mypy tweak

* fix: FILES regeneration

* feat: object, int serialization for multipart reqs

Extends previous commits (and #18140) to cover the python-pydantic-v1
client as well.

* fix: use async with in test

* test: regression test for pydantic-v1-aiohttp

* test: add regression test to pydantic-v1

Also brings the second test in line with the first, patching
`urllib3.PoolManager.urlopen`
  • Loading branch information
roryschadler authored Sep 9, 2024
1 parent 0026e15 commit 8171648
Show file tree
Hide file tree
Showing 48 changed files with 2,207 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,11 @@ class RESTClientObject:
filename=v[0],
content_type=v[2])
else:
# Ensures that dict objects are serialized
if isinstance(v, dict):
v = json.dumps(v)
elif isinstance(v, int):
v = str(v)
data.add_field(k, v)
args["data"] = data

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,8 @@ class RESTClientObject:
# Content-Type which generated by urllib3 will be
# overwritten.
del headers['Content-Type']
# Ensures that dict objects are serialized
post_params = [(a, json.dumps(b)) if isinstance(b, dict) else (a,b) for a, b in post_params]
r = self.pool_manager.request(
method, url,
fields=post_params,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,11 @@ class RESTClientObject:
content_type=v[2]
)
else:
# Ensures that dict objects are serialized
if isinstance(v, dict):
v = json.dumps(v)
elif isinstance(v, int):
v = str(v)
data.add_field(k, v)
args["data"] = data

Expand All @@ -198,8 +203,3 @@ class RESTClientObject:
r = await pool_manager.request(**args)

return RESTResponse(r)





Original file line number Diff line number Diff line change
Expand Up @@ -1552,6 +1552,41 @@ paths:
schema:
type: string
format: byte
/fake/upload_file_with_additional_properties:
post:
tags:
- fake
summary: uploads a file and additional properties using multipart/form-data
description: ''
operationId: uploadFileWithAdditionalProperties
responses:
'200':
description: successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/ApiResponse'
requestBody:
content:
multipart/form-data:
schema:
type: object
properties:
file:
description: file to upload
type: string
format: binary
object:
description: Additional object
type: object
properties:
name:
type: string
count:
description: Integer count
type: integer
required:
- file
/import_test/return_datetime:
get:
tags:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,8 @@ def request(self, method, url, query_params=None, headers=None,
# Content-Type which generated by urllib3 will be
# overwritten.
del headers['Content-Type']
# Ensures that dict objects are serialized
post_params = [(a, json.dumps(b)) if isinstance(b, dict) else (a,b) for a, b in post_params]
r = self.pool_manager.request(
method, url,
fields=post_params,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ docs/TestObjectForMultipartRequestsRequestMarker.md
docs/Tiger.md
docs/UnnamedDictWithAdditionalModelListProperties.md
docs/UnnamedDictWithAdditionalStringListProperties.md
docs/UploadFileWithAdditionalPropertiesRequestObject.md
docs/User.md
docs/UserApi.md
docs/WithNestedOneOf.md
Expand Down Expand Up @@ -233,6 +234,7 @@ petstore_api/models/test_object_for_multipart_requests_request_marker.py
petstore_api/models/tiger.py
petstore_api/models/unnamed_dict_with_additional_model_list_properties.py
petstore_api/models/unnamed_dict_with_additional_string_list_properties.py
petstore_api/models/upload_file_with_additional_properties_request_object.py
petstore_api/models/user.py
petstore_api/models/with_nested_one_of.py
petstore_api/py.typed
Expand Down
2 changes: 2 additions & 0 deletions samples/openapi3/client/petstore/python-aiohttp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ Class | Method | HTTP request | Description
*FakeApi* | [**test_object_for_multipart_requests**](docs/FakeApi.md#test_object_for_multipart_requests) | **POST** /fake/object_for_multipart_requests |
*FakeApi* | [**test_query_parameter_collection_format**](docs/FakeApi.md#test_query_parameter_collection_format) | **PUT** /fake/test-query-parameters |
*FakeApi* | [**test_string_map_reference**](docs/FakeApi.md#test_string_map_reference) | **POST** /fake/stringMap-reference | test referenced string map
*FakeApi* | [**upload_file_with_additional_properties**](docs/FakeApi.md#upload_file_with_additional_properties) | **POST** /fake/upload_file_with_additional_properties | uploads a file and additional properties using multipart/form-data
*FakeClassnameTags123Api* | [**test_classname**](docs/FakeClassnameTags123Api.md#test_classname) | **PATCH** /fake_classname_test | To test class name in snake case
*ImportTestDatetimeApi* | [**import_test_return_datetime**](docs/ImportTestDatetimeApi.md#import_test_return_datetime) | **GET** /import_test/return_datetime | test date time
*PetApi* | [**add_pet**](docs/PetApi.md#add_pet) | **POST** /pet | Add a new pet to the store
Expand Down Expand Up @@ -252,6 +253,7 @@ Class | Method | HTTP request | Description
- [Tiger](docs/Tiger.md)
- [UnnamedDictWithAdditionalModelListProperties](docs/UnnamedDictWithAdditionalModelListProperties.md)
- [UnnamedDictWithAdditionalStringListProperties](docs/UnnamedDictWithAdditionalStringListProperties.md)
- [UploadFileWithAdditionalPropertiesRequestObject](docs/UploadFileWithAdditionalPropertiesRequestObject.md)
- [User](docs/User.md)
- [WithNestedOneOf](docs/WithNestedOneOf.md)

Expand Down
74 changes: 74 additions & 0 deletions samples/openapi3/client/petstore/python-aiohttp/docs/FakeApi.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ Method | HTTP request | Description
[**test_object_for_multipart_requests**](FakeApi.md#test_object_for_multipart_requests) | **POST** /fake/object_for_multipart_requests |
[**test_query_parameter_collection_format**](FakeApi.md#test_query_parameter_collection_format) | **PUT** /fake/test-query-parameters |
[**test_string_map_reference**](FakeApi.md#test_string_map_reference) | **POST** /fake/stringMap-reference | test referenced string map
[**upload_file_with_additional_properties**](FakeApi.md#upload_file_with_additional_properties) | **POST** /fake/upload_file_with_additional_properties | uploads a file and additional properties using multipart/form-data


# **fake_any_type_request_body**
Expand Down Expand Up @@ -2482,3 +2483,76 @@ No authorization required

[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)

# **upload_file_with_additional_properties**
> ModelApiResponse upload_file_with_additional_properties(file, object=object, count=count)
uploads a file and additional properties using multipart/form-data



### Example


```python
import petstore_api
from petstore_api.models.model_api_response import ModelApiResponse
from petstore_api.models.upload_file_with_additional_properties_request_object import UploadFileWithAdditionalPropertiesRequestObject
from petstore_api.rest import ApiException
from pprint import pprint

# Defining the host is optional and defaults to http://petstore.swagger.io:80/v2
# See configuration.py for a list of all supported configuration parameters.
configuration = petstore_api.Configuration(
host = "http://petstore.swagger.io:80/v2"
)


# Enter a context with an instance of the API client
async with petstore_api.ApiClient(configuration) as api_client:
# Create an instance of the API class
api_instance = petstore_api.FakeApi(api_client)
file = None # bytearray | file to upload
object = petstore_api.UploadFileWithAdditionalPropertiesRequestObject() # UploadFileWithAdditionalPropertiesRequestObject | (optional)
count = 56 # int | Integer count (optional)

try:
# uploads a file and additional properties using multipart/form-data
api_response = await api_instance.upload_file_with_additional_properties(file, object=object, count=count)
print("The response of FakeApi->upload_file_with_additional_properties:\n")
pprint(api_response)
except Exception as e:
print("Exception when calling FakeApi->upload_file_with_additional_properties: %s\n" % e)
```



### Parameters


Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**file** | **bytearray**| file to upload |
**object** | [**UploadFileWithAdditionalPropertiesRequestObject**](UploadFileWithAdditionalPropertiesRequestObject.md)| | [optional]
**count** | **int**| Integer count | [optional]

### Return type

[**ModelApiResponse**](ModelApiResponse.md)

### Authorization

No authorization required

### HTTP request headers

- **Content-Type**: multipart/form-data
- **Accept**: application/json

### HTTP response details

| Status code | Description | Response headers |
|-------------|-------------|------------------|
**200** | successful operation | - |

[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)

Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# UploadFileWithAdditionalPropertiesRequestObject

Additional object

## Properties

Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**name** | **str** | | [optional]

## Example

```python
from petstore_api.models.upload_file_with_additional_properties_request_object import UploadFileWithAdditionalPropertiesRequestObject

# TODO update the JSON string below
json = "{}"
# create an instance of UploadFileWithAdditionalPropertiesRequestObject from a JSON string
upload_file_with_additional_properties_request_object_instance = UploadFileWithAdditionalPropertiesRequestObject.from_json(json)
# print the JSON string representation of the object
print(UploadFileWithAdditionalPropertiesRequestObject.to_json())

# convert the object into a dict
upload_file_with_additional_properties_request_object_dict = upload_file_with_additional_properties_request_object_instance.to_dict()
# create an instance of UploadFileWithAdditionalPropertiesRequestObject from a dict
upload_file_with_additional_properties_request_object_from_dict = UploadFileWithAdditionalPropertiesRequestObject.from_dict(upload_file_with_additional_properties_request_object_dict)
```
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)


Original file line number Diff line number Diff line change
Expand Up @@ -141,5 +141,6 @@
from petstore_api.models.tiger import Tiger
from petstore_api.models.unnamed_dict_with_additional_model_list_properties import UnnamedDictWithAdditionalModelListProperties
from petstore_api.models.unnamed_dict_with_additional_string_list_properties import UnnamedDictWithAdditionalStringListProperties
from petstore_api.models.upload_file_with_additional_properties_request_object import UploadFileWithAdditionalPropertiesRequestObject
from petstore_api.models.user import User
from petstore_api.models.with_nested_one_of import WithNestedOneOf
Loading

0 comments on commit 8171648

Please sign in to comment.