Skip to content

Fix nullable dates #277

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Dec 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixes
- Spacing and extra returns for Union types of `additionalProperties` (#266 & #268). Thanks @joshzana & @packyg!
- Title of inline schemas will no longer be missing characters (#271 & #274). Thanks @kalzoo!
- Handling of nulls (Nones) when parsing or constructing dates (#267). Thanks @fyhertz!

## 0.7.2 - 2020-12-08
### Fixes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ def httpx_request(

if isinstance(some_date, datetime.date):
json_some_date = some_date.isoformat()

else:
json_some_date = some_date.isoformat()

Expand Down
12 changes: 10 additions & 2 deletions end_to_end_tests/golden-record-custom/custom_e2e/models/a_model.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import datetime
from typing import Any, Dict, List, Optional, Union
from typing import Any, Dict, List, Optional, Union, cast

import attr
from dateutil.parser import isoparse
Expand All @@ -17,6 +17,7 @@ class AModel:
a_camel_date_time: Union[datetime.datetime, datetime.date]
a_date: datetime.date
required_not_nullable: str
a_nullable_date: Optional[datetime.date]
required_nullable: Optional[str]
nested_list_of_enums: Union[Unset, List[List[DifferentEnum]]] = UNSET
attr_1_leading_digit: Union[Unset, str] = UNSET
Expand All @@ -33,7 +34,6 @@ def to_dict(self) -> Dict[str, Any]:
a_camel_date_time = self.a_camel_date_time.isoformat()

a_date = self.a_date.isoformat()

required_not_nullable = self.required_not_nullable
nested_list_of_enums: Union[Unset, List[Any]] = UNSET
if not isinstance(self.nested_list_of_enums, Unset):
Expand All @@ -47,6 +47,7 @@ def to_dict(self) -> Dict[str, Any]:

nested_list_of_enums.append(nested_list_of_enums_item)

a_nullable_date = self.a_nullable_date.isoformat() if self.a_nullable_date else None
attr_1_leading_digit = self.attr_1_leading_digit
required_nullable = self.required_nullable
not_required_nullable = self.not_required_nullable
Expand All @@ -59,6 +60,7 @@ def to_dict(self) -> Dict[str, Any]:
"aCamelDateTime": a_camel_date_time,
"a_date": a_date,
"required_not_nullable": required_not_nullable,
"a_nullable_date": a_nullable_date,
"required_nullable": required_nullable,
}
)
Expand Down Expand Up @@ -109,6 +111,11 @@ def _parse_a_camel_date_time(data: Any) -> Union[datetime.datetime, datetime.dat

nested_list_of_enums.append(nested_list_of_enums_item)

a_nullable_date = None
_a_nullable_date = d.pop("a_nullable_date")
if _a_nullable_date is not None:
a_nullable_date = isoparse(cast(str, _a_nullable_date)).date()

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

required_nullable = d.pop("required_nullable")
Expand All @@ -123,6 +130,7 @@ def _parse_a_camel_date_time(data: Any) -> Union[datetime.datetime, datetime.dat
a_date=a_date,
required_not_nullable=required_not_nullable,
nested_list_of_enums=nested_list_of_enums,
a_nullable_date=a_nullable_date,
attr_1_leading_digit=attr_1_leading_digit,
required_nullable=required_nullable,
not_required_nullable=not_required_nullable,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ def _get_kwargs(

if isinstance(some_date, datetime.date):
json_some_date = some_date.isoformat()

else:
json_some_date = some_date.isoformat()

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import datetime
from typing import Any, Dict, List, Optional, Union
from typing import Any, Dict, List, Optional, Union, cast

import attr
from dateutil.parser import isoparse
Expand All @@ -17,6 +17,7 @@ class AModel:
a_camel_date_time: Union[datetime.datetime, datetime.date]
a_date: datetime.date
required_not_nullable: str
a_nullable_date: Optional[datetime.date]
required_nullable: Optional[str]
nested_list_of_enums: Union[Unset, List[List[DifferentEnum]]] = UNSET
attr_1_leading_digit: Union[Unset, str] = UNSET
Expand All @@ -33,7 +34,6 @@ def to_dict(self) -> Dict[str, Any]:
a_camel_date_time = self.a_camel_date_time.isoformat()

a_date = self.a_date.isoformat()

required_not_nullable = self.required_not_nullable
nested_list_of_enums: Union[Unset, List[Any]] = UNSET
if not isinstance(self.nested_list_of_enums, Unset):
Expand All @@ -47,6 +47,7 @@ def to_dict(self) -> Dict[str, Any]:

nested_list_of_enums.append(nested_list_of_enums_item)

a_nullable_date = self.a_nullable_date.isoformat() if self.a_nullable_date else None
attr_1_leading_digit = self.attr_1_leading_digit
required_nullable = self.required_nullable
not_required_nullable = self.not_required_nullable
Expand All @@ -59,6 +60,7 @@ def to_dict(self) -> Dict[str, Any]:
"aCamelDateTime": a_camel_date_time,
"a_date": a_date,
"required_not_nullable": required_not_nullable,
"a_nullable_date": a_nullable_date,
"required_nullable": required_nullable,
}
)
Expand Down Expand Up @@ -109,6 +111,11 @@ def _parse_a_camel_date_time(data: Any) -> Union[datetime.datetime, datetime.dat

nested_list_of_enums.append(nested_list_of_enums_item)

a_nullable_date = None
_a_nullable_date = d.pop("a_nullable_date")
if _a_nullable_date is not None:
a_nullable_date = isoparse(cast(str, _a_nullable_date)).date()

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

required_nullable = d.pop("required_nullable")
Expand All @@ -123,6 +130,7 @@ def _parse_a_camel_date_time(data: Any) -> Union[datetime.datetime, datetime.dat
a_date=a_date,
required_not_nullable=required_not_nullable,
nested_list_of_enums=nested_list_of_enums,
a_nullable_date=a_nullable_date,
attr_1_leading_digit=attr_1_leading_digit,
required_nullable=required_nullable,
not_required_nullable=not_required_nullable,
Expand Down
8 changes: 7 additions & 1 deletion end_to_end_tests/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -622,7 +622,7 @@
"schemas": {
"AModel": {
"title": "AModel",
"required": ["an_enum_value", "aCamelDateTime", "a_date", "required_nullable", "required_not_nullable"],
"required": ["an_enum_value", "aCamelDateTime", "a_date", "a_nullable_date", "required_nullable", "required_not_nullable"],
"type": "object",
"properties": {
"an_enum_value": {
Expand Down Expand Up @@ -657,6 +657,12 @@
"type": "string",
"format": "date"
},
"a_nullable_date": {
"title": "A Nullable Date",
"type": "string",
"format": "date",
"nullable": true
},
"1_leading_digit": {
"title": "Leading Digit",
"type": "string"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{% macro construct(property, source, initial_value="None") %}
{% if property.required %}
{% if property.required and not property.nullable %}
{{ property.python_name }} = isoparse({{ source }}).date()
{% else %}
{{ property.python_name }} = {{ initial_value }}
Expand All @@ -11,11 +11,7 @@ if _{{ property.python_name }} is not None:

{% macro transform(property, source, destination, declare_type=True) %}
{% if property.required %}
{% if property.nullable %}
{{ destination }} = {{ source }}.isoformat() if {{ source }} else None
{% else %}
{{ destination }} = {{ source }}.isoformat()
{% endif %}
{{ destination }} = {{ source }}.isoformat() {% if property.nullable %}if {{ source }} else None {%endif%}
{% else %}
{{ destination }}{% if declare_type %}: Union[Unset, str]{% endif %} = UNSET
if not isinstance({{ source }}, Unset):
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
{% macro construct(property, source, initial_value="None") %}
{% if property.required %}
{% if property.nullable %}
{{ property.python_name }} = {{ source }}
{{ property.python_name }} = isoparse({{ property.python_name }}) if {{ property.python_name }} else None
{% else %}
{{ property.python_name }} = isoparse({{ source }})
{% endif %}
{% else %}
{{ property.python_name }} = {{ initial_value }}
_{{ property.python_name }} = {{ source }}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from datetime import date
from typing import cast, Union

from dateutil.parser import isoparse
{% from "property_templates/date_property.pyi" import transform, construct %}
some_source = date(2020, 10, 12)
{{ transform(property, "some_source", "some_destination") }}
{{ construct(property, "some_destination") }}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from datetime import date
from typing import cast, Union

from dateutil.parser import isoparse

some_source = date(2020, 10, 12)


some_destination: Union[Unset, str] = UNSET
if not isinstance(some_source, Unset):

some_destination = some_source.isoformat() if some_source else None





a_prop = None
_a_prop = some_destination
if _a_prop is not None:
a_prop = isoparse(cast(str, _a_prop)).date()

Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from datetime import date
from typing import cast, Union

from dateutil.parser import isoparse

some_source = date(2020, 10, 12)


some_destination = some_source.isoformat()




a_prop = isoparse(some_destination).date()

Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from datetime import date
from typing import cast, Union

from dateutil.parser import isoparse

some_source = date(2020, 10, 12)


some_destination = some_source.isoformat() if some_source else None




a_prop = None
_a_prop = some_destination
if _a_prop is not None:
a_prop = isoparse(cast(str, _a_prop)).date()

Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
from pathlib import Path

import jinja2


def test_required_not_nullable():
from openapi_python_client.parser.properties import DateProperty

prop = DateProperty(
name="a_prop",
required=True,
nullable=False,
default=None,
)
here = Path(__file__).parent
templates_dir = here.parent.parent.parent.parent / "openapi_python_client" / "templates"

env = jinja2.Environment(
loader=jinja2.ChoiceLoader([jinja2.FileSystemLoader(here), jinja2.FileSystemLoader(templates_dir)])
)

template = env.get_template("date_property_template.py")
content = template.render(property=prop)
expected = here / "required_not_null.py"
assert content == expected.read_text()


def test_required_nullable():
from openapi_python_client.parser.properties import DateProperty

prop = DateProperty(
name="a_prop",
required=True,
nullable=True,
default=None,
)
here = Path(__file__).parent
templates_dir = here.parent.parent.parent.parent / "openapi_python_client" / "templates"

env = jinja2.Environment(
loader=jinja2.ChoiceLoader([jinja2.FileSystemLoader(here), jinja2.FileSystemLoader(templates_dir)])
)

template = env.get_template("date_property_template.py")
content = template.render(property=prop)
expected = here / "required_nullable.py"
assert content == expected.read_text()


def test_optional_nullable():
from openapi_python_client.parser.properties import DateProperty

prop = DateProperty(
name="a_prop",
required=False,
nullable=True,
default=None,
)
here = Path(__file__).parent
templates_dir = here.parent.parent.parent.parent / "openapi_python_client" / "templates"

env = jinja2.Environment(
loader=jinja2.ChoiceLoader([jinja2.FileSystemLoader(here), jinja2.FileSystemLoader(templates_dir)])
)

template = env.get_template("date_property_template.py")
content = template.render(property=prop)
expected = here / "optional_nullable.py"
assert content == expected.read_text()