~ntt-pf-lab/nova/monkey_patch_notification

« back to all changes in this revision

Viewing changes to vendor/carrot/serialization.py

  • Committer: Jesse Andrews
  • Date: 2010-05-28 06:05:26 UTC
  • Revision ID: git-v1:bf6e6e718cdc7488e2da87b21e258ccc065fe499
initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
"""
 
2
Centralized support for encoding/decoding of data structures.
 
3
Requires a json library (`cjson`_, `simplejson`_, or `Python 2.6+`_).
 
4
 
 
5
Optionally installs support for ``YAML`` if the necessary
 
6
PyYAML is installed.
 
7
 
 
8
.. _`cjson`: http://pypi.python.org/pypi/python-cjson/
 
9
.. _`simplejson`: http://code.google.com/p/simplejson/
 
10
.. _`Python 2.6+`: http://docs.python.org/library/json.html
 
11
.. _`PyYAML`: http://pyyaml.org/
 
12
 
 
13
"""
 
14
 
 
15
import codecs
 
16
 
 
17
__all__ = ['SerializerNotInstalled', 'registry']
 
18
 
 
19
 
 
20
class SerializerNotInstalled(StandardError):
 
21
    """Support for the requested serialization type is not installed"""
 
22
 
 
23
 
 
24
class SerializerRegistry(object):
 
25
    """The registry keeps track of serialization methods."""
 
26
 
 
27
    def __init__(self):
 
28
        self._encoders = {}
 
29
        self._decoders = {}
 
30
        self._default_encode = None
 
31
        self._default_content_type = None
 
32
        self._default_content_encoding = None
 
33
 
 
34
    def register(self, name, encoder, decoder, content_type,
 
35
                 content_encoding='utf-8'):
 
36
        """Register a new encoder/decoder.
 
37
 
 
38
        :param name: A convenience name for the serialization method.
 
39
 
 
40
        :param encoder: A method that will be passed a python data structure
 
41
            and should return a string representing the serialized data.
 
42
            If ``None``, then only a decoder will be registered. Encoding
 
43
            will not be possible.
 
44
 
 
45
        :param decoder: A method that will be passed a string representing
 
46
            serialized data and should return a python data structure.
 
47
            If ``None``, then only an encoder will be registered.
 
48
            Decoding will not be possible.
 
49
 
 
50
        :param content_type: The mime-type describing the serialized
 
51
            structure.
 
52
 
 
53
        :param content_encoding: The content encoding (character set) that
 
54
            the :param:`decoder` method will be returning. Will usually be
 
55
            ``utf-8``, ``us-ascii``, or ``binary``.
 
56
 
 
57
        """
 
58
        if encoder:
 
59
            self._encoders[name] = (content_type, content_encoding, encoder)
 
60
        if decoder:
 
61
            self._decoders[content_type] = decoder
 
62
 
 
63
    def _set_default_serializer(self, name):
 
64
        """
 
65
        Set the default serialization method used by this library.
 
66
 
 
67
        :param name: The name of the registered serialization method.
 
68
            For example, ``json`` (default), ``pickle``, ``yaml``,
 
69
            or any custom methods registered using :meth:`register`.
 
70
 
 
71
        :raises SerializerNotInstalled: If the serialization method
 
72
            requested is not available.
 
73
        """
 
74
        try:
 
75
            (self._default_content_type, self._default_content_encoding,
 
76
             self._default_encode) = self._encoders[name]
 
77
        except KeyError:
 
78
            raise SerializerNotInstalled(
 
79
                "No encoder installed for %s" % name)
 
80
 
 
81
    def encode(self, data, serializer=None):
 
82
        """
 
83
        Serialize a data structure into a string suitable for sending
 
84
        as an AMQP message body.
 
85
 
 
86
        :param data: The message data to send. Can be a list,
 
87
            dictionary or a string.
 
88
 
 
89
        :keyword serializer: An optional string representing
 
90
            the serialization method you want the data marshalled
 
91
            into. (For example, ``json``, ``raw``, or ``pickle``).
 
92
 
 
93
            If ``None`` (default), then `JSON`_ will be used, unless
 
94
            ``data`` is a ``str`` or ``unicode`` object. In this
 
95
            latter case, no serialization occurs as it would be
 
96
            unnecessary.
 
97
 
 
98
            Note that if ``serializer`` is specified, then that
 
99
            serialization method will be used even if a ``str``
 
100
            or ``unicode`` object is passed in.
 
101
 
 
102
        :returns: A three-item tuple containing the content type
 
103
            (e.g., ``application/json``), content encoding, (e.g.,
 
104
            ``utf-8``) and a string containing the serialized
 
105
            data.
 
106
 
 
107
        :raises SerializerNotInstalled: If the serialization method
 
108
              requested is not available.
 
109
        """
 
110
        if serializer == "raw":
 
111
            return raw_encode(data)
 
112
        if serializer and not self._encoders.get(serializer):
 
113
            raise SerializerNotInstalled(
 
114
                        "No encoder installed for %s" % serializer)
 
115
 
 
116
        # If a raw string was sent, assume binary encoding
 
