Skip to content

Commit 296ffb6

Browse files
emanndbanty
andauthored
Better compatibility for "required" vs. "nullable" (#230)
* Added support for UNSET values and better differentiation between required and nullable. * Models to_dict methods will not include any values which are UNSET Co-authored-by: Ethan Mann <emann@triaxtec.com> Co-authored-by: Dylan Anthony <danthony@triaxtec.com>
1 parent 7a483dc commit 296ffb6

34 files changed

+888
-249
lines changed

CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## 0.7.0 - Unreleased
99

10+
### Breaking Changes
11+
12+
- Any request/response field that is not `required` and wasn't specified is now set to `UNSET` instead of `None`.
13+
- Values that are `UNSET` will not be sent along in API calls
14+
1015
### Additions
1116

1217
- Added a `--custom-template-path` option for providing custom jinja2 templates (#231 - Thanks @erichulburd!).
18+
- Better compatibility for "required" (whether or not the field must be included) and "nullable" (whether or not the field can be null) (#205 & #208). Thanks @bowenwr & @emannguitar!
1319

1420
## 0.6.2 - 2020-11-03
1521

end_to_end_tests/golden-record-custom/my_test_api_client/api/tests/defaults_tests_defaults_post.py

+33-26
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
from ...models.an_enum import AnEnum
1313
from ...models.http_validation_error import HTTPValidationError
14+
from ...types import UNSET, Unset
1415

1516

1617
def _parse_response(*, response: httpx.Response) -> Optional[Union[None, HTTPValidationError]]:
@@ -34,57 +35,63 @@ def httpx_request(
3435
*,
3536
client: Client,
3637
json_body: Dict[Any, Any],
37-
string_prop: Optional[str] = "the default string",
38-
datetime_prop: Optional[datetime.datetime] = isoparse("1010-10-10T00:00:00"),
39-
date_prop: Optional[datetime.date] = isoparse("1010-10-10").date(),
40-
float_prop: Optional[float] = 3.14,
41-
int_prop: Optional[int] = 7,
42-
boolean_prop: Optional[bool] = False,
43-
list_prop: Optional[List[AnEnum]] = None,
44-
union_prop: Optional[Union[Optional[float], Optional[str]]] = "not a float",
45-
enum_prop: Optional[AnEnum] = None,
38+
string_prop: Union[Unset, str] = "the default string",
39+
datetime_prop: Union[Unset, datetime.datetime] = isoparse("1010-10-10T00:00:00"),
40+
date_prop: Union[Unset, datetime.date] = isoparse("1010-10-10").date(),
41+
float_prop: Union[Unset, float] = 3.14,
42+
int_prop: Union[Unset, int] = 7,
43+
boolean_prop: Union[Unset, bool] = False,
44+
list_prop: Union[Unset, List[AnEnum]] = UNSET,
45+
union_prop: Union[Unset, float, str] = "not a float",
46+
enum_prop: Union[Unset, AnEnum] = UNSET,
4647
) -> httpx.Response[Union[None, HTTPValidationError]]:
4748

48-
json_datetime_prop = datetime_prop.isoformat() if datetime_prop else None
49+
json_datetime_prop: Union[Unset, str] = UNSET
50+
if not isinstance(datetime_prop, Unset):
51+
json_datetime_prop = datetime_prop.isoformat()
4952

50-
json_date_prop = date_prop.isoformat() if date_prop else None
53+
json_date_prop: Union[Unset, str] = UNSET
54+
if not isinstance(date_prop, Unset):
55+
json_date_prop = date_prop.isoformat()
5156

52-
if list_prop is None:
53-
json_list_prop = None
54-
else:
57+
json_list_prop: Union[Unset, List[Any]] = UNSET
58+
if not isinstance(list_prop, Unset):
5559
json_list_prop = []
5660
for list_prop_item_data in list_prop:
5761
list_prop_item = list_prop_item_data.value
5862

5963
json_list_prop.append(list_prop_item)
6064

61-
if union_prop is None:
62-
json_union_prop: Optional[Union[Optional[float], Optional[str]]] = None
65+
json_union_prop: Union[Unset, float, str]
66+
if isinstance(union_prop, Unset):
67+
json_union_prop = UNSET
6368
elif isinstance(union_prop, float):
6469
json_union_prop = union_prop
6570
else:
6671
json_union_prop = union_prop
6772

68-
json_enum_prop = enum_prop.value if enum_prop else None
73+
json_enum_prop: Union[Unset, AnEnum] = UNSET
74+
if not isinstance(enum_prop, Unset):
75+
json_enum_prop = enum_prop.value
6976

7077
params: Dict[str, Any] = {}
71-
if string_prop is not None:
78+
if string_prop is not UNSET:
7279
params["string_prop"] = string_prop
73-
if datetime_prop is not None:
80+
if datetime_prop is not UNSET:
7481
params["datetime_prop"] = json_datetime_prop
75-
if date_prop is not None:
82+
if date_prop is not UNSET:
7683
params["date_prop"] = json_date_prop
77-
if float_prop is not None:
84+
if float_prop is not UNSET:
7885
params["float_prop"] = float_prop
79-
if int_prop is not None:
86+
if int_prop is not UNSET:
8087
params["int_prop"] = int_prop
81-
if boolean_prop is not None:
88+
if boolean_prop is not UNSET:
8289
params["boolean_prop"] = boolean_prop
83-
if list_prop is not None:
90+
if list_prop is not UNSET:
8491
params["list_prop"] = json_list_prop
85-
if union_prop is not None:
92+
if union_prop is not UNSET:
8693
params["union_prop"] = json_union_prop
87-
if enum_prop is not None:
94+
if enum_prop is not UNSET:
8895
params["enum_prop"] = json_enum_prop
8996

9097
json_json_body = json_body
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
from typing import Optional
2+
3+
import httpx
4+
5+
Client = httpx.Client
6+
7+
from typing import List, Optional, Union
8+
9+
from ...models.http_validation_error import HTTPValidationError
10+
from ...types import UNSET, Unset
11+
12+
13+
def _parse_response(*, response: httpx.Response) -> Optional[Union[None, HTTPValidationError]]:
14+
if response.status_code == 200:
15+
return None
16+
if response.status_code == 422:
17+
return HTTPValidationError.from_dict(cast(Dict[str, Any], response.json()))
18+
return None
19+
20+
21+
def _build_response(*, response: httpx.Response) -> httpx.Response[Union[None, HTTPValidationError]]:
22+
return httpx.Response(
23+
status_code=response.status_code,
24+
content=response.content,
25+
headers=response.headers,
26+
parsed=_parse_response(response=response),
27+
)
28+
29+
30+
def httpx_request(
31+
*,
32+
client: Client,
33+
query_param: Union[Unset, List[str]] = UNSET,
34+
) -> httpx.Response[Union[None, HTTPValidationError]]:
35+
36+
json_query_param: Union[Unset, List[Any]] = UNSET
37+
if not isinstance(query_param, Unset):
38+
json_query_param = query_param
39+
40+
params: Dict[str, Any] = {}
41+
if query_param is not UNSET:
42+
params["query_param"] = json_query_param
43+
44+
response = client.request(
45+
"get",
46+
"/tests/optional_query_param/",
47+
params=params,
48+
)
49+
50+
return _build_response(response=response)

end_to_end_tests/golden-record-custom/my_test_api_client/api/tests/upload_file_tests_upload_post.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44

55
Client = httpx.Client
66

7-
from typing import Optional
7+
from typing import Optional, Union
88

99
from ...models.body_upload_file_tests_upload_post import BodyUploadFileTestsUploadPost
1010
from ...models.http_validation_error import HTTPValidationError
11+
from ...types import UNSET, Unset
1112

1213

1314
def _parse_response(*, response: httpx.Response) -> Optional[Union[
@@ -37,12 +38,12 @@ def _build_response(*, response: httpx.Response) -> httpx.Response[Union[
3738
def httpx_request(*,
3839
client: Client,
3940
multipart_data: BodyUploadFileTestsUploadPost,
40-
keep_alive: Optional[bool] = None,
41+
keep_alive: Union[Unset, bool] = UNSET,
4142
) -> httpx.Response[Union[
4243
None,
4344
HTTPValidationError
4445
]]:
45-
if keep_alive is not None:
46+
if keep_alive is not UNSET:
4647
headers["keep-alive"] = keep_alive
4748

4849

end_to_end_tests/golden-record-custom/my_test_api_client/models/a_model.py

+47-17
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,27 @@
66

77
from ..models.an_enum import AnEnum
88
from ..models.different_enum import DifferentEnum
9+
from ..types import UNSET, Unset
910

1011

1112
@attr.s(auto_attribs=True)
1213
class AModel:
1314
""" A Model for testing all the ways custom objects can be used """
1415

1516
an_enum_value: AnEnum
16-
some_dict: Optional[Dict[Any, Any]]
1717
a_camel_date_time: Union[datetime.datetime, datetime.date]
1818
a_date: datetime.date
19-
nested_list_of_enums: Optional[List[List[DifferentEnum]]] = None
20-
attr_1_leading_digit: Optional[str] = None
19+
required_not_nullable: str
20+
some_dict: Optional[Dict[Any, Any]]
21+
required_nullable: Optional[str]
22+
nested_list_of_enums: Union[Unset, List[List[DifferentEnum]]] = UNSET
23+
attr_1_leading_digit: Union[Unset, str] = UNSET
24+
not_required_nullable: Union[Unset, Optional[str]] = UNSET
25+
not_required_not_nullable: Union[Unset, str] = UNSET
2126

2227
def to_dict(self) -> Dict[str, Any]:
2328
an_enum_value = self.an_enum_value.value
2429

25-
some_dict = self.some_dict
26-
2730
if isinstance(self.a_camel_date_time, datetime.datetime):
2831
a_camel_date_time = self.a_camel_date_time.isoformat()
2932

@@ -32,9 +35,9 @@ def to_dict(self) -> Dict[str, Any]:
3235

3336
a_date = self.a_date.isoformat()
3437

35-
if self.nested_list_of_enums is None:
36-
nested_list_of_enums = None
37-
else:
38+
required_not_nullable = self.required_not_nullable
39+
nested_list_of_enums: Union[Unset, List[Any]] = UNSET
40+
if not isinstance(self.nested_list_of_enums, Unset):
3841
nested_list_of_enums = []
3942
for nested_list_of_enums_item_data in self.nested_list_of_enums:
4043
nested_list_of_enums_item = []
@@ -45,23 +48,36 @@ def to_dict(self) -> Dict[str, Any]:
4548

4649
nested_list_of_enums.append(nested_list_of_enums_item)
4750

51+
some_dict = self.some_dict if self.some_dict else None
52+
4853
attr_1_leading_digit = self.attr_1_leading_digit
54+
required_nullable = self.required_nullable
55+
not_required_nullable = self.not_required_nullable
56+
not_required_not_nullable = self.not_required_not_nullable
4957

50-
return {
58+
field_dict = {
5159
"an_enum_value": an_enum_value,
52-
"some_dict": some_dict,
5360
"aCamelDateTime": a_camel_date_time,
5461
"a_date": a_date,
55-
"nested_list_of_enums": nested_list_of_enums,
56-
"1_leading_digit": attr_1_leading_digit,
62+
"required_not_nullable": required_not_nullable,
63+
"some_dict": some_dict,
64+
"required_nullable": required_nullable,
5765
}
66+
if nested_list_of_enums is not UNSET:
67+
field_dict["nested_list_of_enums"] = nested_list_of_enums
68+
if attr_1_leading_digit is not UNSET:
69+
field_dict["1_leading_digit"] = attr_1_leading_digit
70+
if not_required_nullable is not UNSET:
71+
field_dict["not_required_nullable"] = not_required_nullable
72+
if not_required_not_nullable is not UNSET:
73+
field_dict["not_required_not_nullable"] = not_required_not_nullable
74+
75+
return field_dict
5876

5977
@staticmethod
6078
def from_dict(d: Dict[str, Any]) -> "AModel":
6179
an_enum_value = AnEnum(d["an_enum_value"])
6280

63-
some_dict = d["some_dict"]
64-
6581
def _parse_a_camel_date_time(data: Dict[str, Any]) -> Union[datetime.datetime, datetime.date]:
6682
a_camel_date_time: Union[datetime.datetime, datetime.date]
6783
try:
@@ -78,8 +94,10 @@ def _parse_a_camel_date_time(data: Dict[str, Any]) -> Union[datetime.datetime, d
7894

7995
a_date = isoparse(d["a_date"]).date()
8096

97+
required_not_nullable = d["required_not_nullable"]
98+
8199
nested_list_of_enums = []
82-
for nested_list_of_enums_item_data in d.get("nested_list_of_enums") or []:
100+
for nested_list_of_enums_item_data in d.get("nested_list_of_enums", UNSET) or []:
83101
nested_list_of_enums_item = []
84102
for nested_list_of_enums_item_item_data in nested_list_of_enums_item_data:
85103
nested_list_of_enums_item_item = DifferentEnum(nested_list_of_enums_item_item_data)
@@ -88,13 +106,25 @@ def _parse_a_camel_date_time(data: Dict[str, Any]) -> Union[datetime.datetime, d
88106

89107
nested_list_of_enums.append(nested_list_of_enums_item)
90108

91-
attr_1_leading_digit = d.get("1_leading_digit")
109+
some_dict = d["some_dict"]
110+
111+
attr_1_leading_digit = d.get("1_leading_digit", UNSET)
112+
113+
required_nullable = d["required_nullable"]
114+
115+
not_required_nullable = d.get("not_required_nullable", UNSET)
116+
117+
not_required_not_nullable = d.get("not_required_not_nullable", UNSET)
92118

93119
return AModel(
94120
an_enum_value=an_enum_value,
95-
some_dict=some_dict,
96121
a_camel_date_time=a_camel_date_time,
97122
a_date=a_date,
123+
required_not_nullable=required_not_nullable,
98124
nested_list_of_enums=nested_list_of_enums,
125+
some_dict=some_dict,
99126
attr_1_leading_digit=attr_1_leading_digit,
127+
required_nullable=required_nullable,
128+
not_required_nullable=not_required_nullable,
129+
not_required_not_nullable=not_required_not_nullable,
100130
)

end_to_end_tests/golden-record-custom/my_test_api_client/models/body_upload_file_tests_upload_post.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,12 @@ class BodyUploadFileTestsUploadPost:
1414
def to_dict(self) -> Dict[str, Any]:
1515
some_file = self.some_file.to_tuple()
1616

17-
return {
17+
field_dict = {
1818
"some_file": some_file,
1919
}
2020

21+
return field_dict
22+
2123
@staticmethod
2224
def from_dict(d: Dict[str, Any]) -> "BodyUploadFileTestsUploadPost":
2325
some_file = d["some_file"]

end_to_end_tests/golden-record-custom/my_test_api_client/models/http_validation_error.py

+11-9
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,36 @@
1-
from typing import Any, Dict, List, Optional
1+
from typing import Any, Dict, List, Union
22

33
import attr
44

55
from ..models.validation_error import ValidationError
6+
from ..types import UNSET, Unset
67

78

89
@attr.s(auto_attribs=True)
910
class HTTPValidationError:
1011
""" """
1112

12-
detail: Optional[List[ValidationError]] = None
13+
detail: Union[Unset, List[ValidationError]] = UNSET
1314

1415
def to_dict(self) -> Dict[str, Any]:
15-
if self.detail is None:
16-
detail = None
17-
else:
16+
detail: Union[Unset, List[Any]] = UNSET
17+
if not isinstance(self.detail, Unset):
1818
detail = []
1919
for detail_item_data in self.detail:
2020
detail_item = detail_item_data.to_dict()
2121

2222
detail.append(detail_item)
2323

24-
return {
25-
"detail": detail,
26-
}
24+
field_dict = {}
25+
if detail is not UNSET:
26+
field_dict["detail"] = detail
27+
28+
return field_dict
2729

2830
@staticmethod
2931
def from_dict(d: Dict[str, Any]) -> "HTTPValidationError":
3032
detail = []
31-
for detail_item_data in d.get("detail") or []:
33+
for detail_item_data in d.get("detail", UNSET) or []:
3234
detail_item = ValidationError.from_dict(detail_item_data)
3335

3436
detail.append(detail_item)

end_to_end_tests/golden-record-custom/my_test_api_client/models/validation_error.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@ def to_dict(self) -> Dict[str, Any]:
1717
msg = self.msg
1818
type = self.type
1919

20-
return {
20+
field_dict = {
2121
"loc": loc,
2222
"msg": msg,
2323
"type": type,
2424
}
2525

26+
return field_dict
27+
2628
@staticmethod
2729
def from_dict(d: Dict[str, Any]) -> "ValidationError":
2830
loc = d["loc"]

0 commit comments

Comments
 (0)