Skip to content
This repository has been archived by the owner on Oct 14, 2024. It is now read-only.

fix serialization of pendulum.DateTime objects in JsonSerializationWriter #369

Merged
Merged
Show file tree
Hide file tree
Changes from 6 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
25 changes: 18 additions & 7 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,28 @@

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.2] - 2024-09-10
## [1.3.3] - 2024-10-03

### Added
### Fixed

- 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.
- 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
Expand All @@ -37,62 +40,71 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

### Changed

- Enhanced error handling: Enabled silent failure when an enum key is not available

## [1.1.0] - 2024-02-29

### Added

### Changed

- Support objects and collections when writing additional data.

## [1.0.1] - 2023-12-16

### Added

### Changed

- Bump pendulum to v3.0.0b1 for python 3.12 support.

## [1.0.0] - 2023-10-31

### Added

### Changed

- GA release

## [0.4.2] - 2023-10-11

### Added

### Changed

- Switched from python-dateutil to pendulum for parsing datetime types.

## [0.4.1] - 2023-09-21

### Added

### Changed

- Allow passing of valid strings as values for datetime and UUID fields.

## [0.4.0] - 2023-07-27

### Added

### Changed

- Enabled backing store support

## [0.3.7] - 2023-07-04

### Added

### Changed

- Fixes the key assignment to the writer in write_bytes_value.

## [0.3.6] - 2023-06-27

### Added

### Changed

- Fixed a bug with loading json response in method to get root parse node.

## [0.3.5] - 2023-06-14
Expand Down Expand Up @@ -153,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.

2 changes: 1 addition & 1 deletion kiota_serialization_json/_version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
VERSION: str = '1.3.2'
VERSION: str = '1.3.3'
63 changes: 33 additions & 30 deletions kiota_serialization_json/json_serialization_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@


class JsonSerializationWriter(SerializationWriter):

PROPERTY_SEPARATOR: str = ','

def __init__(self) -> None:
Expand Down Expand Up @@ -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:
Expand All @@ -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)
Expand Down Expand Up @@ -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 = []
Expand Down Expand Up @@ -360,7 +359,7 @@ def __write_dict_value(self, key: Optional[str], value: Dict[str, Any]) -> None:
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():
Expand Down Expand Up @@ -390,35 +389,35 @@ 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

@property
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

Expand All @@ -428,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
Expand All @@ -439,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
Expand All @@ -458,41 +457,45 @@ 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):
if all(isinstance(x, Parsable) for x in value):
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 isinstance(value, dict):
self.__write_dict_value(key, value)
elif hasattr(value, '__dict__'):
self.write_non_parsable_object_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:
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/test_json_parse_node_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Loading
Loading