Coverage for src / dataknobs_common / serialization.py: 36%
42 statements
« prev ^ index » next coverage.py v7.12.0, created at 2025-12-08 17:37 -0700
« prev ^ index » next coverage.py v7.12.0, created at 2025-12-08 17:37 -0700
1"""Serialization protocols and utilities for dataknobs packages.
3This module provides standard interfaces for objects that can be serialized
4to and from dictionaries. This enables consistent serialization patterns
5across all dataknobs packages.
7The serialization framework supports:
8- Type-safe protocols for serializable objects
9- Utility functions for serialization/deserialization
10- Runtime type checking with isinstance()
11- Integration with dataclasses and custom classes
13Example:
14 ```python
15 from dataknobs_common.serialization import Serializable
16 from dataclasses import dataclass
18 @dataclass
19 class User:
20 name: str
21 email: str
23 def to_dict(self) -> dict:
24 return {"name": self.name, "email": self.email}
26 @classmethod
27 def from_dict(cls, data: dict) -> "User":
28 return cls(name=data["name"], email=data["email"])
30 # Type checking works
31 user = User("Alice", "alice@example.com")
32 assert isinstance(user, Serializable) # True
34 # Use utilities
35 from dataknobs_common.serialization import serialize, deserialize
37 data = serialize(user)
38 restored = deserialize(User, data)
39 ```
40"""
42from typing import Any, Dict, Protocol, Type, TypeVar, runtime_checkable
44from dataknobs_common.exceptions import SerializationError
46T = TypeVar("T")
49@runtime_checkable
50class Serializable(Protocol):
51 """Protocol for objects that can be serialized to/from dict.
53 Implement this protocol by providing to_dict() and from_dict() methods.
54 The @runtime_checkable decorator allows isinstance() checks at runtime.
56 Methods:
57 to_dict: Convert object to dictionary representation
58 from_dict: Create object from dictionary representation
60 Example:
61 ```python
62 class MyClass:
63 def __init__(self, value: str):
64 self.value = value
66 def to_dict(self) -> dict:
67 return {"value": self.value}
69 @classmethod
70 def from_dict(cls, data: dict) -> "MyClass":
71 return cls(data["value"])
73 obj = MyClass("test")
74 isinstance(obj, Serializable)
75 # True
76 ```
77 """
79 def to_dict(self) -> Dict[str, Any]:
80 """Convert object to dictionary representation.
82 Returns:
83 Dictionary with serialized data
85 Raises:
86 SerializationError: If serialization fails
87 """
88 ...
90 @classmethod
91 def from_dict(cls: Type[T], data: Dict[str, Any]) -> T:
92 """Create object from dictionary representation.
94 Args:
95 data: Dictionary with serialized data
97 Returns:
98 Deserialized object instance
100 Raises:
101 SerializationError: If deserialization fails
102 """
103 ...
106def serialize(obj: Any) -> Dict[str, Any]:
107 """Serialize an object to dictionary.
109 Convenience function that calls to_dict() with error handling.
111 Args:
112 obj: Object to serialize (must have to_dict method)
114 Returns:
115 Serialized dictionary
117 Raises:
118 SerializationError: If object doesn't support serialization or serialization fails
120 Example:
121 ```python
122 class Point:
123 def __init__(self, x: int, y: int):
124 self.x, self.y = x, y
125 def to_dict(self):
126 return {"x": self.x, "y": self.y}
128 point = Point(10, 20)
129 data = serialize(point)
130 # {'x': 10, 'y': 20}
131 ```
132 """
133 if not hasattr(obj, "to_dict"):
134 raise SerializationError(
135 f"Object of type {type(obj).__name__} is not serializable (missing to_dict method)",
136 context={"type": type(obj).__name__, "object": str(obj)},
137 )
139 try:
140 result = obj.to_dict()
141 if not isinstance(result, dict):
142 raise SerializationError(
143 f"to_dict() must return a dict, got {type(result).__name__}",
144 context={"type": type(obj).__name__, "result_type": type(result).__name__},
145 )
146 return result
147 except Exception as e:
148 if isinstance(e, SerializationError):
149 raise
150 raise SerializationError(
151 f"Failed to serialize {type(obj).__name__}: {e}",
152 context={"type": type(obj).__name__, "error": str(e)},
153 ) from e
156def deserialize(cls: Type[T], data: Dict[str, Any]) -> T:
157 """Deserialize dictionary into an object.
159 Convenience function that calls from_dict() with error handling.
161 Args:
162 cls: Class to deserialize into (must have from_dict classmethod)
163 data: Dictionary with serialized data
165 Returns:
166 Deserialized object instance
168 Raises:
169 SerializationError: If class doesn't support deserialization or deserialization fails
171 Example:
172 ```python
173 class Point:
174 def __init__(self, x: int, y: int):
175 self.x, self.y = x, y
176 @classmethod
177 def from_dict(cls, data: dict):
178 return cls(data["x"], data["y"])
180 data = {"x": 10, "y": 20}
181 point = deserialize(Point, data)
182 # point.x, point.y
183 # (10, 20)
184 ```
185 """
186 if not hasattr(cls, "from_dict"):
187 raise SerializationError(
188 f"Class {cls.__name__} is not deserializable (missing from_dict classmethod)",
189 context={"class": cls.__name__},
190 )
192 if not isinstance(data, dict):
193 raise SerializationError(
194 f"Data must be a dict, got {type(data).__name__}",
195 context={"class": cls.__name__, "data_type": type(data).__name__},
196 )
198 try:
199 return cls.from_dict(data)
200 except Exception as e:
201 if isinstance(e, SerializationError):
202 raise
203 raise SerializationError(
204 f"Failed to deserialize {cls.__name__}: {e}",
205 context={"class": cls.__name__, "error": str(e), "data": data},
206 ) from e
209def serialize_list(items: list[Any]) -> list[Dict[str, Any]]:
210 """Serialize a list of objects to list of dictionaries.
212 Args:
213 items: List of serializable objects
215 Returns:
216 List of serialized dictionaries
218 Raises:
219 SerializationError: If any item cannot be serialized
221 Example:
222 ```python
223 items = [Point(1, 2), Point(3, 4)]
224 data_list = serialize_list(items)
225 len(data_list)
226 # 2
227 ```
228 """
229 return [serialize(item) for item in items]
232def deserialize_list(cls: Type[T], data_list: list[Dict[str, Any]]) -> list[T]:
233 """Deserialize a list of dictionaries into objects.
235 Args:
236 cls: Class to deserialize into
237 data_list: List of serialized dictionaries
239 Returns:
240 List of deserialized objects
242 Raises:
243 SerializationError: If any item cannot be deserialized
245 Example:
246 ```python
247 data_list = [{"x": 1, "y": 2}, {"x": 3, "y": 4}]
248 points = deserialize_list(Point, data_list)
249 len(points)
250 # 2
251 ```
252 """
253 return [deserialize(cls, data) for data in data_list]
256def is_serializable(obj: Any) -> bool:
257 """Check if an object is serializable.
259 Args:
260 obj: Object to check
262 Returns:
263 True if object has to_dict method
265 Example:
266 ```python
267 class Point:
268 def to_dict(self): return {}
270 is_serializable(Point())
271 # True
272 is_serializable("string")
273 # False
274 ```
275 """
276 return isinstance(obj, Serializable) or hasattr(obj, "to_dict")
279def is_deserializable(cls: Type) -> bool:
280 """Check if a class is deserializable.
282 Args:
283 cls: Class to check
285 Returns:
286 True if class has from_dict classmethod
288 Example:
289 ```python
290 class Point:
291 @classmethod
292 def from_dict(cls, data): return cls()
294 is_deserializable(Point)
295 # True
296 is_deserializable(str)
297 # False
298 ```
299 """
300 return hasattr(cls, "from_dict")
303__all__ = [
304 "Serializable",
305 "serialize",
306 "deserialize",
307 "serialize_list",
308 "deserialize_list",
309 "is_serializable",
310 "is_deserializable",
311]