Skip to content

Commit 1e2de3d

Browse files
committed
Wrap backend-specific exceptions in SerializationError/DeserializationError
1 parent dd3fcc3 commit 1e2de3d

File tree

11 files changed

+98
-24
lines changed

11 files changed

+98
-24
lines changed

docs/api.rst

+6
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ Serializer API
1515
.. autoclass:: CustomizableSerializer
1616
.. autoclass:: CustomTypeCodec
1717

18+
Exceptions
19+
----------
20+
21+
.. autoexception:: SerializationError
22+
.. autoexception:: DeserializationError
23+
1824
Marshalling callbacks
1925
---------------------
2026

docs/versionhistory.rst

+3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ This library adheres to `Semantic Versioning 2.0 <https://semver.org/>`_.
77

88
- Dropped support for Python 3.7 and 3.8
99
- **BACKWARD INCOMPATIBLE** Bumped minimum Asphalt version to 5.0
10+
- **BACKWARD INCOMPATIBLE** The ``Serializer.serialize()`` and
11+
``Serializer.deserialize()`` methods now raise ``SerializationError`` and
12+
``DeserializationError`` regardless of back-end when something goes wrong
1013

1114
**6.0.0** (2022-06-04)
1215

src/asphalt/serialization/__init__.py

+7-7
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
from typing import Any
2-
31
from ._api import CustomizableSerializer as CustomizableSerializer
42
from ._api import CustomTypeCodec as CustomTypeCodec
53
from ._api import Serializer as Serializer
64
from ._component import SerializationComponent as SerializationComponent
5+
from ._exceptions import DeserializationError as DeserializationError
6+
from ._exceptions import SerializationError as SerializationError
77
from ._marshalling import default_marshaller as default_marshaller
88
from ._marshalling import default_unmarshaller as default_unmarshaller
99
from ._object_codec import DefaultCustomTypeCodec as DefaultCustomTypeCodec
1010

1111
# Re-export imports, so they look like they live directly in this package
12-
key: str
13-
value: Any
14-
for key, value in list(locals().items()):
15-
if getattr(value, "__module__", "").startswith(f"{__name__}."):
16-
value.__module__ = __name__
12+
for __value in list(locals().values()):
13+
if getattr(__value, "__module__", "").startswith(f"{__name__}."):
14+
__value.__module__ = __name__
15+
16+
del __value

src/asphalt/serialization/_api.py

