Skip to content

Commit f626147

Browse files
authored
Merge pull request #37 from triaxtec/pr/30
Add support for date properties
2 parents 66970cb + 2b3b04d commit f626147

File tree

18 files changed

+185
-39
lines changed

18 files changed

+185
-39
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,4 @@ dmypy.json
2121

2222
/coverage.xml
2323
/.coverage
24+
htmlcov/

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77
## 0.3.0 - Unreleased
88
### Additions
99
- Link to the GitHub repository from PyPI (#26). Thanks @theY4Kman!
10+
- Support for date properties (#30, #37). Thanks @acgray!
1011

1112
### Fixes
1213
- Fixed some typing issues in generated clients and incorporate mypy into end to end tests (#32). Thanks @acgray!

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ OpenAPI document.
5858
## OpenAPI features supported
5959
1. All HTTP Methods
6060
1. JSON and form bodies, path and query parameters
61-
1. float, string, int, datetimes, string enums, and custom schemas or lists containing any of those
61+
1. float, string, int, date, datetime, string enums, and custom schemas or lists containing any of those
6262
1. html/text or application/json responses containing any of the previous types
6363
1. Bearer token security
6464

openapi_python_client/openapi_parser/openapi.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,16 @@
44
from enum import Enum
55
from typing import Any, Dict, Generator, Iterable, List, Optional, Set, Union
66

7-
from .properties import EnumListProperty, EnumProperty, Property, ReferenceListProperty, RefProperty, property_from_dict
7+
from .properties import (
8+
DateProperty,
9+
DateTimeProperty,
10+
EnumListProperty,
11+
EnumProperty,
12+
Property,
13+
ReferenceListProperty,
14+
RefProperty,
15+
property_from_dict,
16+
)
817
from .reference import Reference
918
from .responses import ListRefResponse, RefResponse, Response, response_from_dict
1019

@@ -108,11 +117,16 @@ def _add_parameters(self, data: Dict[str, Any]) -> None:
108117
prop = property_from_dict(
109118
name=param_dict["name"], required=param_dict["required"], data=param_dict["schema"]
110119
)
111-
if (
120+
if isinstance(prop, DateProperty):
121+
self.relative_imports.add("from datetime import date")
122+
elif isinstance(prop, DateTimeProperty):
123+
self.relative_imports.add("from datetime import datetime")
124+
elif (
112125
isinstance(prop, (ReferenceListProperty, EnumListProperty, RefProperty, EnumProperty))
113126
and prop.reference
114127
):
115128
self.relative_imports.add(import_string_from_reference(prop.reference, prefix="..models"))
129+
116130
if param_dict["in"] == ParameterLocation.QUERY:
117131
self.query_parameters.append(prop)
118132
elif param_dict["in"] == ParameterLocation.PATH:
@@ -168,7 +182,11 @@ def from_dict(d: Dict[str, Any], /) -> Schema:
168182
required_properties.append(p)
169183
else:
170184
optional_properties.append(p)
171-
if isinstance(p, (ReferenceListProperty, EnumListProperty, RefProperty, EnumProperty)) and p.reference:
185+
if isinstance(p, DateTimeProperty):
186+
relative_imports.add("from datetime import datetime")
187+
elif isinstance(p, DateProperty):
188+
relative_imports.add("from datetime import date")
189+
elif isinstance(p, (ReferenceListProperty, EnumListProperty, RefProperty, EnumProperty)) and p.reference:
172190
relative_imports.add(import_string_from_reference(p.reference))
173191
schema = Schema(
174192
reference=Reference.from_ref(d["title"]),

openapi_python_client/openapi_parser/properties.py

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,20 @@ class DateTimeProperty(Property):
7676
_type_string: ClassVar[str] = "datetime"
7777
constructor_template: ClassVar[str] = "datetime_property.pyi"
7878

79+
def transform(self) -> str:
80+
return f"{self.python_name}.isoformat()"
81+
82+
83+
@dataclass
84+
class DateProperty(Property):
85+
""" A property of type datetime.date """
86+
87+
_type_string: ClassVar[str] = "date"
88+
constructor_template: ClassVar[str] = "date_property.pyi"
89+
90+
def transform(self) -> str:
91+
return f"{self.python_name}.isoformat()"
92+
7993

8094
@dataclass
8195
class FloatProperty(Property):
@@ -245,12 +259,14 @@ def property_from_dict(name: str, required: bool, data: Dict[str, Any]) -> Prope
245259
if "$ref" in data:
246260
return RefProperty(name=name, required=required, reference=Reference.from_ref(data["$ref"]), default=None)
247261
if data["type"] == "string":
248-
if "format" not in data:
249-
return StringProperty(
250-
name=name, default=data.get("default"), required=required, pattern=data.get("pattern"),
251-
)
252-
elif data["format"] == "date-time":
253-
return DateTimeProperty(name=name, required=required, default=data.get("default"))
262+
if "format" in data:
263+
if data.get("format") == "date-time":
264+
return DateTimeProperty(name=name, required=required, default=data.get("default"))
265+
elif data.get("format") == "date":
266+
return DateProperty(name=name, required=required, default=data.get("default"))
267+
else:
268+
raise ValueError(f'Unsupported string format:{data["format"]}')
269+
return StringProperty(name=name, default=data.get("default"), required=required, pattern=data.get("pattern"),)
254270
elif data["type"] == "number":
255271
return FloatProperty(name=name, default=data.get("default"), required=required)
256272
elif data["type"] == "integer":
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{% if property.required %}
2+
{{ property.name }} = date.fromisoformat(d["{{ property.name }}"])
3+
{% else %}
4+
{{ property.name }} = None
5+
if ({{ property.name }}_string := d.get("{{ property.name }}")) is not None:
6+
{{ property.name }} = date.fromisoformat(cast(str, {{ property.name }}_string))
7+
{% endif %}

openapi_python_client/templates/model.pyi

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from __future__ import annotations
22

33
from dataclasses import dataclass
4-
from datetime import datetime
54
from typing import Any, Dict, List, Optional, cast
65

76
{% for relative in schema.relative_imports %}

tests/test_end_to_end/fastapi/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
""" A FastAPI app used to create an OpenAPI document for end-to-end testing """
22
import json
3-
from datetime import datetime
3+
from datetime import date, datetime
44
from enum import Enum
55
from pathlib import Path
66
from typing import List
@@ -45,10 +45,11 @@ class AModel(BaseModel):
4545
a_list_of_strings: List[str]
4646
a_list_of_objects: List[OtherModel]
4747
aCamelDateTime: datetime
48+
a_date: date
4849

4950

5051
@test_router.get("/", response_model=List[AModel], operation_id="getUserList")
51-
def get_list(statuses: List[AnEnum] = Query(...),):
52+
def get_list(statuses: List[AnEnum] = Query(...), some_date: date = Query(...), some_datetime: datetime = Query(...)):
5253
""" Get users, filtered by statuses """
5354
return
5455

tests/test_end_to_end/fastapi/openapi.json

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,26 @@
4848
},
4949
"name": "statuses",
5050
"in": "query"
51+
},
52+
{
53+
"required": true,
54+
"schema": {
55+
"title": "Some Date",
56+
"type": "string",
57+
"format": "date"
58+
},
59+
"name": "some_date",
60+
"in": "query"
61+
},
62+
{
63+
"required": true,
64+
"schema": {
65+
"title": "Some Datetime",
66+
"type": "string",
67+
"format": "date-time"
68+
},
69+
"name": "some_datetime",
70+
"in": "query"
5171
}
5272
],
5373
"responses": {
@@ -88,7 +108,8 @@
88108
"a_list_of_enums",
89109
"a_list_of_strings",
90110
"a_list_of_objects",
91-
"aCamelDateTime"
111+
"aCamelDateTime",
112+
"a_date"
92113
],
93114
"type": "object",
94115
"properties": {
@@ -127,6 +148,11 @@
127148
"title": "Acameldatetime",
128149
"type": "string",
129150
"format": "date-time"
151+
},
152+
"a_date": {
153+
"title": "A Date",
154+
"type": "string",
155+
"format": "date"
130156
}
131157
},
132158
"description": "A Model for testing all the ways custom objects can be used "

tests/test_end_to_end/golden-master/my_test_api_client/api/users.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from dataclasses import asdict
2+
from datetime import date, datetime
23
from typing import Any, Dict, List, Optional, Union, cast
34

45
import httpx
@@ -11,7 +12,7 @@
1112

1213

1314
def get_user_list(
14-
*, client: Client, statuses: List[Statuses],
15+
*, client: Client, statuses: List[Statuses], some_date: date, some_datetime: datetime,
1516
) -> Union[
1617
List[AModel], HTTPValidationError,
1718
]:
@@ -20,6 +21,8 @@ def get_user_list(
2021

2122
params = {
2223
"statuses": statuses,
24+
"some_date": some_date.isoformat(),
25+
"some_datetime": some_datetime.isoformat(),
2326
}
2427

2528
response = httpx.get(url=url, headers=client.get_headers(), params=params,)

tests/test_end_to_end/golden-master/my_test_api_client/async_api/users.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from dataclasses import asdict
2+
from datetime import date, datetime
23
from typing import Any, Dict, List, Optional, Union, cast
34

45
import httpx
@@ -11,7 +12,7 @@
1112

1213

1314
async def get_user_list(
14-
*, client: Client, statuses: List[Statuses],
15+
*, client: Client, statuses: List[Statuses], some_date: date, some_datetime: datetime,
1516
) -> Union[
1617
List[AModel], HTTPValidationError,
1718
]:
@@ -20,6 +21,8 @@ async def get_user_list(
2021

2122
params = {
2223
"statuses": statuses,
24+
"some_date": some_date.isoformat(),
25+
"some_datetime": some_datetime.isoformat(),
2326
}
2427

2528
async with httpx.AsyncClient() as _client:

tests/test_end_to_end/golden-master/my_test_api_client/models/a_model.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from __future__ import annotations
22

33
from dataclasses import dataclass
4-
from datetime import datetime
4+
from datetime import date, datetime
55
from typing import Any, Dict, List, Optional, cast
66

77
from .a_list_of_enums import AListOfEnums
@@ -18,14 +18,16 @@ class AModel:
1818
a_list_of_strings: List[str]
1919
a_list_of_objects: List[OtherModel]
2020
a_camel_date_time: datetime
21+
a_date: date
2122

2223
def to_dict(self) -> Dict[str, Any]:
2324
return {
2425
"an_enum_value": self.an_enum_value.value,
2526
"a_list_of_enums": self.a_list_of_enums,
2627
"a_list_of_strings": self.a_list_of_strings,
2728
"a_list_of_objects": self.a_list_of_objects,
28-
"aCamelDateTime": self.a_camel_date_time,
29+
"aCamelDateTime": self.a_camel_date_time.isoformat(),
30+
"a_date": self.a_date.isoformat(),
2931
}
3032

3133
@staticmethod
@@ -45,10 +47,13 @@ def from_dict(d: Dict[str, Any]) -> AModel:
4547

4648
a_camel_date_time = datetime.fromisoformat(d["aCamelDateTime"])
4749

50+
a_date = date.fromisoformat(d["a_date"])
51+
4852
return AModel(
4953
an_enum_value=an_enum_value,
5054
a_list_of_enums=a_list_of_enums,
5155
a_list_of_strings=a_list_of_strings,
5256
a_list_of_objects=a_list_of_objects,
5357
a_camel_date_time=a_camel_date_time,
58+
a_date=a_date,
5459
)

tests/test_end_to_end/golden-master/my_test_api_client/models/abc_response.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from __future__ import annotations
22

33
from dataclasses import dataclass
4-
from datetime import datetime
54
from typing import Any, Dict, List, Optional, cast
65

76

tests/test_end_to_end/golden-master/my_test_api_client/models/http_validation_error.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from __future__ import annotations
22

33
from dataclasses import dataclass
4-
from datetime import datetime
54
from typing import Any, Dict, List, Optional, cast
65

76
from .validation_error import ValidationError

tests/test_end_to_end/golden-master/my_test_api_client/models/other_model.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from __future__ import annotations
22

33
from dataclasses import dataclass
4-
from datetime import datetime
54
from typing import Any, Dict, List, Optional, cast
65

76

tests/test_end_to_end/golden-master/my_test_api_client/models/validation_error.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from __future__ import annotations
22

33
from dataclasses import dataclass
4-
from datetime import datetime
54
from typing import Any, Dict, List, Optional, cast
65

76

0 commit comments

Comments
 (0)