diff --git a/openapi_python_client/parser/properties.py b/openapi_python_client/parser/properties.py index 88db0b7a9..c1076e9dd 100644 --- a/openapi_python_client/parser/properties.py +++ b/openapi_python_client/parser/properties.py @@ -64,7 +64,9 @@ def get_imports(self, *, prefix: str) -> Set[str]: back to the root of the generated client. """ if self.nullable or not self.required: - return {"from typing import Optional"} + return {"from typing import Optional", + "from typing import cast", + f"from {prefix}types import UNSET"} return set() def to_string(self) -> str: @@ -72,15 +74,28 @@ def to_string(self) -> str: if self.default: default = self.default elif not self.required: - default = "None" + default = "cast(None, UNSET)" else: default = None if default is not None: - return f"{self.python_name}: {self.get_type_string()} = {self.default}" + return f"{self.python_name}: {self.get_type_string()} = {default}" else: return f"{self.python_name}: {self.get_type_string()}" + def to_query_method_arg(self) -> str: + """ How this should be declared in a query string """ + if self.default: + default = self.default + elif not self.required: + default = "None" + else: + default = None + + if default is not None: + return f"{self.python_name}: {self.get_type_string()} = {default}" + else: + return f"{self.python_name}: {self.get_type_string()}" @dataclass class StringProperty(Property): diff --git a/openapi_python_client/templates/endpoint_macros.pyi b/openapi_python_client/templates/endpoint_macros.pyi index 7fe2ca053..1d5df8c23 100644 --- a/openapi_python_client/templates/endpoint_macros.pyi +++ b/openapi_python_client/templates/endpoint_macros.pyi @@ -80,7 +80,7 @@ client: Client, {% endif %} {# path parameters #} {% for parameter in endpoint.path_parameters %} -{{ parameter.to_string() }}, +{{parameter.to_query_method_arg()}}, {% endfor %} {# Form data if any #} {% if endpoint.form_body_reference %} @@ -96,10 +96,10 @@ json_body: {{ endpoint.json_body.get_type_string() }}, {% endif %} {# query parameters #} {% for parameter in endpoint.query_parameters %} -{{ parameter.to_string() }}, +{{parameter.to_query_method_arg()}}, {% endfor %} {% for parameter in endpoint.header_parameters %} -{{ parameter.to_string() }}, +{{ parameter.to_query_method_arg() }}, {% endfor %} {% endmacro %} diff --git a/openapi_python_client/templates/model.pyi b/openapi_python_client/templates/model.pyi index 899256b2d..4aa246273 100644 --- a/openapi_python_client/templates/model.pyi +++ b/openapi_python_client/templates/model.pyi @@ -24,11 +24,17 @@ class {{ model.reference.class_name }}: {% endif %} {% endfor %} - return { - {% for property in model.required_properties + model.optional_properties %} - "{{ property.name }}": {{ property.python_name }}, - {% endfor %} - } + properties: Dict[str, Any] = dict() + + {% for property in model.required_properties + model.optional_properties %} + {% if not property.required %} + if {{property.python_name}} is not UNSET: + properties["{{ property.name }}"] = {{ property.python_name }} + {% else %} + properties["{{ property.name }}"] = {{ property.python_name }} + {% endif %} + {% endfor %} + return properties @staticmethod def from_dict(d: Dict[str, Any]) -> "{{ model.reference.class_name }}": diff --git a/openapi_python_client/templates/property_templates/date_property.pyi b/openapi_python_client/templates/property_templates/date_property.pyi index 416acc1e1..2ab3b0f10 100644 --- a/openapi_python_client/templates/property_templates/date_property.pyi +++ b/openapi_python_client/templates/property_templates/date_property.pyi @@ -12,6 +12,9 @@ if {{ source }} is not None: {% if property.required %} {{ destination }} = {{ source }}.isoformat() {% else %} -{{ destination }} = {{ source }}.isoformat() if {{ source }} else None +if {{ source }} is UNSET: + {{ destination }} = UNSET +else: + {{ destination }} = {{ source }}.isoformat() if {{ source }} else None {% endif %} {% endmacro %} diff --git a/openapi_python_client/templates/property_templates/datetime_property.pyi b/openapi_python_client/templates/property_templates/datetime_property.pyi index ff57249a5..e3cf3242d 100644 --- a/openapi_python_client/templates/property_templates/datetime_property.pyi +++ b/openapi_python_client/templates/property_templates/datetime_property.pyi @@ -12,6 +12,9 @@ if {{ source }} is not None: {% if property.required %} {{ destination }} = {{ source }}.isoformat() {% else %} -{{ destination }} = {{ source }}.isoformat() if {{ source }} else None +if {{ source }} is UNSET: + {{ destination }} = UNSET +else: + {{ destination }} = {{ source }}.isoformat() if {{ source }} else None {% endif %} {% endmacro %} diff --git a/openapi_python_client/templates/property_templates/dict_property.pyi b/openapi_python_client/templates/property_templates/dict_property.pyi index 2feabd1d2..e4c8a9edc 100644 --- a/openapi_python_client/templates/property_templates/dict_property.pyi +++ b/openapi_python_client/templates/property_templates/dict_property.pyi @@ -12,6 +12,9 @@ if {{ source }} is not None: {% if property.required %} {{ destination }} = {{ source }} {% else %} -{{ destination }} = {{ source }} if {{ source }} else None +if {{ source }} is UNSET: + {{ destination }} = UNSET +else: + {{ destination }} = {{ source }} if {{ source }} else None {% endif %} {% endmacro %} diff --git a/openapi_python_client/templates/property_templates/enum_property.pyi b/openapi_python_client/templates/property_templates/enum_property.pyi index 2cff340c3..e513ab347 100644 --- a/openapi_python_client/templates/property_templates/enum_property.pyi +++ b/openapi_python_client/templates/property_templates/enum_property.pyi @@ -1,3 +1,5 @@ +from openapi_python_client.templates.types import UNSET + {% macro construct(property, source) %} {% if property.required %} {{ property.python_name }} = {{ property.reference.class_name }}({{ source }}) @@ -12,6 +14,9 @@ if {{ source }} is not None: {% if property.required %} {{ destination }} = {{ source }}.value {% else %} -{{ destination }} = {{ source }}.value if {{ source }} else None +if {{ source }} is UNSET: + {{ destination }} = UNSET +else: + {{ destination }} = {{ source }}.value if {{ source }} else None {% endif %} {% endmacro %} diff --git a/openapi_python_client/templates/property_templates/file_property.pyi b/openapi_python_client/templates/property_templates/file_property.pyi index 444c9dbcb..10914730b 100644 --- a/openapi_python_client/templates/property_templates/file_property.pyi +++ b/openapi_python_client/templates/property_templates/file_property.pyi @@ -7,6 +7,9 @@ {% if property.required %} {{ destination }} = {{ source }}.to_tuple() {% else %} -{{ destination }} = {{ source }}.to_tuple() if {{ source }} else None +if {{ source }} is UNSET: + {{ destination }} = UNSET +else: + {{ destination }} = {{ source }}.to_tuple() if {{ source }} else None {% endif %} {% endmacro %} diff --git a/openapi_python_client/templates/property_templates/list_property.pyi b/openapi_python_client/templates/property_templates/list_property.pyi index 015eb0880..2f96d5d87 100644 --- a/openapi_python_client/templates/property_templates/list_property.pyi +++ b/openapi_python_client/templates/property_templates/list_property.pyi @@ -1,3 +1,5 @@ +from openapi_python_client.templates.types import UNSET + {% macro construct(property, source) %} {% set inner_property = property.inner_property %} {% if inner_property.template %} @@ -36,6 +38,8 @@ for {{ inner_source }} in {{ source }}: {% if not property.required %} if {{ source }} is None: {{ destination }} = None +elif {{ source }} is UNSET: + {{ destination }} = UNSET else: {{ _transform(property, source, destination) | indent(4) }} {% else %} diff --git a/openapi_python_client/templates/property_templates/ref_property.pyi b/openapi_python_client/templates/property_templates/ref_property.pyi index c38a5199c..ebd3cd674 100644 --- a/openapi_python_client/templates/property_templates/ref_property.pyi +++ b/openapi_python_client/templates/property_templates/ref_property.pyi @@ -12,6 +12,9 @@ if {{ source }} is not None: {% if property.required %} {{ destination }} = {{ source }}.to_dict() {% else %} -{{ destination }} = {{ source }}.to_dict() if {{ source }} else None +if {{ source }} is UNSET: + {{ destination }} = UNSET +else: + {{ destination }} = {{ source }}.to_dict() if {{ source }} else None {% endif %} {% endmacro %} diff --git a/openapi_python_client/templates/property_templates/union_property.pyi b/openapi_python_client/templates/property_templates/union_property.pyi index 1498d68c7..b4b0e24d9 100644 --- a/openapi_python_client/templates/property_templates/union_property.pyi +++ b/openapi_python_client/templates/property_templates/union_property.pyi @@ -25,6 +25,8 @@ def _parse_{{ property.python_name }}(data: Dict[str, Any]) -> {{ property.get_t {% if not property.required %} if {{ source }} is None: {{ destination }}: {{ property.get_type_string() }} = None +elif {{ source }} is UNSET: + {{ destination }} = UNSET {% endif %} {% for inner_property in property.inner_properties %} {% if loop.first and property.required %}{# No if None statement before this #} @@ -36,7 +38,7 @@ else: {% endif %} {% if inner_property.template %} {% from "property_templates/" + inner_property.template import transform %} - {{ transform(inner_property, source, destination) | indent(8) }} + {{ transform(inner_property, source, destination) | indent(4) }} {% else %} {{ destination }} = {{ source }} {% endif %} diff --git a/openapi_python_client/templates/types.py b/openapi_python_client/templates/types.py index 951227435..84146cef2 100644 --- a/openapi_python_client/templates/types.py +++ b/openapi_python_client/templates/types.py @@ -1,8 +1,11 @@ """ Contains some shared types for properties """ -from typing import BinaryIO, Generic, MutableMapping, Optional, TextIO, Tuple, TypeVar, Union +from typing import BinaryIO, Generic, MutableMapping, NewType, Optional, TextIO, Tuple, TypeVar, Union import attr +Unset = NewType("Unset", object) +UNSET: Unset = Unset(object()) + @attr.s(auto_attribs=True) class File: diff --git a/tests/test_openapi_parser/test_properties.py b/tests/test_openapi_parser/test_properties.py index c0f4cea6a..70c25cfbd 100644 --- a/tests/test_openapi_parser/test_properties.py +++ b/tests/test_openapi_parser/test_properties.py @@ -43,7 +43,7 @@ def test_to_string(self, mocker): assert p.to_string() == f"{snake_case(name)}: {get_type_string()}" p.required = False - assert p.to_string() == f"{snake_case(name)}: {get_type_string()} = None" + assert p.to_string() == f"{snake_case(name)}: {get_type_string()} = cast(None, UNSET)" p.default = "TEST" assert p.to_string() == f"{snake_case(name)}: {get_type_string()} = TEST" @@ -57,7 +57,26 @@ def test_get_imports(self, mocker): assert p.get_imports(prefix="") == set() p.required = False - assert p.get_imports(prefix="") == {"from typing import Optional"} + assert p.get_imports(prefix="") == { + "from types import UNSET", + "from typing import Optional", + "from typing import cast", + } + + def test_to_query_method_arg(self, mocker): + from openapi_python_client.parser.properties import Property + + name = mocker.MagicMock() + snake_case = mocker.patch("openapi_python_client.utils.snake_case") + p = Property(name=name, required=True, default=None, nullable=False) + get_type_string = mocker.patch.object(p, "get_type_string") + + assert p.to_query_method_arg() == f"{snake_case(name)}: {get_type_string()}" + p.required = False + assert p.to_query_method_arg() == f"{snake_case(name)}: {get_type_string()} = None" + + p.default = "TEST" + assert p.to_query_method_arg() == f"{snake_case(name)}: {get_type_string()} = TEST" def test__validate_default(self): from openapi_python_client.parser.properties import Property