+11-3
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,23 @@ class Serializer(metaclass=ABCMeta):
3838
serialization support for custom types.
3939
"""
4040

41-
__slots__ = ()
41+
__slots__ = ("__weakref__",)
4242

4343
@abstractmethod
4444
def serialize(self, obj: Any) -> bytes:
45-
"""Serialize a Python object into bytes."""
45+
"""
46+
Serialize a Python object into bytes.
47+
48+
:raises SerializationError: if serialization fails
49+
"""
4650

4751
@abstractmethod
4852
def deserialize(self, payload: bytes) -> Any:
49-
"""Deserialize bytes into a Python object."""
53+
"""
54+
Deserialize bytes into a Python object.
55+
56+
:raises DesrializationError: if deserialization fails
57+
"""
5058

5159
@property
5260
@abstractmethod
+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
class SerializationError(Exception):
2+
"""Raised when serialization fails."""
3+
4+
5+
class DeserializationError(Exception):
6+
"""Raised when deserialization fails."""

src/asphalt/serialization/serializers/cbor.py

+9-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import cbor2
66
from asphalt.core import qualified_name, resolve_reference
77

8+
from .. import DeserializationError, SerializationError
89
from .._api import CustomizableSerializer
910
from .._object_codec import DefaultCustomTypeCodec
1011

@@ -129,10 +130,16 @@ def __init__(
129130
self.decoder_options: dict[str, Any] = decoder_options or {}
130131

131132
def serialize(self, obj: Any) -> bytes:
132-
return cbor2.dumps(obj, **self.encoder_options)
133+
try:
134+
return cbor2.dumps(obj, **self.encoder_options)
135+
except cbor2.CBOREncodeError as exc:
136+
raise SerializationError(str(exc)) from exc
133137

134138
def deserialize(self, payload: bytes) -> Any:
135-
return cbor2.loads(payload, **self.decoder_options)
139+
try:
140+
return cbor2.loads(payload, **self.decoder_options)
141+
except cbor2.CBORDecodeError as exc:
142+
raise DeserializationError(str(exc)) from exc
136143

137144
@property
138145
def mimetype(self) -> str:

src/asphalt/serialization/serializers/json.py

+10-3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from asphalt.core import resolve_reference
88

9+
from .. import DeserializationError, SerializationError
910
from .._api import CustomizableSerializer
1011
from .._object_codec import DefaultCustomTypeCodec
1112

@@ -81,11 +82,17 @@ def __init__(
8182
self._decoder = JSONDecoder(**self.decoder_options)
8283

8384
def serialize(self, obj: Any) -> bytes:
84-
return self._encoder.encode(obj).encode(self.encoding)
85+
try:
86+
return self._encoder.encode(obj).encode(self.encoding)
87+
except Exception as exc:
88+
raise SerializationError(str(exc)) from exc
8589

8690
def deserialize(self, payload: bytes) -> Any:
87-
text_payload = payload.decode(self.encoding)
88-
return self._decoder.decode(text_payload)
91+
try:
92+
text_payload = payload.decode(self.encoding)
93+
return self._decoder.decode(text_payload)
94+
except Exception as exc:
95+
raise DeserializationError(str(exc)) from exc
8996

9097
@property
9198
def mimetype(self) -> str:

src/asphalt/serialization/serializers/msgpack.py

+9-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from asphalt.core import resolve_reference
66
from msgpack import ExtType, packb, unpackb
77

8+
from .. import DeserializationError, SerializationError
89
from .._api import CustomizableSerializer
910
from .._object_codec import DefaultCustomTypeCodec
1011

@@ -103,10 +104,16 @@ def __init__(
103104
self.unpacker_options.setdefault("raw", False)
104105

105106
def serialize(self, obj: Any) -> bytes:
106-
return packb(obj, **self.packer_options) # type: ignore[no-any-return]
107+
try:
108+
return packb(obj, **self.packer_options) # type: ignore[no-any-return]
109+
except Exception as exc:
110+
raise SerializationError(str(exc)) from exc
107111

108112
def deserialize(self, payload: bytes) -> Any:
109-
return unpackb(payload, **self.unpacker_options)
113+
try:
114+
return unpackb(payload, **self.unpacker_options)
115+
except Exception as exc:
116+
raise DeserializationError(str(exc)) from exc
110117

111118
@property
112119
def mimetype(self) -> str:

src/asphalt/serialization/serializers/pickle.py

+9-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from typing import Any
55

66
from .._api import Serializer
7+
from .._exceptions import DeserializationError, SerializationError
78

89

910
class PickleSerializer(Serializer):
@@ -26,10 +27,16 @@ def __init__(self, protocol: int = pickle.HIGHEST_PROTOCOL):
2627
self.protocol: int = protocol
2728

2829
def serialize(self, obj: Any) -> bytes:
29-
return pickle.dumps(obj, protocol=self.protocol)
30+
try:
31+
return pickle.dumps(obj, protocol=self.protocol)
32+
except Exception as exc:
33+
raise SerializationError(str(exc)) from exc
3034

3135
def deserialize(self, payload: bytes) -> Any:
32-
return pickle.loads(payload)
36+
try:
37+
return pickle.loads(payload)
38+
except Exception as exc:
39+
raise DeserializationError(str(exc)) from exc
3340

3441
@property
3542
def mimetype(self) -> str:

src/asphalt/serialization/serializers/yaml.py

+11-4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
from ruamel.yaml import YAML
77

8+
from .. import DeserializationError, SerializationError
89
from .._api import Serializer
910

1011

@@ -35,12 +36,18 @@ def __init__(self, safe: bool = True):
3536
self._yaml = YAML(typ="safe" if safe else "unsafe")
3637

3738
def serialize(self, obj: Any) -> bytes:
38-
buffer = StringIO()
39-
self._yaml.dump(obj, buffer)
40-
return buffer.getvalue().encode("utf-8")
39+
try:
40+
buffer = StringIO()
41+
self._yaml.dump(obj, buffer)
42+
return buffer.getvalue().encode("utf-8")
43+
except Exception as exc:
44+
raise SerializationError(str(exc)) from exc
4145

4246
def deserialize(self, payload: bytes) -> Any:
43-
return self._yaml.load(payload)
47+
try:
48+
return self._yaml.load(payload)
49+
except Exception as exc:
50+
raise DeserializationError(str(exc)) from exc
4451

4552
@property
4653
def mimetype(self) -> str:

tests/test_serializers.py

+17-1
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
import re
44
import sys
5+
from binascii import unhexlify
56
from datetime import datetime, timezone
67
from functools import partial
8+
from socket import socket
79
from types import SimpleNamespace
810
from typing import Any, cast
911

@@ -12,7 +14,11 @@
1214
from cbor2 import CBORTag
1315
from msgpack import ExtType
1416

15-
from asphalt.serialization import CustomizableSerializer
17+
from asphalt.serialization import (
18+
CustomizableSerializer,
19+
DeserializationError,
20+
SerializationError,
21+
)
1622
from asphalt.serialization.serializers.cbor import CBORSerializer, CBORTypeCodec
1723
from asphalt.serialization.serializers.json import JSONSerializer
1824
from asphalt.serialization.serializers.msgpack import (
@@ -115,6 +121,16 @@ def test_basic_types_roundtrip(serializer: CustomizableSerializer, input: Any) -
115121
assert deserialized == input
116122

117123

124+
def test_serialization_error(serializer: CustomizableSerializer) -> None:
125+
with pytest.raises(SerializationError), socket() as sock:
126+
serializer.serialize(sock)
127+
128+
129+
def test_deserialization_error(serializer: CustomizableSerializer) -> None:
130+
with pytest.raises(DeserializationError):
131+
serializer.deserialize(unhexlify("c11b9b9b9b0000000000"))
132+
133+
118134
@pytest.mark.parametrize("serializer_type", ["cbor", "pickle", "yaml"])
119135
def test_circular_reference(serializer: CustomizableSerializer) -> None:
120136
a: dict[str, Any] = {"foo": 1}

0 commit comments

Comments
 (0)