adds support for datetime serialization

This commit is contained in:
Mose Müller 2024-07-09 15:41:30 +02:00
parent 8afee54c51
commit cf0780b2ca
5 changed files with 58 additions and 5 deletions

View File

@ -1,16 +1,22 @@
import enum import enum
import logging import logging
from datetime import datetime
from typing import TYPE_CHECKING, Any, NoReturn, cast from typing import TYPE_CHECKING, Any, NoReturn, cast
import pydase import pydase
import pydase.components import pydase.components
import pydase.units as u import pydase.units as u
from pydase.utils.helpers import get_component_classes 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: if TYPE_CHECKING:
from collections.abc import Callable from collections.abc import Callable
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -33,6 +39,7 @@ class Deserializer:
"dict": cls.deserialize_dict, "dict": cls.deserialize_dict,
"method": cls.deserialize_method, "method": cls.deserialize_method,
"Exception": cls.deserialize_exception, "Exception": cls.deserialize_exception,
"datetime": cls.deserialize_datetime,
} }
# First go through handled types (as ColouredEnum is also within the components) # 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: def deserialize_quantity(cls, serialized_object: SerializedObject) -> Any:
return u.convert_to_quantity(serialized_object["value"]) # type: ignore 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 @classmethod
def deserialize_enum( def deserialize_enum(
cls, cls,
@ -88,11 +99,11 @@ class Deserializer:
return return
@classmethod @classmethod
def deserialize_exception(cls, serialized_object: SerializedObject) -> NoReturn: def deserialize_exception(cls, serialized_object: SerializedException) -> NoReturn:
import builtins import builtins
try: try:
exception = getattr(builtins, serialized_object["name"]) # type: ignore exception = getattr(builtins, serialized_object["name"])
except AttributeError: except AttributeError:
exception = type(serialized_object["name"], (Exception,), {}) # type: ignore exception = type(serialized_object["name"], (Exception,), {}) # type: ignore
raise exception(serialized_object["value"]) raise exception(serialized_object["value"])

View File

@ -3,6 +3,7 @@ from __future__ import annotations
import inspect import inspect
import logging import logging
import sys import sys
from datetime import datetime
from enum import Enum from enum import Enum
from typing import TYPE_CHECKING, Any, Literal, cast from typing import TYPE_CHECKING, Any, Literal, cast
@ -21,6 +22,7 @@ from pydase.utils.serialization.types import (
DataServiceTypes, DataServiceTypes,
SerializedBool, SerializedBool,
SerializedDataService, SerializedDataService,
SerializedDatetime,
SerializedDict, SerializedDict,
SerializedEnum, SerializedEnum,
SerializedException, SerializedException,
@ -61,6 +63,9 @@ class Serializer:
if isinstance(obj, Exception): if isinstance(obj, Exception):
result = cls._serialize_exception(obj) result = cls._serialize_exception(obj)
elif isinstance(obj, datetime):
result = cls._serialize_datetime(obj, access_path=access_path)
elif isinstance(obj, AbstractDataService): elif isinstance(obj, AbstractDataService):
result = cls._serialize_data_service(obj, access_path=access_path) result = cls._serialize_data_service(obj, access_path=access_path)
@ -113,6 +118,16 @@ class Serializer:
"value": obj, "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 @classmethod
def _serialize_exception(cls, obj: Exception) -> SerializedException: def _serialize_exception(cls, obj: Exception) -> SerializedException:
return { return {

View File

@ -45,6 +45,11 @@ class SerializedString(SerializedObjectBase):
type: Literal["str"] type: Literal["str"]
class SerializedDatetime(SerializedObjectBase):
type: Literal["datetime"]
value: str
class SerializedEnum(SerializedObjectBase): class SerializedEnum(SerializedObjectBase):
name: str name: str
value: str value: str
@ -107,6 +112,7 @@ SerializedObject = (
| SerializedFloat | SerializedFloat
| SerializedInteger | SerializedInteger
| SerializedString | SerializedString
| SerializedDatetime
| SerializedList | SerializedList
| SerializedDict | SerializedDict
| SerializedNoneType | SerializedNoneType

View File

@ -1,4 +1,5 @@
import enum import enum
from datetime import datetime
from typing import Any from typing import Any
import pydase.components import pydase.components
@ -137,6 +138,16 @@ def test_loads_primitive_types(obj: Any, obj_serialization: SerializedObject) ->
"name": "MyService", "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: def test_loads_advanced_types(obj: Any, obj_serialization: SerializedObject) -> None:

View File

@ -1,5 +1,6 @@
import asyncio import asyncio
import enum import enum
from datetime import datetime
from enum import Enum from enum import Enum
from typing import Any, ClassVar from typing import Any, ClassVar
@ -92,6 +93,16 @@ service_instance = ServiceClass()
"doc": None, "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: 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: def name(self, value: str) -> None:
self._name = value self._name = value
class DerivedService(BaseService): class DerivedService(BaseService): ...
...
base_service_serialization = dump(BaseService()) base_service_serialization = dump(BaseService())
derived_service_serialization = dump(DerivedService()) derived_service_serialization = dump(DerivedService())