From 620e2b62f51c981649a10b6cfc5cc917e961d33d Mon Sep 17 00:00:00 2001 From: Ask Solem Date: Mon, 16 Dec 2013 16:53:54 +0000 Subject: [PATCH] Fixes for #290 --- Changelog | 8 ++++++- kombu/exceptions.py | 14 ++++++++---- kombu/serialization.py | 37 ++++++++++++++++++++++--------- kombu/tests/test_serialization.py | 11 ++++++++- kombu/utils/__init__.py | 17 -------------- 5 files changed, 54 insertions(+), 33 deletions(-) diff --git a/Changelog b/Changelog index 7253f95a..4ffecb8b 100644 --- a/Changelog +++ b/Changelog @@ -8,9 +8,15 @@ 3.0.8 ===== -:release-date: 2013-12-16 16:00 P.M UTC +:release-date: 2013-12-16 17:00 P.M UTC :release-by: Ask Solem +- Serializer: loads and dumps now wraps exceptions raised into + :exc:`~kombu.exceptions.DecodeError` and + :exc:`kombu.exceptions.EncodeError` respectively. + + Contributed by Ionel Cristian Maries + - Redis: Would attempt to read from the wrong connection if a select/epoll/kqueue exception event happened. diff --git a/kombu/exceptions.py b/kombu/exceptions.py index 4365a889..feb7e39d 100644 --- a/kombu/exceptions.py +++ b/kombu/exceptions.py @@ -24,13 +24,19 @@ class KombuError(Exception): """Common subclass for all Kombu exceptions.""" pass + class SerializationError(KombuError): - """Failed to encode a message.""" + """Failed to serialize/deserialize content.""" + + +class EncodeError(SerializationError): + """Cannot encode object.""" pass -class DeserializationError(KombuError): - """Failed to decode a message.""" - pass + +class DecodeError(SerializationError): + """Cannot decode object.""" + class NotBoundError(KombuError): """Trying to call channel dependent method on unbound entity.""" diff --git a/kombu/serialization.py b/kombu/serialization.py index f39230f6..5f71c023 100644 --- a/kombu/serialization.py +++ b/kombu/serialization.py @@ -18,10 +18,13 @@ except ImportError: # pragma: no cover cpickle = None # noqa from collections import namedtuple +from contextlib import contextmanager -from .exceptions import SerializerNotInstalled, ContentDisallowed, SerializationError, DeserializationError -from .five import BytesIO, text_t -from .utils import entrypoints, wrap_exceptions +from .exceptions import ( + ContentDisallowed, DecodeError, EncodeError, SerializerNotInstalled +) +from .five import BytesIO, reraise, text_t +from .utils import entrypoints from .utils.encoding import str_to_bytes, bytes_t __all__ = ['pickle', 'loads', 'dumps', 'register', 'unregister'] @@ -44,6 +47,17 @@ pickle_protocol = int(os.environ.get('PICKLE_PROTOCOL', 2)) codec = namedtuple('codec', ('content_type', 'content_encoding', 'encoder')) +@contextmanager +def _reraise_errors(wrapper, + include=(Exception, ), exclude=(SerializerNotInstalled, )): + try: + yield + except exclude: + raise + except include as exc: + reraise(wrapper, wrapper(exc), sys.exc_info()[2]) + + def pickle_loads(s, load=pickle_load): # used to support buffer objects return load(BytesIO(s)) @@ -116,7 +130,6 @@ class SerializerRegistry(object): raise SerializerNotInstalled( 'No encoder installed for {0}'.format(name)) - @wrap_exceptions(SerializationError) def dumps(self, data, serializer=None): if serializer == 'raw': return raw_encode(data) @@ -134,7 +147,8 @@ class SerializerRegistry(object): # For Unicode objects, force it into a string if not serializer and isinstance(data, text_t): - payload = data.encode('utf-8') + with _reraise_errors(EncodeError, exclude=()): + payload = data.encode('utf-8') return 'text/plain', 'utf-8', payload if serializer: @@ -145,11 +159,11 @@ class SerializerRegistry(object): content_type = self._default_content_type content_encoding = self._default_content_encoding - payload = encoder(data) + with _reraise_errors(EncodeError): + payload = encoder(data) return content_type, content_encoding, payload encode = dumps # XXX compat - @wrap_exceptions(DeserializationError) def loads(self, data, content_type, content_encoding, accept=None, force=False): if accept is not None: @@ -164,10 +178,12 @@ class SerializerRegistry(object): if data: decode = self._decoders.get(content_type) if decode: - return decode(data) + with _reraise_errors(DecodeError): + return decode(data) if content_encoding not in SKIP_DECODE and \ not isinstance(data, text_t): - return _decode(data, content_encoding) + with _reraise_errors(DecodeError): + return _decode(data, content_encoding) return data decode = loads # XXX compat @@ -280,7 +296,8 @@ def raw_encode(data): payload = data if isinstance(payload, text_t): content_encoding = 'utf-8' - payload = payload.encode(content_encoding) + with _reraise_errors(EncodeError, exclude=()): + payload = payload.encode(content_encoding) else: content_encoding = 'binary' return content_type, content_encoding, payload diff --git a/kombu/tests/test_serialization.py b/kombu/tests/test_serialization.py index 8ce1a425..8c84d6d8 100644 --- a/kombu/tests/test_serialization.py +++ b/kombu/tests/test_serialization.py @@ -7,7 +7,7 @@ import sys from base64 import b64decode -from kombu.exceptions import ContentDisallowed +from kombu.exceptions import ContentDisallowed, EncodeError, DecodeError from kombu.five import text_t, bytes_t from kombu.serialization import ( registry, register, SerializerNotInstalled, @@ -186,6 +186,15 @@ class test_Serialization(Case): call('pickle'), call('yaml'), call('doomsday') ]) + def test_reraises_EncodeError(self): + with self.assertRaises(EncodeError): + dumps([object()], serializer='json') + + def test_reraises_DecodeError(self): + with self.assertRaises(DecodeError): + loads(object(), content_type='application/json', + content_encoding='utf-8') + def test_json_loads(self): self.assertEqual( py_data, diff --git a/kombu/utils/__init__.py b/kombu/utils/__init__.py index 026a3b2a..32cb18b8 100644 --- a/kombu/utils/__init__.py +++ b/kombu/utils/__init__.py @@ -429,20 +429,3 @@ def maybe_fileno(f): return fileno(f) except FILENO_ERRORS: pass - -def wrap_exceptions(exception, catch=Exception): - """ - Catch the exception specified by ``catch`` and raise ``exception`` instead with - the old exception as the value. - """ - - def decorator(func): - @wraps(func) - def wrap_exceptions_wrapper(*args, **kwargs): - try: - return func(*args, **kwargs) - except catch as exc: - raise exception(exc) - return wrap_exceptions_wrapper - - return decorator