From 88655dc5ea34bd45c08091532f49b8a11d37ee8a Mon Sep 17 00:00:00 2001 From: Andy Grigg Date: Fri, 23 Feb 2024 10:20:21 -0500 Subject: [PATCH] Support optional fields in models (#497) --- src/ansys/openapi/common/__init__.py | 4 ++- src/ansys/openapi/common/_api_client.py | 10 +++---- src/ansys/openapi/common/_base/_types.py | 19 +++++++++--- tests/models/example_model.py | 12 ++++---- tests/test_api_client.py | 38 ++++++++++++++++++++++++ 5 files changed, 68 insertions(+), 15 deletions(-) diff --git a/src/ansys/openapi/common/__init__.py b/src/ansys/openapi/common/__init__.py index 22c23cd5..5d1e7122 100644 --- a/src/ansys/openapi/common/__init__.py +++ b/src/ansys/openapi/common/__init__.py @@ -5,7 +5,7 @@ __version__ = metadata.version("ansys-openapi-common") from ._api_client import ApiClient -from ._base import ApiBase, ApiClientBase, ModelBase +from ._base import ApiBase, ApiClientBase, ModelBase, Unset, Unset_Type from ._exceptions import ApiConnectionException, ApiException, AuthenticationWarning from ._session import ApiClientFactory, OIDCSessionBuilder from ._util import SessionConfiguration, generate_user_agent @@ -22,4 +22,6 @@ "ApiBase", "ApiClientBase", "ModelBase", + "Unset", + "Unset_Type", ] diff --git a/src/ansys/openapi/common/_api_client.py b/src/ansys/openapi/common/_api_client.py index 2dd4af46..360ff613 100644 --- a/src/ansys/openapi/common/_api_client.py +++ b/src/ansys/openapi/common/_api_client.py @@ -25,7 +25,7 @@ from dateutil.parser import parse import requests -from ._base import ApiClientBase, DeserializedType, ModelBase, PrimitiveType, SerializedType +from ._base import ApiClientBase, DeserializedType, ModelBase, PrimitiveType, SerializedType, Unset from ._exceptions import ApiException from ._util import SessionConfiguration, handle_response @@ -133,7 +133,7 @@ def __call_api( collection_formats: Optional[Dict[str, str]] = None, _preload_content: bool = True, _request_timeout: Union[float, Tuple[float, float], None] = None, - response_type_map: Optional[Dict[int, Union[str, None]]] = None, + response_type_map: Optional[Mapping[int, Union[str, None]]] = None, ) -> Union[requests.Response, DeserializedType, None]: # header parameters header_params = header_params or {} @@ -272,7 +272,7 @@ def sanitize_for_serialization(self, obj: Any) -> Any: obj_dict = { obj.attribute_map[attr]: getattr(obj, attr) for attr in obj.swagger_types - if getattr(obj, attr) is not None + if getattr(obj, attr) is not Unset } return {key: self.sanitize_for_serialization(val) for key, val in obj_dict.items()} @@ -397,7 +397,7 @@ def call_api( collection_formats: Optional[Dict[str, str]] = None, _preload_content: bool = True, _request_timeout: Union[float, Tuple[float, float], None] = None, - response_type_map: Optional[Dict[int, Union[str, None]]] = None, + response_type_map: Optional[Mapping[int, Union[str, None]]] = None, ) -> Union[requests.Response, DeserializedType, None]: """Make the HTTP request and return the deserialized data. @@ -854,7 +854,7 @@ def __deserialize_model( if key not in klass.swagger_types: instance[key] = value try: - klass_name = instance.get_real_child_model(data) + klass_name = instance.get_real_child_model(data) # type: ignore[arg-type] if klass_name: instance = self.__deserialize(data, klass_name) # type: ignore[assignment] except NotImplementedError: diff --git a/src/ansys/openapi/common/_base/_types.py b/src/ansys/openapi/common/_base/_types.py index dd7e4fff..72284cb1 100644 --- a/src/ansys/openapi/common/_base/_types.py +++ b/src/ansys/openapi/common/_base/_types.py @@ -2,7 +2,7 @@ import datetime from enum import Enum import pprint -from typing import Any, Dict, List, Optional, Tuple, Union +from typing import Any, Dict, List, Literal, Mapping, Optional, Tuple, Union import requests @@ -75,7 +75,7 @@ def to_str(self) -> str: """ return pprint.pformat(self.to_dict()) - def get_real_child_model(self, data: Union[Dict, str]) -> str: + def get_real_child_model(self, data: Dict[str, str]) -> str: """Classes with discriminators will override this method and may change the method signature.""" raise NotImplementedError() @@ -110,12 +110,23 @@ def call_api( header_params: Union[Dict[str, Union[str, int]], None] = None, body: Optional[DeserializedType] = None, post_params: Optional[List[Tuple[str, Union[str, bytes]]]] = None, - files: Optional[Dict[str, str]] = None, + files: Optional[Mapping[str, str]] = None, response_type: Optional[str] = None, _return_http_data_only: Optional[bool] = None, collection_formats: Optional[Dict[str, str]] = None, _preload_content: bool = True, _request_timeout: Union[float, Tuple[float, float], None] = None, - response_type_map: Optional[Dict[int, Union[str, None]]] = None, + response_type_map: Optional[Mapping[int, Union[str, None]]] = None, ) -> Union[requests.Response, DeserializedType, None]: """Provide method signature for calling the API.""" + + +class _Unset: + @staticmethod + def __bool__() -> Literal[False]: + return False + + +Unset_Type = _Unset +Unset = _Unset() +"""Magic value to indicate a value has not been set.""" diff --git a/tests/models/example_model.py b/tests/models/example_model.py index a55656f0..ac14a322 100644 --- a/tests/models/example_model.py +++ b/tests/models/example_model.py @@ -1,4 +1,4 @@ -from ansys.openapi.common import ModelBase +from ansys.openapi.common import ModelBase, Unset class ExampleModel(ModelBase): @@ -31,15 +31,17 @@ class ExampleModel(ModelBase): def __init__( self, - string_property=None, - int_property=None, - bool_property=None, - list_property=None, + string_property=Unset, + int_property=Unset, + bool_property=Unset, + list_property=Unset, ): # noqa: E501 self._string_property = None self._int_property = None self._bool_property = None self._list_property = None + self._nullable_property = None + self._optional_property = None self.discriminator = None if string_property is not None: self._string_property = string_property diff --git a/tests/test_api_client.py b/tests/test_api_client.py index e7089b4f..c2ef9deb 100644 --- a/tests/test_api_client.py +++ b/tests/test_api_client.py @@ -186,6 +186,44 @@ def test_serialize_model(self): serialized_model = self._client.sanitize_for_serialization(model_instance) assert serialized_model == model_dict + def test_serialize_model_null_values(self): + from . import models + + self._client.setup_client(models) + model_instance = models.ExampleModel("foo", None, False, None) + model_dict = { + "Boolean": False, + "Integer": None, + "ListOfStrings": None, + "String": "foo", + } + serialized_model = self._client.sanitize_for_serialization(model_instance) + assert serialized_model == model_dict + + def test_serialize_model_unset_values(self): + from . import models + + self._client.setup_client(models) + model_instance = models.ExampleModel(int_property=3, list_property=["It's", "a", "list"]) + model_dict = { + "Integer": 3, + "ListOfStrings": ["It's", "a", "list"], + } + serialized_model = self._client.sanitize_for_serialization(model_instance) + assert serialized_model == model_dict + + def test_serialize_model_set_unset_and_null_values(self): + from . import models + + self._client.setup_client(models) + model_instance = models.ExampleModel(int_property=None, list_property=["It's", "a", "list"]) + model_dict = { + "Integer": None, + "ListOfStrings": ["It's", "a", "list"], + } + serialized_model = self._client.sanitize_for_serialization(model_instance) + assert serialized_model == model_dict + def test_serialize_enum_model(self): from . import models