Skip to content

Commit 8171648

Browse files
authored
fix(python,asyncio): multipart form data serialization (#19302)
* 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`
1 parent 0026e15 commit 8171648

File tree

48 files changed

+2207
-18
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+2207
-18
lines changed

modules/openapi-generator/src/main/resources/python-pydantic-v1/asyncio/rest.mustache

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,11 @@ class RESTClientObject:
143143
filename=v[0],
144144
content_type=v[2])
145145
else:
146+
# Ensures that dict objects are serialized
147+
if isinstance(v, dict):
148+
v = json.dumps(v)
149+
elif isinstance(v, int):
150+
v = str(v)
146151
data.add_field(k, v)
147152
args["data"] = data
148153

modules/openapi-generator/src/main/resources/python-pydantic-v1/rest.mustache

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,8 @@ class RESTClientObject:
190190
# Content-Type which generated by urllib3 will be
191191
# overwritten.
192192
del headers['Content-Type']
193+
# Ensures that dict objects are serialized
194+
post_params = [(a, json.dumps(b)) if isinstance(b, dict) else (a,b) for a, b in post_params]
193195
r = self.pool_manager.request(
194196
method, url,
195197
fields=post_params,

modules/openapi-generator/src/main/resources/python/asyncio/rest.mustache

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,11 @@ class RESTClientObject:
174174
content_type=v[2]
175175
)
176176
else:
177+
# Ensures that dict objects are serialized
178+
if isinstance(v, dict):
179+
v = json.dumps(v)
180+
elif isinstance(v, int):
181+
v = str(v)
177182
data.add_field(k, v)
178183
args["data"] = data
179184

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

200205
return RESTResponse(r)
201-
202-
203-
204-
205-

modules/openapi-generator/src/test/resources/3_0/python/petstore-with-fake-endpoints-models-for-testing.yaml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1552,6 +1552,41 @@ paths:
15521552
schema:
15531553
type: string
15541554
format: byte
1555+
/fake/upload_file_with_additional_properties:
1556+
post:
1557+
tags:
1558+
- fake
1559+
summary: uploads a file and additional properties using multipart/form-data
1560+
description: ''
1561+
operationId: uploadFileWithAdditionalProperties
1562+
responses:
1563+
'200':
1564+
description: successful operation
1565+
content:
1566+
application/json:
1567+
schema:
1568+
$ref: '#/components/schemas/ApiResponse'
1569+
requestBody:
1570+
content:
1571+
multipart/form-data:
1572+
schema:
1573+
type: object
1574+
properties:
1575+
file:
1576+
description: file to upload
1577+
type: string
1578+
format: binary
1579+
object:
1580+
description: Additional object
1581+
type: object
1582+
properties:
1583+
name:
1584+
type: string
1585+
count:
1586+
description: Integer count
1587+
type: integer
1588+
required:
1589+
- file
15551590
/import_test/return_datetime:
15561591
get:
15571592
tags:

samples/client/echo_api/python-pydantic-v1/openapi_client/rest.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,8 @@ def request(self, method, url, query_params=None, headers=None,
201201
# Content-Type which generated by urllib3 will be
202202
# overwritten.
203203
del headers['Content-Type']
204+
# Ensures that dict objects are serialized
205+
post_params = [(a, json.dumps(b)) if isinstance(b, dict) else (a,b) for a, b in post_params]
204206
r = self.pool_manager.request(
205207
method, url,
206208
fields=post_params,

samples/openapi3/client/petstore/python-aiohttp/.openapi-generator/FILES

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ docs/TestObjectForMultipartRequestsRequestMarker.md
112112
docs/Tiger.md
113113
docs/UnnamedDictWithAdditionalModelListProperties.md
114114
docs/UnnamedDictWithAdditionalStringListProperties.md
115+
docs/UploadFileWithAdditionalPropertiesRequestObject.md
115116
docs/User.md
116117
docs/UserApi.md
117118
docs/WithNestedOneOf.md
@@ -233,6 +234,7 @@ petstore_api/models/test_object_for_multipart_requests_request_marker.py
233234
petstore_api/models/tiger.py
234235
petstore_api/models/unnamed_dict_with_additional_model_list_properties.py
235236
petstore_api/models/unnamed_dict_with_additional_string_list_properties.py
237+
petstore_api/models/upload_file_with_additional_properties_request_object.py
236238
petstore_api/models/user.py
237239
petstore_api/models/with_nested_one_of.py
238240
petstore_api/py.typed

samples/openapi3/client/petstore/python-aiohttp/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ Class | Method | HTTP request | Description
123123
*FakeApi* | [**test_object_for_multipart_requests**](docs/FakeApi.md#test_object_for_multipart_requests) | **POST** /fake/object_for_multipart_requests |
124124
*FakeApi* | [**test_query_parameter_collection_format**](docs/FakeApi.md#test_query_parameter_collection_format) | **PUT** /fake/test-query-parameters |
125125
*FakeApi* | [**test_string_map_reference**](docs/FakeApi.md#test_string_map_reference) | **POST** /fake/stringMap-reference | test referenced string map
126+
*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
126127
*FakeClassnameTags123Api* | [**test_classname**](docs/FakeClassnameTags123Api.md#test_classname) | **PATCH** /fake_classname_test | To test class name in snake case
127128
*ImportTestDatetimeApi* | [**import_test_return_datetime**](docs/ImportTestDatetimeApi.md#import_test_return_datetime) | **GET** /import_test/return_datetime | test date time
128129
*PetApi* | [**add_pet**](docs/PetApi.md#add_pet) | **POST** /pet | Add a new pet to the store
@@ -252,6 +253,7 @@ Class | Method | HTTP request | Description
252253
- [Tiger](docs/Tiger.md)
253254
- [UnnamedDictWithAdditionalModelListProperties](docs/UnnamedDictWithAdditionalModelListProperties.md)
254255
- [UnnamedDictWithAdditionalStringListProperties](docs/UnnamedDictWithAdditionalStringListProperties.md)
256+
- [UploadFileWithAdditionalPropertiesRequestObject](docs/UploadFileWithAdditionalPropertiesRequestObject.md)
255257
- [User](docs/User.md)
256258
- [WithNestedOneOf](docs/WithNestedOneOf.md)
257259

samples/openapi3/client/petstore/python-aiohttp/docs/FakeApi.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ Method | HTTP request | Description
4040
[**test_object_for_multipart_requests**](FakeApi.md#test_object_for_multipart_requests) | **POST** /fake/object_for_multipart_requests |
4141
[**test_query_parameter_collection_format**](FakeApi.md#test_query_parameter_collection_format) | **PUT** /fake/test-query-parameters |
4242
[**test_string_map_reference**](FakeApi.md#test_string_map_reference) | **POST** /fake/stringMap-reference | test referenced string map
43+
[**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
4344

4445

4546
# **fake_any_type_request_body**
@@ -2482,3 +2483,76 @@ No authorization required
24822483

24832484
[[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)
24842485

2486+
# **upload_file_with_additional_properties**
2487+
> ModelApiResponse upload_file_with_additional_properties(file, object=object, count=count)
2488+
2489+
uploads a file and additional properties using multipart/form-data
2490+
2491+
2492+
2493+
### Example
2494+
2495+
2496+
```python
2497+
import petstore_api
2498+
from petstore_api.models.model_api_response import ModelApiResponse
2499+
from petstore_api.models.upload_file_with_additional_properties_request_object import UploadFileWithAdditionalPropertiesRequestObject
2500+
from petstore_api.rest import ApiException
2501+
from pprint import pprint
2502+
2503+
# Defining the host is optional and defaults to http://petstore.swagger.io:80/v2
2504+
# See configuration.py for a list of all supported configuration parameters.
2505+
configuration = petstore_api.Configuration(
2506+
host = "http://petstore.swagger.io:80/v2"
2507+
)
2508+
2509+
2510+
# Enter a context with an instance of the API client
2511+
async with petstore_api.ApiClient(configuration) as api_client:
2512+
# Create an instance of the API class
2513+
api_instance = petstore_api.FakeApi(api_client)
2514+
file = None # bytearray | file to upload
2515+
object = petstore_api.UploadFileWithAdditionalPropertiesRequestObject() # UploadFileWithAdditionalPropertiesRequestObject | (optional)
2516+
count = 56 # int | Integer count (optional)
2517+
2518+
try:
2519+
# uploads a file and additional properties using multipart/form-data
2520+
api_response = await api_instance.upload_file_with_additional_properties(file, object=object, count=count)
2521+
print("The response of FakeApi->upload_file_with_additional_properties:\n")
2522+
pprint(api_response)
2523+
except Exception as e:
2524+
print("Exception when calling FakeApi->upload_file_with_additional_properties: %s\n" % e)
2525+
```
2526+
2527+
2528+
2529+
### Parameters
2530+
2531+
2532+
Name | Type | Description | Notes
2533+
------------- | ------------- | ------------- | -------------
2534+
**file** | **bytearray**| file to upload |
2535+
**object** | [**UploadFileWithAdditionalPropertiesRequestObject**](UploadFileWithAdditionalPropertiesRequestObject.md)| | [optional]
2536+
**count** | **int**| Integer count | [optional]
2537+
2538+
### Return type
2539+
2540+
[**ModelApiResponse**](ModelApiResponse.md)
2541+
2542+
### Authorization
2543+
2544+
No authorization required
2545+
2546+
### HTTP request headers
2547+
2548+
- **Content-Type**: multipart/form-data
2549+
- **Accept**: application/json
2550+
2551+
### HTTP response details
2552+
2553+
| Status code | Description | Response headers |
2554+
|-------------|-------------|------------------|
2555+
**200** | successful operation | - |
2556+
2557+
[[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)
2558+
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# UploadFileWithAdditionalPropertiesRequestObject
2+
3+
Additional object
4+
5+
## Properties
6+
7+
Name | Type | Description | Notes
8+
------------ | ------------- | ------------- | -------------
9+
**name** | **str** | | [optional]
10+
11+
## Example
12+
13+
```python
14+
from petstore_api.models.upload_file_with_additional_properties_request_object import UploadFileWithAdditionalPropertiesRequestObject
15+
16+
# TODO update the JSON string below
17+
json = "{}"
18+
# create an instance of UploadFileWithAdditionalPropertiesRequestObject from a JSON string
19+
upload_file_with_additional_properties_request_object_instance = UploadFileWithAdditionalPropertiesRequestObject.from_json(json)
20+
# print the JSON string representation of the object
21+
print(UploadFileWithAdditionalPropertiesRequestObject.to_json())
22+
23+
# convert the object into a dict
24+
upload_file_with_additional_properties_request_object_dict = upload_file_with_additional_properties_request_object_instance.to_dict()
25+
# create an instance of UploadFileWithAdditionalPropertiesRequestObject from a dict
26+
upload_file_with_additional_properties_request_object_from_dict = UploadFileWithAdditionalPropertiesRequestObject.from_dict(upload_file_with_additional_properties_request_object_dict)
27+
```
28+
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
29+
30+

samples/openapi3/client/petstore/python-aiohttp/petstore_api/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,5 +141,6 @@
141141
from petstore_api.models.tiger import Tiger
142142
from petstore_api.models.unnamed_dict_with_additional_model_list_properties import UnnamedDictWithAdditionalModelListProperties
143143
from petstore_api.models.unnamed_dict_with_additional_string_list_properties import UnnamedDictWithAdditionalStringListProperties
144+
from petstore_api.models.upload_file_with_additional_properties_request_object import UploadFileWithAdditionalPropertiesRequestObject
144145
from petstore_api.models.user import User
145146
from petstore_api.models.with_nested_one_of import WithNestedOneOf

0 commit comments

Comments
 (0)