diff --git a/.github/dependabot.yml b/.github/dependabot.yml index c78c2ed..e9b8cea 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,6 +5,14 @@ updates: schedule: interval: daily open-pull-requests-limit: 10 + groups: + open-telemetry: + patterns: + - "*opentelemetry*" + pylint: + patterns: + - "*pylint*" + - "*astroid*" - package-ecosystem: github-actions directory: "/" schedule: diff --git a/.github/policies/resourceManagement.yml b/.github/policies/resourceManagement.yml index 0f7b81e..dedbd45 100644 --- a/.github/policies/resourceManagement.yml +++ b/.github/policies/resourceManagement.yml @@ -16,7 +16,7 @@ configuration: - isIssue - isOpen - hasLabel: - label: 'Needs: Author Feedback' + label: 'status:waiting-for-author-feedback' - hasLabel: label: 'Status: No Recent Activity' - noActivitySince: @@ -31,7 +31,7 @@ configuration: - isIssue - isOpen - hasLabel: - label: 'Needs: Author Feedback' + label: 'status:waiting-for-author-feedback' - noActivitySince: days: 4 - isNotLabeledWith: @@ -64,13 +64,13 @@ configuration: - isActivitySender: issueAuthor: True - hasLabel: - label: 'Needs: Author Feedback' + label: 'status:waiting-for-author-feedback' - isOpen then: - addLabel: label: 'Needs: Attention :wave:' - removeLabel: - label: 'Needs: Author Feedback' + label: 'status:waiting-for-author-feedback' description: - if: - payloadType: Issues diff --git a/.github/workflows/auto-merge-dependabot.yml b/.github/workflows/auto-merge-dependabot.yml index 145487d..3d9334e 100644 --- a/.github/workflows/auto-merge-dependabot.yml +++ b/.github/workflows/auto-merge-dependabot.yml @@ -19,7 +19,7 @@ jobs: steps: - name: Dependabot metadata id: metadata - uses: dependabot/fetch-metadata@v2.0.0 + uses: dependabot/fetch-metadata@v2.2.0 with: github-token: "${{ secrets.GITHUB_TOKEN }}" diff --git a/CHANGELOG.md b/CHANGELOG.md index e50400c..4bd64b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,14 +2,45 @@ All notable changes to this project will be documented in this file. -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.3.3] - 2024-10-03 + +### Fixed + +- Fixed an issue where `pendulum.DateTime` objects were not properly serialized by `JsonSerializationWriter`. Now, `pendulum.DateTime` objects are correctly recognized as subclasses of `datetime.datetime` and serialized accordingly. + +## [1.3.2] - 2024-09-10 + +### Added + +- Fixed numeric strings from being parsed as Datetime objects to being parsed as strings. +- Only parse to Datetime objects that conform to ISO 8601 format. + +## [1.3.1] - 2024-08-23 + +### Added + +- Fixed 4-digit numeric strings from being parsed as Datetime objects to being parsed as strings. + +## [1.3.0] - 2024-07-26 + +### Added + +- Support `dict[str, Any]` and `list[dict[str, Any]]` when writing additional data. + +### Changed + +- Fixed a bug where date time deserialization would fail because of empty strings. +- Fixed a bug where float deserialization if the number represented qualified as an int. + ## [1.2.0] - 2024-04-09 ### Added ### Changed + - Enhanced error handling: Enabled silent failure when an enum key is not available ## [1.1.0] - 2024-02-29 @@ -17,6 +48,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added ### Changed + - Support objects and collections when writing additional data. ## [1.0.1] - 2023-12-16 @@ -24,6 +56,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added ### Changed + - Bump pendulum to v3.0.0b1 for python 3.12 support. ## [1.0.0] - 2023-10-31 @@ -31,6 +64,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added ### Changed + - GA release ## [0.4.2] - 2023-10-11 @@ -38,6 +72,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added ### Changed + - Switched from python-dateutil to pendulum for parsing datetime types. ## [0.4.1] - 2023-09-21 @@ -45,6 +80,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added ### Changed + - Allow passing of valid strings as values for datetime and UUID fields. ## [0.4.0] - 2023-07-27 @@ -52,6 +88,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added ### Changed + - Enabled backing store support ## [0.3.7] - 2023-07-04 @@ -59,6 +96,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added ### Changed + - Fixes the key assignment to the writer in write_bytes_value. ## [0.3.6] - 2023-06-27 @@ -66,6 +104,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added ### Changed + - Fixed a bug with loading json response in method to get root parse node. ## [0.3.5] - 2023-06-14 @@ -126,4 +165,3 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Fixed a bug with deserializing 'None' string values in enums. - diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index f9ba8cf..686e5e7 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -7,3 +7,4 @@ Resources: - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns +- Employees can reach out at [aka.ms/opensource/moderation-support](https://aka.ms/opensource/moderation-support) diff --git a/README.md b/README.md index 46c8d17..5b81f69 100644 --- a/README.md +++ b/README.md @@ -1,40 +1,3 @@ # Microsoft Kiota Serialization library for JSON -[![PyPI version](https://badge.fury.io/py/microsoft-kiota-serialization-json.svg)](https://badge.fury.io/py/microsoft-kiota-serialization-json) -[![CI Actions Status](https://github.com/microsoft/kiota-serialization-json-python/actions/workflows/build_publish.yml/badge.svg?branch=main)](https://github.com/microsoft/kiota-serialization-json-python/actions) -[![Downloads](https://pepy.tech/badge/microsoft-kiota-serialization-json)](https://pepy.tech/project/microsoft-kiota-serialization-json) -The Microsoft Kiota Serialization Library for JSON is a python implementation to serialize/deserialize JSON. - -A [Kiota](https://github.com/microsoft/kiota) generated project will need a reference to a json serialization package to handle json payloads from an API endpoint. - -Read more about Kiota [here](https://github.com/microsoft/kiota/blob/main/README.md). - -## Using the Microsoft Kiota Serialization JSON library - -In order to use this library, install the package by running: - -```cmd -pip install microsoft-kiota-serialization-json -``` - -## Contributing - -This project welcomes contributions and suggestions. Most contributions require you to agree to a -Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us -the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. - -When you submit a pull request, a CLA bot will automatically determine whether you need to provide -a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions -provided by the bot. You will only need to do this once across all repos using our CLA. - -This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). -For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or -contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. - -## Trademarks - -This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft -trademarks or logos is subject to and must follow -[Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general). -Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. -Any use of third-party trademarks or logos are subject to those third-party's policies. +This repository has been archived and source migrated. You can contribute and file issues in the [Kiota Python](https://github.com/microsoft/kiota-python) repository. diff --git a/kiota_serialization_json/_version.py b/kiota_serialization_json/_version.py index a9ecfe2..9ca2418 100644 --- a/kiota_serialization_json/_version.py +++ b/kiota_serialization_json/_version.py @@ -1 +1 @@ -VERSION: str = '1.2.0' +VERSION: str = '1.3.3' diff --git a/kiota_serialization_json/json_parse_node.py b/kiota_serialization_json/json_parse_node.py index 2a2dd4f..937ccd3 100644 --- a/kiota_serialization_json/json_parse_node.py +++ b/kiota_serialization_json/json_parse_node.py @@ -8,6 +8,8 @@ from uuid import UUID import pendulum +import re + from kiota_abstractions.serialization import Parsable, ParsableFactory, ParseNode T = TypeVar("T") @@ -67,9 +69,9 @@ def get_int_value(self) -> Optional[int]: def get_float_value(self) -> Optional[float]: """Gets the float value of the json node Returns: - float: The integer value of the node + float: The number value of the node """ - return self._json_node if isinstance(self._json_node, float) else None + return float(self._json_node) if isinstance(self._json_node, (float, int)) else None def get_uuid_value(self) -> Optional[UUID]: """Gets the UUID value of the json node @@ -89,7 +91,11 @@ def get_datetime_value(self) -> Optional[datetime]: """ if isinstance(self._json_node, datetime): return self._json_node + if isinstance(self._json_node, str): + if len(self._json_node) < 10: + return None + datetime_obj = pendulum.parse(self._json_node, exact=True) if isinstance(datetime_obj, pendulum.DateTime): return datetime_obj @@ -296,11 +302,13 @@ def try_get_anything(self, value: Any) -> Any: return dict(map(lambda x: (x[0], self.try_get_anything(x[1])), value.items())) if isinstance(value, str): try: + if value.isdigit(): + return value datetime_obj = pendulum.parse(value) if isinstance(datetime_obj, pendulum.Duration): return datetime_obj.as_timedelta() return datetime_obj - except: + except ValueError: pass try: return UUID(value) diff --git a/kiota_serialization_json/json_serialization_writer.py b/kiota_serialization_json/json_serialization_writer.py index 8fc5ed4..534e937 100644 --- a/kiota_serialization_json/json_serialization_writer.py +++ b/kiota_serialization_json/json_serialization_writer.py @@ -16,7 +16,6 @@ class JsonSerializationWriter(SerializationWriter): - PROPERTY_SEPARATOR: str = ',' def __init__(self) -> None: @@ -80,7 +79,7 @@ def write_uuid_value(self, key: Optional[str], value: Optional[UUID]) -> None: """Writes the specified uuid value to the stream with an optional given key. Args: key (Optional[str]): The key to be used for the written value. May be null. - value (Optional[UUId]): The uuid value to be written. + value (Optional[UUID]): The uuid value to be written. """ if isinstance(value, UUID): if key: @@ -107,9 +106,9 @@ def write_datetime_value(self, key: Optional[str], value: Optional[datetime]) -> """ if isinstance(value, datetime): if key: - self.writer[key] = str(value.isoformat()) + self.writer[key] = value.isoformat() else: - self.value = str(value.isoformat()) + self.value = value.isoformat() elif isinstance(value, str): try: pendulum.parse(value) @@ -203,7 +202,7 @@ def write_collection_of_primitive_values( if isinstance(values, list): result = [] for val in values: - temp_writer = self._create_new_writer() + temp_writer: JsonSerializationWriter = self._create_new_writer() temp_writer.write_any_value(None, val) result.append(temp_writer.value) @@ -239,7 +238,7 @@ def write_collection_of_enum_values( """Writes the specified collection of enum values to the stream with an optional given key. Args: key (Optional[str]): The key to be used for the written value. May be null. - values Optional[List[Enum]): The enum values to be written. + values (Optional[List[Enum]]): The enum values to be written. """ if isinstance(values, list): result = [] @@ -253,6 +252,28 @@ def write_collection_of_enum_values( else: self.value = result + def __write_collection_of_dict_values( + self, key: Optional[str], values: Optional[List[Dict[str, Any]]] + ) -> None: + """Writes the specified collection of dictionary values to the stream with an optional + given key. + Args: + key (Optional[str]): The key to be used for the written value. May be null. + values (Optional[List[Dict[str, Any]]]): The collection of dictionary values + to be written. + """ + if isinstance(values, list): + result = [] + for val in values: + temp_writer: JsonSerializationWriter = self._create_new_writer() + temp_writer.__write_dict_value(None, val) + result.append(temp_writer.value) + + if key: + self.writer[key] = result + else: + self.value = result + def write_bytes_value(self, key: Optional[str], value: bytes) -> None: """Writes the specified byte array as a base64 string to the stream with an optional given key. @@ -320,10 +341,25 @@ def write_null_value(self, key: Optional[str]) -> None: else: self.value = "null" + def __write_dict_value(self, key: Optional[str], value: Dict[str, Any]) -> None: + """Writes the specified dictionary value to the stream with an optional given key. + Args: + key (Optional[str]): The key to be used for the written value. May be null. + value (Dict[str, Any]): The dictionary value to be written. + """ + if isinstance(value, dict): + temp_writer: JsonSerializationWriter = self._create_new_writer() + for dict_key, dict_value in value.items(): + temp_writer.write_any_value(dict_key, dict_value) + if key: + self.writer[key] = temp_writer.writer + else: + self.value = temp_writer.writer + def write_additional_data_value(self, value: Dict[str, Any]) -> None: """Writes the specified additional data to the stream. Args: - value (Dict[str, Any]): he additional data to be written. + value (Dict[str, Any]): The additional data to be written. """ if isinstance(value, dict): for key, val in value.items(): @@ -353,17 +389,17 @@ def get_serialized_content(self) -> bytes: def on_before_object_serialization(self) -> Optional[Callable[[Parsable], None]]: """Gets the callback called before the object gets serialized. Returns: - Optional[Callable[[Parsable], None]]:the callback called before the object + Optional[Callable[[Parsable], None]]: The callback called before the object gets serialized. """ return self._on_before_object_serialization @on_before_object_serialization.setter def on_before_object_serialization(self, value: Optional[Callable[[Parsable], None]]) -> None: - """Sets the callback called before the objects gets serialized. + """Sets the callback called before the objects get serialized. Args: - value (Optional[Callable[[Parsable], None]]): the callback called before the objects - gets serialized. + value (Optional[Callable[[Parsable], None]]): The callback called before the objects + get serialized. """ self._on_before_object_serialization = value @@ -371,17 +407,17 @@ def on_before_object_serialization(self, value: Optional[Callable[[Parsable], No def on_after_object_serialization(self) -> Optional[Callable[[Parsable], None]]: """Gets the callback called after the object gets serialized. Returns: - Optional[Optional[Callable[[Parsable], None]]]: the callback called after the object + Optional[Callable[[Parsable], None]]: The callback called after the object gets serialized. """ return self._on_after_object_serialization @on_after_object_serialization.setter def on_after_object_serialization(self, value: Optional[Callable[[Parsable], None]]) -> None: - """Sets the callback called after the objects gets serialized. + """Sets the callback called after the objects get serialized. Args: - value (Optional[Callable[[Parsable], None]]): the callback called after the objects - gets serialized. + value (Optional[Callable[[Parsable], None]]): The callback called after the objects + get serialized. """ self._on_after_object_serialization = value @@ -391,7 +427,7 @@ def on_start_object_serialization( ) -> Optional[Callable[[Parsable, SerializationWriter], None]]: """Gets the callback called right after the serialization process starts. Returns: - Optional[Callable[[Parsable, SerializationWriter], None]]: the callback called + Optional[Callable[[Parsable, SerializationWriter], None]]: The callback called right after the serialization process starts. """ return self._on_start_object_serialization @@ -402,7 +438,7 @@ def on_start_object_serialization( ) -> None: """Sets the callback called right after the serialization process starts. Args: - value (Optional[Callable[[Parsable, SerializationWriter], None]]): the callback + value (Optional[Callable[[Parsable, SerializationWriter], None]]): The callback called right after the serialization process starts. """ self._on_start_object_serialization = value @@ -421,16 +457,13 @@ def write_non_parsable_object_value(self, key: Optional[str], value: T) -> None: def write_any_value(self, key: Optional[str], value: Any) -> Any: """Writes the specified value to the stream with an optional given key. + Args: key (Optional[str]): The key to be used for the written value. May be null. - value Any): The value to be written. + value (Any): The value to be written. """ - value_type = type(value) if value is None: self.write_null_value(key) - elif value_type in PRIMITIVE_TYPES: - method = getattr(self, f'write_{value_type.__name__.lower()}_value') - method(key, value) elif isinstance(value, Parsable): self.write_object_value(key, value) elif isinstance(value, list): @@ -438,20 +471,32 @@ def write_any_value(self, key: Optional[str], value: Any) -> Any: self.write_collection_of_object_values(key, value) elif all(isinstance(x, Enum) for x in value): self.write_collection_of_enum_values(key, value) - elif all((type(x) in PRIMITIVE_TYPES) for x in value): + elif all( + any(isinstance(x, primitive_type) for primitive_type in PRIMITIVE_TYPES) + for x in value + ): self.write_collection_of_primitive_values(key, value) + elif all(isinstance(x, dict) for x in value): + self.__write_collection_of_dict_values(key, value) else: raise TypeError( - f"Encountered an unknown collection type during serialization \ - {value_type} with key {key}" + f"Encountered an unknown collection type during serialization {type(value)} \ + with key {key}" ) - elif hasattr(value, '__dict__'): - self.write_non_parsable_object_value(key, value) + elif isinstance(value, dict): + self.__write_dict_value(key, value) else: - raise TypeError( - f"Encountered an unknown type during serialization {value_type} \ - with key {key}" - ) + for primitive_type in PRIMITIVE_TYPES: + if isinstance(value, primitive_type): + method = getattr(self, f"write_{primitive_type.__name__.lower()}_value") + method(key, value) + return + if hasattr(value, "__dict__"): + self.write_non_parsable_object_value(key, value) + else: + raise TypeError( + f"Encountered an unknown type during serialization {type(value)} with key {key}" + ) def _serialize_value(self, temp_writer: JsonSerializationWriter, value: U): if on_before := self.on_before_object_serialization: diff --git a/requirements-dev.txt b/requirements-dev.txt index f6afff0..dd01a37 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,14 +1,14 @@ -i https://pypi.org/simple -astroid==3.1.0 +astroid==3.2.4 -certifi==2024.2.2 +certifi==2024.8.30 -charset-normalizer==3.3.2 +charset-normalizer==3.4.0 -coverage[toml]==7.4.4 +coverage[toml]==7.6.1 -dill==0.3.8 +dill==0.3.9 docutils==0.20.1 @@ -16,7 +16,7 @@ flit==3.9.0 flit-core==3.9.0 -idna==3.7 +idna==3.10 importlib-metadata==6.8.0 ; python_version >= '3.8' @@ -28,47 +28,47 @@ lazy-object-proxy==1.10.0 mccabe==0.7.0 -mypy==1.9.0 +mypy==1.11.2 mypy-extensions==1.0.0 -packaging==24.0 +packaging==24.1 -platformdirs==4.2.0 +platformdirs==4.3.6 -pluggy==1.4.0 +pluggy==1.5.0 -pylint==3.1.0 +pylint==3.2.7 -pytest==8.1.1 +pytest==8.3.3 pytest-cov==5.0.0 -requests==2.31.0 +requests==2.32.3 toml==0.10.2 -tomli==2.0.1 +tomli==2.0.2 -tomlkit==0.12.4 +tomlkit==0.13.2 -types-python-dateutil==2.9.0.20240316 +types-python-dateutil==2.9.0.20241003 -typing-extensions==4.11.0 +typing-extensions==4.12.2 -urllib3==2.2.1 +urllib3==2.2.3 wrapt==1.16.0 yapf==0.40.2 -zipp==3.18.1 ; python_version >= '3.8' +zipp==3.20.2 ; python_version >= '3.8' colorama==0.4.6 -exceptiongroup==1.2.0 +exceptiongroup==1.2.2 -microsoft-kiota-abstractions==1.3.2 +microsoft-kiota-abstractions==1.3.3 pendulum==3.0.0 diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index 702b6a5..bca9a29 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -19,7 +19,12 @@ def user1_json(): '"updated_at": "2022-01-27T12:59:45.596117+00:00", "is_active": true}, '\ '"approvers": [{"id": "8f841f30-e6e3-439a-a812-ebd369559c36", '\ '"updated_at": "2022-01-27T12:59:45.596117+00:00", "is_active": true}, '\ - '{"display_name": "John Doe", "age": 32}]}}' + '{"display_name": "John Doe", "age": 32}], '\ + '"data": {'\ + '"groups": ['\ + '{"friends": [{"display_name": "John Doe", "age": 32}]}'\ + ']'\ + '}}}'\ @pytest.fixture diff --git a/tests/unit/test_json_parse_node.py b/tests/unit/test_json_parse_node.py index d5c605a..cf1e003 100644 --- a/tests/unit/test_json_parse_node.py +++ b/tests/unit/test_json_parse_node.py @@ -1,6 +1,7 @@ import json from datetime import date, datetime, time, timedelta from uuid import UUID +import pendulum import pytest from pendulum import DateTime, FixedTimezone @@ -28,18 +29,46 @@ def test_get_bool_value(): assert result is False -def test_get_float_value(): +def test_get_float_value_from_float(): + """ + This test is to ensure that the get_float_value method returns a float when the value is a float + """ parse_node = JsonParseNode(44.6) result = parse_node.get_float_value() + assert isinstance(result, float) assert result == 44.6 +@pytest.mark.parametrize("value", [0, 10, 100]) +def test_get_float_value(value: int): + """ + Consider an OpenAPI Specification using the type: number and format: float or double + Note: The OpenAPI Specification also allows for the use of the type: integer and format: int32 or int64 + + Consider an API with Price data [0, 0.5, 1, 1.5, 2] and so on + In this case, the contract must define the type as a number, with a hint of float or double as the format + + Kiota should be able to parse the response as a float, even if the value is an integer, because it's still a number. + """ + parse_node = JsonParseNode(value) + result = parse_node.get_float_value() + assert isinstance(result, float) + assert result == float(value) + + def test_get_uuid_value(): parse_node = JsonParseNode("f58411c7-ae78-4d3c-bb0d-3f24d948de41") result = parse_node.get_uuid_value() assert result == UUID("f58411c7-ae78-4d3c-bb0d-3f24d948de41") +@pytest.mark.parametrize("value", ["", " ", " ", "2022-01-0"]) +def test_get_datetime_value_returns_none_with_invalid_str(value: str): + parse_node = JsonParseNode(value) + result = parse_node.get_datetime_value() + assert result is None + + def test_get_datetime_value(): parse_node = JsonParseNode('2022-01-27T12:59:45.596117') result = parse_node.get_datetime_value() @@ -105,6 +134,39 @@ def test_get_enum_value_for_key_not_found(): assert result is None +def test_get_anythin_does_not_convert_numeric_chars_to_datetime(): + parse_node = JsonParseNode("1212") + result = parse_node.try_get_anything("1212") + assert isinstance(result, str) + assert result == "1212" + + +def test_get_anythin_does_not_convert_any_length_numeric_chars_to_datetime(): + parse_node = JsonParseNode("1212") + result1 = parse_node.try_get_anything("1212") + parse_node_two = JsonParseNode("-PT15M") + result2 = parse_node_two.try_get_anything("-PT15M") + parse_node_three = JsonParseNode("20081008") + result3 = parse_node_three.try_get_anything("20081008") + parse_node_four = JsonParseNode("1011317") + result4 = parse_node_four.try_get_anything("1011317") + assert isinstance(result1, str) + assert result1 == "1212" + assert isinstance(result2, str) + assert result2 == "-PT15M" + assert isinstance(result3, str) + assert result3 == "20081008" + assert isinstance(result4, str) + assert result4 == "1011317" + + +def test_get_anythin_does_convert_date_string_to_datetime(): + parse_node = JsonParseNode("2023-10-05T14:48:00.000Z") + result = parse_node.try_get_anything("2023-10-05T14:48:00.000Z") + assert isinstance(result, pendulum.DateTime) + assert result == pendulum.parse("2023-10-05T14:48:00.000Z") + + def test_get_object_value(user1_json): parse_node = JsonParseNode(json.loads(user1_json)) result = parse_node.get_object_value(User) @@ -137,6 +199,14 @@ def test_get_object_value(user1_json): "age": 32 } ] + assert result.additional_data["additional_data"]["data"] == { + "groups": [{ + "friends": [{ + "display_name": "John Doe", + "age": 32 + }] + }] + } def test_get_collection_of_object_values(users_json): diff --git a/tests/unit/test_json_parse_node_factory.py b/tests/unit/test_json_parse_node_factory.py index 174875a..2975f37 100644 --- a/tests/unit/test_json_parse_node_factory.py +++ b/tests/unit/test_json_parse_node_factory.py @@ -18,7 +18,7 @@ def test_get_root_parse_node(sample_json_string): assert isinstance(root, JsonParseNode) assert root._json_node == json.loads(sample_json_string) - + def test_get_root_parse_node_no_content_type(sample_json_string): with pytest.raises(Exception) as e_info: factory = JsonParseNodeFactory() diff --git a/tests/unit/test_json_serialization_writer.py b/tests/unit/test_json_serialization_writer.py index 925024d..462b92b 100644 --- a/tests/unit/test_json_serialization_writer.py +++ b/tests/unit/test_json_serialization_writer.py @@ -2,7 +2,6 @@ from uuid import UUID import pytest - import pendulum from kiota_serialization_json.json_serialization_writer import JsonSerializationWriter @@ -72,20 +71,23 @@ def test_write_uuid_value(): content = json_serialization_writer.get_serialized_content() content_string = content.decode('utf-8') assert content_string == '{"id": "8f841f30-e6e3-439a-a812-ebd369559c36"}' - + + def test_write_uuid_value_with_valid_string(): json_serialization_writer = JsonSerializationWriter() json_serialization_writer.write_uuid_value("id", "8f841f30-e6e3-439a-a812-ebd369559c36") content = json_serialization_writer.get_serialized_content() content_string = content.decode('utf-8') assert content_string == '{"id": "8f841f30-e6e3-439a-a812-ebd369559c36"}' - + + def test_write_uuid_value_with_invalid_string(): with pytest.raises(ValueError) as excinfo: json_serialization_writer = JsonSerializationWriter() json_serialization_writer.write_uuid_value("id", "invalid-uuid-string") assert "Invalid UUID string value found for property id" in str(excinfo.value) - + + def test_write_datetime_value(): json_serialization_writer = JsonSerializationWriter() json_serialization_writer.write_datetime_value( @@ -94,53 +96,50 @@ def test_write_datetime_value(): content = json_serialization_writer.get_serialized_content() content_string = content.decode('utf-8') assert content_string == '{"updatedAt": "2022-01-27T12:59:45.596117+00:00"}' - + + def test_write_datetime_value_valid_string(): json_serialization_writer = JsonSerializationWriter() - json_serialization_writer.write_datetime_value( - "updatedAt", "2022-01-27T12:59:45.596117" - ) + json_serialization_writer.write_datetime_value("updatedAt", "2022-01-27T12:59:45.596117") content = json_serialization_writer.get_serialized_content() content_string = content.decode('utf-8') - assert content_string == '{"updatedAt": "2022-01-27T12:59:45.596117+00:00"}' - -def test_write_datetime_value_valid_string(): + assert content_string == '{"updatedAt": "2022-01-27T12:59:45.596117"}' + + +def test_write_datetime_value_invalid_string(): with pytest.raises(ValueError) as excinfo: json_serialization_writer = JsonSerializationWriter() - json_serialization_writer.write_datetime_value( - "updatedAt", "invalid-datetime-string" - ) + json_serialization_writer.write_datetime_value("updatedAt", "invalid-datetime-string") assert "Invalid datetime string value found for property updatedAt" in str(excinfo.value) + def test_write_timedelta_value(): json_serialization_writer = JsonSerializationWriter() json_serialization_writer.write_timedelta_value( - "diff", - (pendulum.parse('2022-01-27T12:59:45.596117') - pendulum.parse('2022-01-27T10:59:45.596117')).as_timedelta() + "diff", ( + pendulum.parse('2022-01-27T12:59:45.596117') - + pendulum.parse('2022-01-27T10:59:45.596117') + ).as_timedelta() ) content = json_serialization_writer.get_serialized_content() content_string = content.decode('utf-8') assert content_string == '{"diff": "2:00:00"}' - + + def test_write_timedelta_value_valid_string(): json_serialization_writer = JsonSerializationWriter() - json_serialization_writer.write_timedelta_value( - "diff", - "2:00:00" - ) + json_serialization_writer.write_timedelta_value("diff", "2:00:00") content = json_serialization_writer.get_serialized_content() content_string = content.decode('utf-8') assert content_string == '{"diff": "2:00:00"}' - + + def test_write_timedelta_value_invalid_string(): with pytest.raises(ValueError) as excinfo: json_serialization_writer = JsonSerializationWriter() - json_serialization_writer.write_timedelta_value( - "diff", - "invalid-timedelta-string" - ) + json_serialization_writer.write_timedelta_value("diff", "invalid-timedelta-string") assert "Invalid timedelta string value found for property diff" in str(excinfo.value) - + def test_write_date_value(): json_serialization_writer = JsonSerializationWriter() @@ -149,19 +148,22 @@ def test_write_date_value(): content_string = content.decode('utf-8') assert content_string == '{"birthday": "2000-09-04"}' + def test_write_date_value_valid_string(): json_serialization_writer = JsonSerializationWriter() json_serialization_writer.write_date_value("birthday", "2000-09-04") content = json_serialization_writer.get_serialized_content() content_string = content.decode('utf-8') assert content_string == '{"birthday": "2000-09-04"}' - + + def test_write_date_value_invalid_string(): with pytest.raises(ValueError) as excinfo: json_serialization_writer = JsonSerializationWriter() json_serialization_writer.write_date_value("birthday", "invalid-date-string") assert "Invalid date string value found for property birthday" in str(excinfo.value) + def test_write_time_value(): json_serialization_writer = JsonSerializationWriter() json_serialization_writer.write_time_value( @@ -172,25 +174,22 @@ def test_write_time_value(): content_string = content.decode('utf-8') assert content_string == '{"time": "12:59:45.596117"}' + def test_write_time_value_valid_string(): json_serialization_writer = JsonSerializationWriter() - json_serialization_writer.write_time_value( - "time", - "12:59:45.596117" - ) + json_serialization_writer.write_time_value("time", "12:59:45.596117") content = json_serialization_writer.get_serialized_content() content_string = content.decode('utf-8') assert content_string == '{"time": "12:59:45.596117"}' - + + def test_write_time_value_invalid_string(): with pytest.raises(ValueError) as excinfo: json_serialization_writer = JsonSerializationWriter() - json_serialization_writer.write_time_value( - "time", - "invalid-time-string" - ) + json_serialization_writer.write_time_value("time", "invalid-time-string") assert "Invalid time string value found for property time" in str(excinfo.value) + def test_write_collection_of_primitive_values(): json_serialization_writer = JsonSerializationWriter() json_serialization_writer.write_collection_of_primitive_values( @@ -206,9 +205,14 @@ def test_write_collection_of_object_values(user_1, user_2): json_serialization_writer.write_collection_of_object_values("users", [user_1, user_2]) content = json_serialization_writer.get_serialized_content() content_string = content.decode('utf-8') - assert content_string == '{"users": [{"id": "8f841f30-e6e3-439a-a812-ebd369559c36", '\ - '"updated_at": "2022-01-27T12:59:45.596117+00:00", "is_active": true}, '\ - '{"display_name": "John Doe", "age": 32}]}' + expected = ( + '{"users": [' + '{"id": "8f841f30-e6e3-439a-a812-ebd369559c36", ' + '"updated_at": "2022-01-27T12:59:45.596117+00:00", "is_active": true}, ' + '{"display_name": "John Doe", "age": 32}' + ']}' + ) + assert content_string == expected def test_write_collection_of_enum_values(): @@ -226,8 +230,13 @@ def test_write_object_value(user_1): json_serialization_writer.write_object_value("user1", user_1) content = json_serialization_writer.get_serialized_content() content_string = content.decode('utf-8') - assert content_string == '{"user1": {"id": "8f841f30-e6e3-439a-a812-ebd369559c36", '\ - '"updated_at": "2022-01-27T12:59:45.596117+00:00", "is_active": true}}' + expected = ( + '{"user1": {' + '"id": "8f841f30-e6e3-439a-a812-ebd369559c36", ' + '"updated_at": "2022-01-27T12:59:45.596117+00:00", "is_active": true' + '}}' + ) + assert content_string == expected def test_write_enum_value(): @@ -254,16 +263,29 @@ def test_write_additional_data_value(user_1, user_2): "businessPhones": ["+1 205 555 0108"], "manager": user_1, "approvers": [user_1, user_2], - "created_at": date(2022, 1, 27) + "created_at": date(2022, 1, 27), + "updated_at": pendulum.DateTime(2024, 9, 24), + "data": { + "groups": [{ + "friends": [user_2] + }] + } } ) content = json_serialization_writer.get_serialized_content() content_string = content.decode('utf-8') - assert content_string == '{"@odata.context": '\ - '"https://graph.microsoft.com/v1.0/$metadata#users/$entity", '\ - '"businessPhones": ["+1 205 555 0108"], '\ - '"manager": {"id": "8f841f30-e6e3-439a-a812-ebd369559c36", '\ - '"updated_at": "2022-01-27T12:59:45.596117+00:00", "is_active": true}, '\ - '"approvers": [{"id": "8f841f30-e6e3-439a-a812-ebd369559c36", '\ - '"updated_at": "2022-01-27T12:59:45.596117+00:00", "is_active": true}, '\ - '{"display_name": "John Doe", "age": 32}], "created_at": "2022-01-27"}' + expected = ( + '{"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users/$entity", ' + '"businessPhones": ["+1 205 555 0108"], ' + '"manager": {"id": "8f841f30-e6e3-439a-a812-ebd369559c36", ' + '"updated_at": "2022-01-27T12:59:45.596117+00:00", "is_active": true}, ' + '"approvers": [' + '{"id": "8f841f30-e6e3-439a-a812-ebd369559c36", ' + '"updated_at": "2022-01-27T12:59:45.596117+00:00", "is_active": true}, ' + '{"display_name": "John Doe", "age": 32}' + '], ' + '"created_at": "2022-01-27", ' + '"updated_at": "2024-09-24T00:00:00", ' + '"data": {"groups": [{"friends": [{"display_name": "John Doe", "age": 32}]}]}}' + ) + assert content_string == expected