Skip to content

Commit 2421642

Browse files
authored
fix(template): Properly strip out UNSET values from form data [#430]. Thanks @p1-ra!
* templates / endpoint_module / form_data: do not use `attr.asdict`, use <Model>.to_dict()` serializer - to handle internal UNSET values to not serialize them, `attr.asdict` do not know UNSET type and will serialize them * e2e / update golden-record
1 parent 0f58cfd commit 2421642

File tree

5 files changed

+177
-2
lines changed

5 files changed

+177
-2
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
from typing import Any, Dict
2+
3+
import httpx
4+
5+
from ...client import Client
6+
from ...models.a_form_data import AFormData
7+
from ...types import Response
8+
9+
10+
def _get_kwargs(
11+
*,
12+
client: Client,
13+
form_data: AFormData,
14+
) -> Dict[str, Any]:
15+
url = "{}/tests/post_form_data".format(client.base_url)
16+
17+
headers: Dict[str, Any] = client.get_headers()
18+
cookies: Dict[str, Any] = client.get_cookies()
19+
20+
return {
21+
"url": url,
22+
"headers": headers,
23+
"cookies": cookies,
24+
"timeout": client.get_timeout(),
25+
"data": form_data.to_dict(),
26+
}
27+
28+
29+
def _build_response(*, response: httpx.Response) -> Response[None]:
30+
return Response(
31+
status_code=response.status_code,
32+
content=response.content,
33+
headers=response.headers,
34+
parsed=None,
35+
)
36+
37+
38+
def sync_detailed(
39+
*,
40+
client: Client,
41+
form_data: AFormData,
42+
) -> Response[None]:
43+
kwargs = _get_kwargs(
44+
client=client,
45+
form_data=form_data,
46+
)
47+
48+
response = httpx.post(
49+
**kwargs,
50+
)
51+
52+
return _build_response(response=response)
53+
54+
55+
async def asyncio_detailed(
56+
*,
57+
client: Client,
58+
form_data: AFormData,
59+
) -> Response[None]:
60+
kwargs = _get_kwargs(
61+
client=client,
62+
form_data=form_data,
63+
)
64+
65+
async with httpx.AsyncClient() as _client:
66+
response = await _client.post(**kwargs)
67+
68+
return _build_response(response=response)

end_to_end_tests/golden-record/my_test_api_client/models/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
""" Contains all the data models used in inputs/outputs """
22

3+
from .a_form_data import AFormData
34
from .a_model import AModel
45
from .a_model_with_properties_reference_that_are_not_object import AModelWithPropertiesReferenceThatAreNotObject
56
from .all_of_sub_model import AllOfSubModel
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
from typing import Any, Dict, List, Type, TypeVar, Union
2+
3+
import attr
4+
5+
from ..types import UNSET, Unset
6+
7+
T = TypeVar("T", bound="AFormData")
8+
9+
10+
@attr.s(auto_attribs=True)
11+
class AFormData:
12+
""" """
13+
14+
an_required_field: str
15+
an_optional_field: Union[Unset, str] = UNSET
16+
additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict)
17+
18+
def to_dict(self) -> Dict[str, Any]:
19+
an_required_field = self.an_required_field
20+
an_optional_field = self.an_optional_field
21+
22+
field_dict: Dict[str, Any] = {}
23+
field_dict.update(self.additional_properties)
24+
field_dict.update(
25+
{
26+
"an_required_field": an_required_field,
27+
}
28+
)
29+
if an_optional_field is not UNSET:
30+
field_dict["an_optional_field"] = an_optional_field
31+
32+
return field_dict
33+
34+
@classmethod
35+
def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
36+
d = src_dict.copy()
37+
an_required_field = d.pop("an_required_field")
38+
39+
an_optional_field = d.pop("an_optional_field", UNSET)
40+
41+
a_form_data = cls(
42+
an_required_field=an_required_field,
43+
an_optional_field=an_optional_field,
44+
)
45+
46+
a_form_data.additional_properties = d
47+
return a_form_data
48+
49+
@property
50+
def additional_keys(self) -> List[str]:
51+
return list(self.additional_properties.keys())
52+
53+
def __getitem__(self, key: str) -> Any:
54+
return self.additional_properties[key]
55+
56+
def __setitem__(self, key: str, value: Any) -> None:
57+
self.additional_properties[key] = value
58+
59+
def __delitem__(self, key: str) -> None:
60+
del self.additional_properties[key]
61+
62+
def __contains__(self, key: str) -> bool:
63+
return key in self.additional_properties

end_to_end_tests/openapi.json

+44
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,36 @@
188188
}
189189
}
190190
},
191+
"/tests/post_form_data": {
192+
"post": {
193+
"tags": [
194+
"tests"
195+
],
196+
"sumnary": "Post from data",
197+
"description": "Post form data",
198+
"operationId": "post_form_data",
199+
"requestBody": {
200+
"content": {
201+
"application/x-www-form-urlencoded": {
202+
"schema": {
203+
"$ref": "#/components/schemas/AFormData"
204+
}
205+
}
206+
},
207+
"required": true
208+
},
209+
"responses": {
210+
"200": {
211+
"description": "Successful Response",
212+
"content": {
213+
"application/json": {
214+
"schema": {}
215+
}
216+
}
217+
}
218+
}
219+
}
220+
},
191221
"/tests/upload": {
192222
"post": {
193223
"tags": [
@@ -797,6 +827,20 @@
797827
},
798828
"components": {
799829
"schemas": {
830+
"AFormData": {
831+
"type": "object",
832+
"properties": {
833+
"an_optional_field": {
834+
"type": "string"
835+
},
836+
"an_required_field": {
837+
"type": "string"
838+
}
839+
},
840+
"required": [
841+
"an_required_field"
842+
]
843+
},
800844
"AModel": {
801845
"title": "AModel",
802846
"required": [

openapi_python_client/templates/endpoint_module.py.jinja

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from typing import Any, Dict, List, Optional, Union, cast
22

33
import httpx
4-
from attr import asdict
54

65
from ...client import AuthenticatedClient, Client
76
from ...types import Response, UNSET{% if endpoint.multipart_body_class %}, File {% endif %}
@@ -52,7 +51,7 @@ def _get_kwargs(
5251
"cookies": cookies,
5352
"timeout": client.get_timeout(),
5453
{% if endpoint.form_body_class %}
55-
"data": asdict(form_data),
54+
"data": form_data.to_dict(),
5655
{% elif endpoint.multipart_body_class %}
5756
"files": files,
5857
"data": data,

0 commit comments

Comments
 (0)