summaryrefslogtreecommitdiff
path: root/vendor/carrot/serialization.py
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/carrot/serialization.py')
-rw-r--r--vendor/carrot/serialization.py253
1 files changed, 253 insertions, 0 deletions
diff --git a/vendor/carrot/serialization.py b/vendor/carrot/serialization.py
new file mode 100644
index 0000000000..508836fe89
--- /dev/null
+++ b/vendor/carrot/serialization.py
@@ -0,0 +1,253 @@
+"""
+Centralized support for encoding/decoding of data structures.
+Requires a json library (`cjson`_, `simplejson`_, or `Python 2.6+`_).
+
+Optionally installs support for ``YAML`` if the necessary
+PyYAML is installed.
+
+.. _`cjson`: http://pypi.python.org/pypi/python-cjson/
+.. _`simplejson`: http://code.google.com/p/simplejson/
+.. _`Python 2.6+`: http://docs.python.org/library/json.html
+.. _`PyYAML`: http://pyyaml.org/
+
+"""
+
+import codecs
+
+__all__ = ['SerializerNotInstalled', 'registry']
+
+
+class SerializerNotInstalled(StandardError):
+ """Support for the requested serialization type is not installed"""
+
+
+class SerializerRegistry(object):
+ """The registry keeps track of serialization methods."""
+
+ def __init__(self):
+ self._encoders = {}
+ self._decoders = {}
+ self._default_encode = None
+ self._default_content_type = None
+ self._default_content_encoding = None
+
+ def register(self, name, encoder, decoder, content_type,
+ content_encoding='utf-8'):
+ """Register a new encoder/decoder.
+
+ :param name: A convenience name for the serialization method.
+
+ :param encoder: A method that will be passed a python data structure
+ and should return a string representing the serialized data.
+ If ``None``, then only a decoder will be registered. Encoding
+ will not be possible.
+
+ :param decoder: A method that will be passed a string representing
+ serialized data and should return a python data structure.
+ If ``None``, then only an encoder will be registered.
+ Decoding will not be possible.
+
+ :param content_type: The mime-type describing the serialized
+ structure.
+
+ :param content_encoding: The content encoding (character set) that
+ the :param:`decoder` method will be returning. Will usually be
+ ``utf-8``, ``us-ascii``, or ``binary``.
+
+ """
+ if encoder:
+ self._encoders[name] = (content_type, content_encoding, encoder)
+ if decoder:
+ self._decoders[content_type] = decoder
+
+ def _set_default_serializer(self, name):
+ """
+ Set the default serialization method used by this library.
+
+ :param name: The name of the registered serialization method.
+ For example, ``json`` (default), ``pickle``, ``yaml``,
+ or any custom methods registered using :meth:`register`.
+
+ :raises SerializerNotInstalled: If the serialization method
+ requested is not available.
+ """
+ try:
+ (self._default_content_type, self._default_content_encoding,
+ self._default_encode) = self._encoders[name]
+ except KeyError:
+ raise SerializerNotInstalled(
+ "No encoder installed for %s" % name)
+
+ def encode(self, data, serializer=None):
+ """
+ Serialize a data structure into a string suitable for sending
+ as an AMQP message body.
+
+ :param data: The message data to send. Can be a list,
+ dictionary or a string.
+
+ :keyword serializer: An optional string representing
+ the serialization method you want the data marshalled
+ into. (For example, ``json``, ``raw``, or ``pickle``).
+
+ If ``None`` (default), then `JSON`_ will be used, unless
+ ``data`` is a ``str`` or ``unicode`` object. In this
+ latter case, no serialization occurs as it would be
+ unnecessary.
+
+ Note that if ``serializer`` is specified, then that
+ serialization method will be used even if a ``str``
+ or ``unicode`` object is passed in.
+
+ :returns: A three-item tuple containing the content type
+ (e.g., ``application/json``), content encoding, (e.g.,
+ ``utf-8``) and a string containing the serialized
+ data.
+
+ :raises SerializerNotInstalled: If the serialization method
+ requested is not available.
+ """
+ if serializer == "raw":
+ return raw_encode(data)
+ if serializer and not self._encoders.get(serializer):
+ raise SerializerNotInstalled(
+ "No encoder installed for %s" % serializer)
+
+ # If a raw string was sent, assume binary encoding
+ # (it's likely either ASCII or a raw binary file, but 'binary'
+ # charset will encompass both, even if not ideal.
+ if not serializer and isinstance(data, str):
+ # In Python 3+, this would be "bytes"; allow binary data to be
+ # sent as a message without getting encoder errors
+ return "application/data", "binary", data
+
+ # For unicode objects, force it into a string
+ if not serializer and isinstance(data, unicode):
+ payload = data.encode("utf-8")
+ return "text/plain", "utf-8", payload
+
+ if serializer:
+ content_type, content_encoding, encoder = \
+ self._encoders[serializer]
+ else:
+ encoder = self._default_encode
+ content_type = self._default_content_type
+ content_encoding = self._default_content_encoding
+
+ payload = encoder(data)
+ return content_type, content_encoding, payload
+
+ def decode(self, data, content_type, content_encoding):
+ """Deserialize a data stream as serialized using ``encode``
+ based on :param:`content_type`.
+
+ :param data: The message data to deserialize.
+
+ :param content_type: The content-type of the data.
+ (e.g., ``application/json``).
+
+ :param content_encoding: The content-encoding of the data.
+ (e.g., ``utf-8``, ``binary``, or ``us-ascii``).
+
+ :returns: The unserialized data.
+ """
+ content_type = content_type or 'application/data'
+ content_encoding = (content_encoding or 'utf-8').lower()
+
+ # Don't decode 8-bit strings or unicode objects
+ if content_encoding not in ('binary', 'ascii-8bit') and \
+ not isinstance(data, unicode):
+ data = codecs.decode(data, content_encoding)
+
+ try:
+ decoder = self._decoders[content_type]
+ except KeyError:
+ return data
+
+ return decoder(data)
+
+
+"""
+.. data:: registry
+
+Global registry of serializers/deserializers.
+
+"""
+registry = SerializerRegistry()
+
+"""
+.. function:: encode(data, serializer=default_serializer)
+
+Encode data using the registry's default encoder.
+
+"""
+encode = registry.encode
+
+"""
+.. function:: decode(data, content_type, content_encoding):
+
+Decode data using the registry's default decoder.
+
+"""
+decode = registry.decode
+
+
+def raw_encode(data):
+ """Special case serializer."""
+ content_type = 'application/data'
+ payload = data
+ if isinstance(payload, unicode):
+ content_encoding = 'utf-8'
+ payload = payload.encode(content_encoding)
+ else:
+ content_encoding = 'binary'
+ return content_type, content_encoding, payload
+
+
+def register_json():
+ """Register a encoder/decoder for JSON serialization."""
+ from anyjson import serialize as json_serialize
+ from anyjson import deserialize as json_deserialize
+
+ registry.register('json', json_serialize, json_deserialize,
+ content_type='application/json',
+ content_encoding='utf-8')
+
+
+def register_yaml():
+ """Register a encoder/decoder for YAML serialization.
+
+ It is slower than JSON, but allows for more data types
+ to be serialized. Useful if you need to send data such as dates"""
+ try:
+ import yaml
+ registry.register('yaml', yaml.safe_dump, yaml.safe_load,
+ content_type='application/x-yaml',
+ content_encoding='utf-8')
+ except ImportError:
+
+ def not_available(*args, **kwargs):
+ """In case a client receives a yaml message, but yaml
+ isn't installed."""
+ raise SerializerNotInstalled(
+ "No decoder installed for YAML. Install the PyYAML library")
+ registry.register('yaml', None, not_available, 'application/x-yaml')
+
+
+def register_pickle():
+ """The fastest serialization method, but restricts
+ you to python clients."""
+ import cPickle
+ registry.register('pickle', cPickle.dumps, cPickle.loads,
+ content_type='application/x-python-serialize',
+ content_encoding='binary')
+
+
+# Register the base serialization methods.
+register_json()
+register_pickle()
+register_yaml()
+
+# JSON is assumed to always be available, so is the default.
+# (this matches the historical use of carrot.)
+registry._set_default_serializer('json')