117
        # (it's likely either ASCII or a raw binary file, but 'binary'
 
118
        # charset will encompass both, even if not ideal.
 
119
        if not serializer and isinstance(data, str):
 
120
            # In Python 3+, this would be "bytes"; allow binary data to be
 
121
            # sent as a message without getting encoder errors
 
122
            return "application/data", "binary", data
 
123
 
 
124
        # For unicode objects, force it into a string
 
125
        if not serializer and isinstance(data, unicode):
 
126
            payload = data.encode("utf-8")
 
127
            return "text/plain", "utf-8", payload
 
128
 
 
129
        if serializer:
 
130
            content_type, content_encoding, encoder = \
 
131
                    self._encoders[serializer]
 
132
        else:
 
133
            encoder = self._default_encode
 
134
            content_type = self._default_content_type
 
135
            content_encoding = self._default_content_encoding
 
136
 
 
137
        payload = encoder(data)
 
138
        return content_type, content_encoding, payload
 
139
 
 
140
    def decode(self, data, content_type, content_encoding):
 
141
        """Deserialize a data stream as serialized using ``encode``
 
142
        based on :param:`content_type`.
 
143
 
 
144
        :param data: The message data to deserialize.
 
145
 
 
146
        :param content_type: The content-type of the data.
 
147
            (e.g., ``application/json``).
 
148
 
 
149
        :param content_encoding: The content-encoding of the data.
 
150
            (e.g., ``utf-8``, ``binary``, or ``us-ascii``).
 
151
 
 
152
        :returns: The unserialized data.
 
153
        """
 
154
        content_type = content_type or 'application/data'
 
155
        content_encoding = (content_encoding or 'utf-8').lower()
 
156
 
 
157
        # Don't decode 8-bit strings or unicode objects
 
158
        if content_encoding not in ('binary', 'ascii-8bit') and \
 
159
                not isinstance(data, unicode):
 
160
            data = codecs.decode(data, content_encoding)
 
161
 
 
162
        try:
 
163
            decoder = self._decoders[content_type]
 
164
        except KeyError:
 
165
            return data
 
166
 
 
167
        return decoder(data)
 
168
 
 
169
 
 
170
"""
 
171
.. data:: registry
 
172
 
 
173
Global registry of serializers/deserializers.
 
174
 
 
175
"""
 
176
registry = SerializerRegistry()
 
177
 
 
178
"""
 
179
.. function:: encode(data, serializer=default_serializer)
 
180
 
 
181
Encode data using the registry's default encoder.
 
182
 
 
183
"""
 
184
encode = registry.encode
 
185
 
 
186
"""
 
187
.. function:: decode(data, content_type, content_encoding):
 
188
 
 
189
Decode data using the registry's default decoder.
 
190
 
 
191
"""
 
192
decode = registry.decode
 
193
 
 
194
 
 
195
def raw_encode(data):
 
196
    """Special case serializer."""
 
197
    content_type = 'application/data'
 
198
    payload = data
 
199
    if isinstance(payload, unicode):
 
200
        content_encoding = 'utf-8'
 
201
        payload = payload.encode(content_encoding)
 
202
    else:
 
203
        content_encoding = 'binary'
 
204
    return content_type, content_encoding, payload
 
205
 
 
206
 
 
207
def register_json():
 
208
    """Register a encoder/decoder for JSON serialization."""
 
209
    from anyjson import serialize as json_serialize
 
210
    from anyjson import deserialize as json_deserialize
 
211
 
 
212
    registry.register('json', json_serialize, json_deserialize,
 
213
                      content_type='application/json',
 
214
                      content_encoding='utf-8')
 
215
 
 
216
 
 
217
def register_yaml():
 
218
    """Register a encoder/decoder for YAML serialization.
 
219
 
 
220
    It is slower than JSON, but allows for more data types
 
221
    to be serialized. Useful if you need to send data such as dates"""
 
222
    try:
 
223
        import yaml
 
224
        registry.register('yaml', yaml.safe_dump, yaml.safe_load,
 
225
                          content_type='application/x-yaml',
 
226
                          content_encoding='utf-8')
 
227
    except ImportError:
 
228
 
 
229
        def not_available(*args, **kwargs):
 
230
            """In case a client receives a yaml message, but yaml
 
231
            isn't installed."""
 
232
            raise SerializerNotInstalled(
 
233
                "No decoder installed for YAML. Install the PyYAML library")
 
234
        registry.register('yaml', None, not_available, 'application/x-yaml')
 
235
 
 
236
 
 
237
def register_pickle():
 
238
    """The fastest serialization method, but restricts
 
239
    you to python clients."""
 
240
    import cPickle
 
241
    registry.register('pickle', cPickle.dumps, cPickle.loads,
 
242
                      content_type='application/x-python-serialize',
 
243
                      content_encoding='binary')
 
244
 
 
245
 
 
246
# Register the base serialization methods.
 
247
register_json()
 
248
register_pickle()
 
249
register_yaml()
 
250
 
 
251
# JSON is assumed to always be available, so is the default.
 
252
# (this matches the historical use of carrot.)
 
253
registry._set_default_serializer('json')