Skip to content

Commit 772dc24

Browse files
committed
Merge branch 'main' into benchling-allof-support
2 parents 74da896 + 2861a34 commit 772dc24

File tree

91 files changed

+1742
-322
lines changed

Some content is hidden

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

91 files changed

+1742
-322
lines changed

CHANGELOG.md

+20-3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8-
## 0.8.0 - Unreleased
8+
## 0.8.0 - 2021-02-19
9+
10+
### Breaking Changes
11+
12+
- Generated clients will no longer pass through `None` to query parameters. Previously, any query params set to `None` would surface as empty strings (per the default behavior of `httpx`). This is contrary to the defaults indicated by the OpenAPI 3.0.3 spec. Ommitting these parameters makes us more compliant. If you require a style of `null` to be passed to your query parameters, please request support for the OpenAPI "style" attribute. Thank you to @forest-benchling and @bowenwr for a ton of input on this.
913

1014
### Additions
1115

@@ -15,13 +19,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1519
project info.
1620
- `none` will not create a project folder at all, only the inner package folder (which won't be inner anymore)
1721
- Attempt to detect and alert users if they are using an unsupported version of OpenAPI (#281).
18-
- Fixes `Enum` deserialization when the value is `UNSET`.
19-
- Basic support for `allOf` in models (#98)
22+
- The media type application/vnd.api+json will now be handled just like application/json (#307). Thanks @jrversteegh!
23+
- Support passing models into query parameters (#316). Thanks @forest-benchling!
24+
- Add support for cookie parameters (#326).
25+
- New `--file-encoding` command line option (#330). Sets the encoding used when writing generated files (defaults to utf-8). Thanks @dongfangtianyu!
2026

2127
### Changes
2228

2329
- Lowered the minimum version of `python-dateutil` to 2.8.0 for improved compatibility (#298 & #299). Thanks @bowenwr!
2430
- The `from_dict` method on generated models is now a `@classmethod` instead of `@staticmethod` (#215 & #292). Thanks @forest-benchling!
31+
- Renamed all templates to end in `.jinja`, and all python-templates to end in `.py.jinja` to fix confusion with the latest version of mypy. Note **this will break existing custom templates until you update your template file names**.
32+
33+
### Fixes
34+
35+
- Endpoint tags are now sanitized during parsing to fix an issue where `My Tag` and `MyTag` are seen as two different tags but are then later unified, causing errors when creating directories. Thanks @p1-ra! (#328)
36+
- Parser will softly ignore value error during schema responses' status code convertion from string to integer (not a number). Errors will be reported to the end user and parsing will continue to proceed (#327).
37+
- The generated `from_dict` and `to_dict` methods of models will now properly handle `nullable` and `not required` properties that are themselves generated models (#315). Thanks @forest-benchling!
38+
- Fixed a typo in the async example in generated README.md files (#337). Thanks @synchronizing!
39+
- Fix deserialization of `None` and `Unset` properties for all types by unifying the checks (#334). Thanks @forest-benchling!
40+
- If duplicate model names are detected during generation, you'll now get an error message instead of broken code (#336). Thanks @forest-benchling!
41+
- Fixes `Enum` deserialization when the value is `UNSET` (#306). Thanks @bowenwr!
2542

2643
## 0.7.3 - 2020-12-21
2744

dobby.toml

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
[[workflows]]
2+
name = "Start Task"
3+
[[workflows.steps]]
4+
type = "SelectGitHubIssue"
5+
6+
[[workflows.steps]]
7+
type = "SwitchBranches"
8+
9+
[[workflows]]
10+
name = "Prepare Release"
11+
[[workflows.steps]]
12+
type = "UpdateProjectFromCommits"
13+
14+
[[workflows.steps]]
15+
type = "Command"
16+
command = "prettier --write CHANGELOG.md"
17+
18+
[github]
19+
owner = "triaxtec"
20+
repo = "openapi-python-client"

end_to_end_tests/golden-record-custom/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ Or do the same thing with an async version:
3434

3535
```python
3636
from custom_e2e.models import MyDataModel
37-
from custom_e2e.async_api.my_tag import get_my_data_model
37+
from custom_e2e.api.my_tag import get_my_data_model
3838
from custom_e2e.types import Response
3939

4040
my_data: MyDataModel = await get_my_data_model.asyncio(client=client)

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

+25-21
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
from ...models.an_enum import AnEnum
1515
from ...models.http_validation_error import HTTPValidationError
16+
from ...models.model_with_union_property import ModelWithUnionProperty
1617
from ...types import UNSET, Unset
1718

1819

@@ -50,6 +51,8 @@ def httpx_request(
5051
union_prop: Union[Unset, float, str] = "not a float",
5152
union_prop_with_ref: Union[Unset, float, AnEnum] = 0.6,
5253
enum_prop: Union[Unset, AnEnum] = UNSET,
54+
model_prop: Union[ModelWithUnionProperty, Unset] = UNSET,
55+
required_model_prop: ModelWithUnionProperty,
5356
) -> Response[Union[None, HTTPValidationError]]:
5457

5558
json_datetime_prop: Union[Unset, str] = UNSET
@@ -89,27 +92,28 @@ def httpx_request(
8992
if not isinstance(enum_prop, Unset):
9093
json_enum_prop = enum_prop
9194

92-
params: Dict[str, Any] = {}
93-
if string_prop is not UNSET:
94-
params["string_prop"] = string_prop
95-
if datetime_prop is not UNSET:
96-
params["datetime_prop"] = json_datetime_prop
97-
if date_prop is not UNSET:
98-
params["date_prop"] = json_date_prop
99-
if float_prop is not UNSET:
100-
params["float_prop"] = float_prop
101-
if int_prop is not UNSET:
102-
params["int_prop"] = int_prop
103-
if boolean_prop is not UNSET:
104-
params["boolean_prop"] = boolean_prop
105-
if list_prop is not UNSET:
106-
params["list_prop"] = json_list_prop
107-
if union_prop is not UNSET:
108-
params["union_prop"] = json_union_prop
109-
if union_prop_with_ref is not UNSET:
110-
params["union_prop_with_ref"] = json_union_prop_with_ref
111-
if enum_prop is not UNSET:
112-
params["enum_prop"] = json_enum_prop
95+
json_model_prop: Union[Unset, Dict[str, Any]] = UNSET
96+
if not isinstance(model_prop, Unset):
97+
json_model_prop = model_prop.to_dict()
98+
99+
json_required_model_prop = required_model_prop.to_dict()
100+
101+
params: Dict[str, Any] = {
102+
"string_prop": string_prop,
103+
"datetime_prop": json_datetime_prop,
104+
"date_prop": json_date_prop,
105+
"float_prop": float_prop,
106+
"int_prop": int_prop,
107+
"boolean_prop": boolean_prop,
108+
"list_prop": json_list_prop,
109+
"union_prop": json_union_prop,
110+
"union_prop_with_ref": json_union_prop_with_ref,
111+
"enum_prop": json_enum_prop,
112+
}
113+
if not isinstance(json_model_prop, Unset):
114+
params.update(json_model_prop)
115+
params.update(json_required_model_prop)
116+
params = {k: v for k, v in params.items() if v is not UNSET and v is not None}
113117

114118
response = client.request(
115119
"post",

end_to_end_tests/golden-record-custom/custom_e2e/api/tests/get_user_list.py

+1
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ def httpx_request(
6262
"an_enum_value": json_an_enum_value,
6363
"some_date": json_some_date,
6464
}
65+
params = {k: v for k, v in params.items() if v is not UNSET and v is not None}
6566

6667
response = client.request(
6768
"get",

end_to_end_tests/golden-record-custom/custom_e2e/api/tests/int_enum_tests_int_enum_post.py

+1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ def httpx_request(
4444
params: Dict[str, Any] = {
4545
"int_enum": json_int_enum,
4646
}
47+
params = {k: v for k, v in params.items() if v is not UNSET and v is not None}
4748

4849
response = client.request(
4950
"post",

end_to_end_tests/golden-record-custom/custom_e2e/api/tests/optional_value_tests_optional_query_param.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,10 @@ def httpx_request(
4343
if not isinstance(query_param, Unset):
4444
json_query_param = query_param
4545

46-
params: Dict[str, Any] = {}
47-
if query_param is not UNSET:
48-
params["query_param"] = json_query_param
46+
params: Dict[str, Any] = {
47+
"query_param": json_query_param,
48+
}
49+
params = {k: v for k, v in params.items() if v is not UNSET and v is not None}
4950

5051
response = client.request(
5152
"get",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
from typing import Optional
2+
3+
import httpx
4+
5+
from ...types import Response
6+
7+
Client = httpx.Client
8+
9+
10+
def _parse_response(*, response: httpx.Response) -> Optional[Union[None, None]]:
11+
if response.status_code == 200:
12+
response_200 = None
13+
14+
return response_200
15+
if response.status_code == 401:
16+
response_401 = None
17+
18+
return response_401
19+
return None
20+
21+
22+
def _build_response(*, response: httpx.Response) -> Response[Union[None, None]]:
23+
return Response(
24+
status_code=response.status_code,
25+
content=response.content,
26+
headers=response.headers,
27+
parsed=_parse_response(response=response),
28+
)
29+
30+
31+
def httpx_request(
32+
*,
33+
client: Client,
34+
my_token: str,
35+
) -> Response[Union[None, None]]:
36+
37+
response = client.request(
38+
"get",
39+
"/auth/token_with_cookie",
40+
)
41+
42+
return _build_response(response=response)

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

+2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ def _parse_response(*, response: httpx.Response) -> Optional[Union[
2424
if response.status_code == 422:
2525
response_422 = HTTPValidationError.from_dict(response.json())
2626

27+
28+
2729
return response_422
2830
return None
2931

end_to_end_tests/golden-record-custom/custom_e2e/models/__init__.py

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

33
from .a_model import AModel
4+
from .a_model_model import AModelModel
5+
from .a_model_not_required_model import AModelNotRequiredModel
6+
from .a_model_not_required_nullable_model import AModelNotRequiredNullableModel
7+
from .a_model_nullable_model import AModelNullableModel
48
from .all_of_sub_model import AllOfSubModel
59
from .an_enum import AnEnum
610
from .an_int_enum import AnIntEnum

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

+63-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
import datetime
2-
from typing import Any, Dict, List, Optional, Type, TypeVar, Union, cast
2+
from typing import Any, Dict, List, Optional, Type, TypeVar, Union
33

44
import attr
55
from dateutil.parser import isoparse
66

7+
from ..models.a_model_model import AModelModel
8+
from ..models.a_model_not_required_model import AModelNotRequiredModel
9+
from ..models.a_model_not_required_nullable_model import AModelNotRequiredNullableModel
10+
from ..models.a_model_nullable_model import AModelNullableModel
711
from ..models.an_enum import AnEnum
812
from ..models.different_enum import DifferentEnum
913
from ..types import UNSET, Unset
@@ -19,12 +23,17 @@ class AModel:
1923
a_camel_date_time: Union[datetime.datetime, datetime.date]
2024
a_date: datetime.date
2125
required_not_nullable: str
26+
model: AModelModel
2227
a_nullable_date: Optional[datetime.date]
2328
required_nullable: Optional[str]
29+
nullable_model: Optional[AModelNullableModel]
2430
nested_list_of_enums: Union[Unset, List[List[DifferentEnum]]] = UNSET
31+
a_not_required_date: Union[Unset, datetime.date] = UNSET
2532
attr_1_leading_digit: Union[Unset, str] = UNSET
2633
not_required_nullable: Union[Unset, Optional[str]] = UNSET
2734
not_required_not_nullable: Union[Unset, str] = UNSET
35+
not_required_model: Union[AModelNotRequiredModel, Unset] = UNSET
36+
not_required_nullable_model: Union[Optional[AModelNotRequiredNullableModel], Unset] = UNSET
2837

2938
def to_dict(self) -> Dict[str, Any]:
3039
an_enum_value = self.an_enum_value.value
@@ -37,6 +46,8 @@ def to_dict(self) -> Dict[str, Any]:
3746

3847
a_date = self.a_date.isoformat()
3948
required_not_nullable = self.required_not_nullable
49+
model = self.model.to_dict()
50+
4051
nested_list_of_enums: Union[Unset, List[Any]] = UNSET
4152
if not isinstance(self.nested_list_of_enums, Unset):
4253
nested_list_of_enums = []
@@ -50,10 +61,25 @@ def to_dict(self) -> Dict[str, Any]:
5061
nested_list_of_enums.append(nested_list_of_enums_item)
5162

5263
a_nullable_date = self.a_nullable_date.isoformat() if self.a_nullable_date else None
64+
a_not_required_date: Union[Unset, str] = UNSET
65+
if not isinstance(self.a_not_required_date, Unset):
66+
a_not_required_date = self.a_not_required_date.isoformat()
67+
5368
attr_1_leading_digit = self.attr_1_leading_digit
5469
required_nullable = self.required_nullable
5570
not_required_nullable = self.not_required_nullable
5671
not_required_not_nullable = self.not_required_not_nullable
72+
nullable_model = self.nullable_model.to_dict() if self.nullable_model else None
73+
74+
not_required_model: Union[Unset, Dict[str, Any]] = UNSET
75+
if not isinstance(self.not_required_model, Unset):
76+
not_required_model = self.not_required_model.to_dict()
77+
78+
not_required_nullable_model: Union[None, Unset, Dict[str, Any]] = UNSET
79+
if not isinstance(self.not_required_nullable_model, Unset):
80+
not_required_nullable_model = (
81+
self.not_required_nullable_model.to_dict() if self.not_required_nullable_model else None
82+
)
5783

5884
field_dict: Dict[str, Any] = {}
5985
field_dict.update(
@@ -62,18 +88,26 @@ def to_dict(self) -> Dict[str, Any]:
6288
"aCamelDateTime": a_camel_date_time,
6389
"a_date": a_date,
6490
"required_not_nullable": required_not_nullable,
91+
"model": model,
6592
"a_nullable_date": a_nullable_date,
6693
"required_nullable": required_nullable,
94+
"nullable_model": nullable_model,
6795
}
6896
)
6997
if nested_list_of_enums is not UNSET:
7098
field_dict["nested_list_of_enums"] = nested_list_of_enums
99+
if a_not_required_date is not UNSET:
100+
field_dict["a_not_required_date"] = a_not_required_date
71101
if attr_1_leading_digit is not UNSET:
72102
field_dict["1_leading_digit"] = attr_1_leading_digit
73103
if not_required_nullable is not UNSET:
74104
field_dict["not_required_nullable"] = not_required_nullable
75105
if not_required_not_nullable is not UNSET:
76106
field_dict["not_required_not_nullable"] = not_required_not_nullable
107+
if not_required_model is not UNSET:
108+
field_dict["not_required_model"] = not_required_model
109+
if not_required_nullable_model is not UNSET:
110+
field_dict["not_required_nullable_model"] = not_required_nullable_model
77111

78112
return field_dict
79113

@@ -101,6 +135,8 @@ def _parse_a_camel_date_time(data: Any) -> Union[datetime.datetime, datetime.dat
101135

102136
required_not_nullable = d.pop("required_not_nullable")
103137

138+
model = AModelModel.from_dict(d.pop("model"))
139+
104140
nested_list_of_enums = []
105141
_nested_list_of_enums = d.pop("nested_list_of_enums", UNSET)
106142
for nested_list_of_enums_item_data in _nested_list_of_enums or []:
@@ -116,7 +152,12 @@ def _parse_a_camel_date_time(data: Any) -> Union[datetime.datetime, datetime.dat
116152
a_nullable_date = None
117153
_a_nullable_date = d.pop("a_nullable_date")
118154
if _a_nullable_date is not None:
119-
a_nullable_date = isoparse(cast(str, _a_nullable_date)).date()
155+
a_nullable_date = isoparse(_a_nullable_date).date()
156+
157+
a_not_required_date: Union[Unset, datetime.date] = UNSET
158+
_a_not_required_date = d.pop("a_not_required_date", UNSET)
159+
if not isinstance(_a_not_required_date, Unset):
160+
a_not_required_date = isoparse(_a_not_required_date).date()
120161

121162
attr_1_leading_digit = d.pop("1_leading_digit", UNSET)
122163

@@ -126,17 +167,37 @@ def _parse_a_camel_date_time(data: Any) -> Union[datetime.datetime, datetime.dat
126167

127168
not_required_not_nullable = d.pop("not_required_not_nullable", UNSET)
128169

170+
nullable_model = None
171+
_nullable_model = d.pop("nullable_model")
172+
if _nullable_model is not None:
173+
nullable_model = AModelNullableModel.from_dict(_nullable_model)
174+
175+
not_required_model: Union[AModelNotRequiredModel, Unset] = UNSET
176+
_not_required_model = d.pop("not_required_model", UNSET)
177+
if not isinstance(_not_required_model, Unset):
178+
not_required_model = AModelNotRequiredModel.from_dict(_not_required_model)
179+
180+
not_required_nullable_model = None
181+
_not_required_nullable_model = d.pop("not_required_nullable_model", UNSET)
182+
if _not_required_nullable_model is not None and not isinstance(_not_required_nullable_model, Unset):
183+
not_required_nullable_model = AModelNotRequiredNullableModel.from_dict(_not_required_nullable_model)
184+
129185
a_model = cls(
130186
an_enum_value=an_enum_value,
131187
a_camel_date_time=a_camel_date_time,
132188
a_date=a_date,
133189
required_not_nullable=required_not_nullable,
190+
model=model,
134191
nested_list_of_enums=nested_list_of_enums,
135192
a_nullable_date=a_nullable_date,
193+
a_not_required_date=a_not_required_date,
136194
attr_1_leading_digit=attr_1_leading_digit,
137195
required_nullable=required_nullable,
138196
not_required_nullable=not_required_nullable,
139197
not_required_not_nullable=not_required_not_nullable,
198+
nullable_model=nullable_model,
199+
not_required_model=not_required_model,
200+
not_required_nullable_model=not_required_nullable_model,
140201
)
141202

142203
return a_model

0 commit comments

Comments
 (0)