diff --git a/src/pydase/utils/serialization/deserializer.py b/src/pydase/utils/serialization/deserializer.py index 7e255c4..17a9807 100644 --- a/src/pydase/utils/serialization/deserializer.py +++ b/src/pydase/utils/serialization/deserializer.py @@ -1,16 +1,22 @@ import enum import logging +from datetime import datetime from typing import TYPE_CHECKING, Any, NoReturn, cast import pydase import pydase.components import pydase.units as u from pydase.utils.helpers import get_component_classes -from pydase.utils.serialization.types import SerializedObject +from pydase.utils.serialization.types import ( + SerializedDatetime, + SerializedException, + SerializedObject, +) if TYPE_CHECKING: from collections.abc import Callable + logger = logging.getLogger(__name__) @@ -33,6 +39,7 @@ class Deserializer: "dict": cls.deserialize_dict, "method": cls.deserialize_method, "Exception": cls.deserialize_exception, + "datetime": cls.deserialize_datetime, } # First go through handled types (as ColouredEnum is also within the components) @@ -57,6 +64,10 @@ class Deserializer: def deserialize_quantity(cls, serialized_object: SerializedObject) -> Any: return u.convert_to_quantity(serialized_object["value"]) # type: ignore + @classmethod + def deserialize_datetime(cls, serialized_object: SerializedDatetime) -> datetime: + return datetime.fromisoformat(serialized_object["value"]) + @classmethod def deserialize_enum( cls, @@ -88,11 +99,11 @@ class Deserializer: return @classmethod - def deserialize_exception(cls, serialized_object: SerializedObject) -> NoReturn: + def deserialize_exception(cls, serialized_object: SerializedException) -> NoReturn: import builtins try: - exception = getattr(builtins, serialized_object["name"]) # type: ignore + exception = getattr(builtins, serialized_object["name"]) except AttributeError: exception = type(serialized_object["name"], (Exception,), {}) # type: ignore raise exception(serialized_object["value"]) diff --git a/src/pydase/utils/serialization/serializer.py b/src/pydase/utils/serialization/serializer.py index e885c50..261c5ce 100644 --- a/src/pydase/utils/serialization/serializer.py +++ b/src/pydase/utils/serialization/serializer.py @@ -3,6 +3,7 @@ from __future__ import annotations import inspect import logging import sys +from datetime import datetime from enum import Enum from typing import TYPE_CHECKING, Any, Literal, cast @@ -21,6 +22,7 @@ from pydase.utils.serialization.types import ( DataServiceTypes, SerializedBool, SerializedDataService, + SerializedDatetime, SerializedDict, SerializedEnum, SerializedException, @@ -61,6 +63,9 @@ class Serializer: if isinstance(obj, Exception): result = cls._serialize_exception(obj) + elif isinstance(obj, datetime): + result = cls._serialize_datetime(obj, access_path=access_path) + elif isinstance(obj, AbstractDataService): result = cls._serialize_data_service(obj, access_path=access_path) @@ -113,6 +118,16 @@ class Serializer: "value": obj, } + @classmethod + def _serialize_datetime(cls, obj: datetime, access_path: str) -> SerializedDatetime: + return { + "type": "datetime", + "value": str(obj), + "doc": None, + "full_access_path": access_path, + "readonly": True, + } + @classmethod def _serialize_exception(cls, obj: Exception) -> SerializedException: return { diff --git a/src/pydase/utils/serialization/types.py b/src/pydase/utils/serialization/types.py index b03da15..a02a988 100644 --- a/src/pydase/utils/serialization/types.py +++ b/src/pydase/utils/serialization/types.py @@ -45,6 +45,11 @@ class SerializedString(SerializedObjectBase): type: Literal["str"] +class SerializedDatetime(SerializedObjectBase): + type: Literal["datetime"] + value: str + + class SerializedEnum(SerializedObjectBase): name: str value: str @@ -107,6 +112,7 @@ SerializedObject = ( | SerializedFloat | SerializedInteger | SerializedString + | SerializedDatetime | SerializedList | SerializedDict | SerializedNoneType diff --git a/tests/utils/serialization/test_deserializer.py b/tests/utils/serialization/test_deserializer.py index b640494..8b2a3b4 100644 --- a/tests/utils/serialization/test_deserializer.py +++ b/tests/utils/serialization/test_deserializer.py @@ -1,4 +1,5 @@ import enum +from datetime import datetime from typing import Any import pydase.components @@ -137,6 +138,16 @@ def test_loads_primitive_types(obj: Any, obj_serialization: SerializedObject) -> "name": "MyService", }, ), + ( + datetime.fromisoformat("2024-07-09 15:37:08.249845"), + { + "full_access_path": "", + "type": "datetime", + "value": "2024-07-09 15:37:08.249845", + "readonly": True, + "doc": None, + }, + ), ], ) def test_loads_advanced_types(obj: Any, obj_serialization: SerializedObject) -> None: diff --git a/tests/utils/serialization/test_serializer.py b/tests/utils/serialization/test_serializer.py index f8e6d0a..df07cf7 100644 --- a/tests/utils/serialization/test_serializer.py +++ b/tests/utils/serialization/test_serializer.py @@ -1,5 +1,6 @@ import asyncio import enum +from datetime import datetime from enum import Enum from typing import Any, ClassVar @@ -92,6 +93,16 @@ service_instance = ServiceClass() "doc": None, }, ), + ( + datetime.fromisoformat("2024-07-09 15:37:08.249845"), + { + "full_access_path": "", + "type": "datetime", + "value": "2024-07-09 15:37:08.249845", + "readonly": True, + "doc": None, + }, + ), ], ) def test_dump(test_input: Any, expected: dict[str, Any]) -> None: @@ -476,8 +487,7 @@ def test_derived_data_service_serialization() -> None: def name(self, value: str) -> None: self._name = value - class DerivedService(BaseService): - ... + class DerivedService(BaseService): ... base_service_serialization = dump(BaseService()) derived_service_serialization = dump(DerivedService())