Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

""" 

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 

    from simplejson import dumps, loads 

 

    registry.register('json', dumps, loads, 

                      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')