~ubuntu-branches/ubuntu/wily/pymongo/wily-proposed

« back to all changes in this revision

Viewing changes to bson/json_util.py

  • Committer: Package Import Robot
  • Author(s): Federico Ceratto
  • Date: 2015-04-26 22:43:13 UTC
  • mfrom: (24.1.5 sid)
  • Revision ID: package-import@ubuntu.com-20150426224313-0hga2jphvf0rrmfe
Tags: 3.0.1-1
New upstream release.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright 2009-2014 MongoDB, Inc.
 
1
# Copyright 2009-2015 MongoDB, Inc.
2
2
#
3
3
# Licensed under the Apache License, Version 2.0 (the "License");
4
4
# you may not use this file except in compliance with the License.
47
47
instances (as they are extended strings you can't provide custom defaults),
48
48
but it will be faster as there is less recursion.
49
49
 
 
50
.. versionchanged:: 2.8
 
51
   The output format for :class:`~bson.timestamp.Timestamp` has changed from
 
52
   '{"t": <int>, "i": <int>}' to '{"$timestamp": {"t": <int>, "i": <int>}}'.
 
53
   This new format will be decoded to an instance of
 
54
   :class:`~bson.timestamp.Timestamp`. The old format will continue to be
 
55
   decoded to a python dict as before. Encoding to the old format is no longer
 
56
   supported as it was never correct and loses type information.
 
57
   Added support for $numberLong and $undefined - new in MongoDB 2.6 - and
 
58
   parsing $date in ISO-8601 format.
 
59
 
50
60
.. versionchanged:: 2.7
51
61
   Preserves order when rendering SON, Timestamp, Code, Binary, and DBRef
52
 
   instances. (But not in Python 2.4.)
 
62
   instances.
53
63
 
54
64
.. versionchanged:: 2.3
55
65
   Added dumps and loads helpers to automatically handle conversion to and
56
66
   from json and supports :class:`~bson.binary.Binary` and
57
67
   :class:`~bson.code.Code`
58
 
 
59
 
.. versionchanged:: 1.9
60
 
   Handle :class:`uuid.UUID` instances, whenever possible.
61
 
 
62
 
.. versionchanged:: 1.8
63
 
   Handle timezone aware datetime instances on encode, decode to
64
 
   timezone aware datetime instances.
65
 
 
66
 
.. versionchanged:: 1.8
67
 
   Added support for encoding/decoding :class:`~bson.max_key.MaxKey`
68
 
   and :class:`~bson.min_key.MinKey`, and for encoding
69
 
   :class:`~bson.timestamp.Timestamp`.
70
 
 
71
 
.. versionchanged:: 1.2
72
 
   Added support for encoding/decoding datetimes and regular expressions.
73
68
"""
74
69
 
75
70
import base64
76
71
import calendar
 
72
import collections
77
73
import datetime
 
74
import json
78
75
import re
79
 
 
80
 
json_lib = True
81
 
try:
82
 
    import json
83
 
except ImportError:
84
 
    try:
85
 
        import simplejson as json
86
 
    except ImportError:
87
 
        json_lib = False
88
 
 
89
 
import bson
 
76
import uuid
 
77
 
90
78
from bson import EPOCH_AWARE, RE_TYPE, SON
91
79
from bson.binary import Binary
92
80
from bson.code import Code
93
81
from bson.dbref import DBRef
 
82
from bson.int64 import Int64
94
83
from bson.max_key import MaxKey
95
84
from bson.min_key import MinKey
96
85
from bson.objectid import ObjectId
97
86
from bson.regex import Regex
98
87
from bson.timestamp import Timestamp
 
88
from bson.tz_util import utc
99
89
 
100
 
from bson.py3compat import PY3, binary_type, string_types
 
90
from bson.py3compat import PY3, iteritems, string_type, text_type
101
91
 
102
92
 
103
93
_RE_OPT_TABLE = {
118
108
 
119
109
    .. versionchanged:: 2.7
120
110
       Preserves order when rendering SON, Timestamp, Code, Binary, and DBRef
121
 
       instances. (But not in Python 2.4.)
 
111
       instances.
122
112
    """
123
 
    if not json_lib:
124
 
        raise Exception("No json library available")
125
113
    return json.dumps(_json_convert(obj), *args, **kwargs)
126
114
 
127
115
 
129
117
    """Helper function that wraps :class:`json.loads`.
130
118
 
131
119
    Automatically passes the object_hook for BSON type conversion.
132
 
 
133
 
    :Parameters:
134
 
      - `compile_re` (optional): if ``False``, don't attempt to compile BSON
135
 
        regular expressions into Python regular expressions. Return instances
136
 
        of :class:`~bson.bsonregex.BSONRegex` instead.
137
 
 
138
 
    .. versionchanged:: 2.7
139
 
       Added ``compile_re`` option.
140
120
    """
141
 
    if not json_lib:
142
 
        raise Exception("No json library available")
143
 
 
144
 
    compile_re = kwargs.pop('compile_re', True)
145
 
    kwargs['object_hook'] = lambda dct: object_hook(dct, compile_re)
 
121
    kwargs['object_hook'] = lambda dct: object_hook(dct)
146
122
    return json.loads(s, *args, **kwargs)
147
123
 
148
124
 
151
127
    converted into json.
152
128
    """
153
129
    if hasattr(obj, 'iteritems') or hasattr(obj, 'items'):  # PY3 support
154
 
        return SON(((k, _json_convert(v)) for k, v in obj.iteritems()))
155
 
    elif hasattr(obj, '__iter__') and not isinstance(obj, string_types):
 
130
        return SON(((k, _json_convert(v)) for k, v in iteritems(obj)))
 
131
    elif hasattr(obj, '__iter__') and not isinstance(obj, (text_type, bytes)):
156
132
        return list((_json_convert(v) for v in obj))
157
133
    try:
158
134
        return default(obj)
160
136
        return obj
161
137
 
162
138
 
163
 
def object_hook(dct, compile_re=True):
 
139
def object_hook(dct):
164
140
    if "$oid" in dct:
165
141
        return ObjectId(str(dct["$oid"]))
166
142
    if "$ref" in dct:
167
143
        return DBRef(dct["$ref"], dct["$id"], dct.get("$db", None))
168
144
    if "$date" in dct:
169
 
        secs = float(dct["$date"]) / 1000.0
 
145
        dtm = dct["$date"]
 
146
        # mongoexport 2.6 and newer
 
147
        if isinstance(dtm, string_type):
 
148
            aware = datetime.datetime.strptime(
 
149
                dtm[:23], "%Y-%m-%dT%H:%M:%S.%f").replace(tzinfo=utc)
 
150
            offset = dtm[23:]
 
151
            if not offset or offset == 'Z':
 
152
                # UTC
 
153
                return aware
 
154
            else:
 
155
                if len(offset) == 5:
 
156
                    # Offset from mongoexport is in format (+|-)HHMM
 
157
                    secs = (int(offset[1:3]) * 3600 + int(offset[3:]) * 60)
 
158
                elif ':' in offset and len(offset) == 6:
 
159
                    # RFC-3339 format (+|-)HH:MM
 
160
                    hours, minutes = offset[1:].split(':')
 
161
                    secs = (int(hours) * 3600 + int(minutes) * 60)
 
162
                else:
 
163
                    # Not RFC-3339 compliant or mongoexport output.
 
164
                    raise ValueError("invalid format for offset")
 
165
                if offset[0] == "-":
 
166
                    secs *= -1
 
167
                return aware - datetime.timedelta(seconds=secs)
 
168
        # mongoexport 2.6 and newer, time before the epoch (SERVER-15275)
 
169
        elif isinstance(dtm, collections.Mapping):
 
170
            secs = float(dtm["$numberLong"]) / 1000.0
 
171
        # mongoexport before 2.6
 
172
        else:
 
173
            secs = float(dtm) / 1000.0
170
174
        return EPOCH_AWARE + datetime.timedelta(seconds=secs)
171
175
    if "$regex" in dct:
172
176
        flags = 0
173
177
        # PyMongo always adds $options but some other tools may not.
174
178
        for opt in dct.get("$options", ""):
175
179
            flags |= _RE_OPT_TABLE.get(opt, 0)
176
 
 
177
 
        if compile_re:
178
 
            return re.compile(dct["$regex"], flags)
179
 
        else:
180
 
            return Regex(dct["$regex"], flags)
 
180
        return Regex(dct["$regex"], flags)
181
181
    if "$minKey" in dct:
182
182
        return MinKey()
183
183
    if "$maxKey" in dct:
191
191
        return Binary(base64.b64decode(dct["$binary"].encode()), subtype)
192
192
    if "$code" in dct:
193
193
        return Code(dct["$code"], dct.get("$scope"))
194
 
    if bson.has_uuid() and "$uuid" in dct:
195
 
        return bson.uuid.UUID(dct["$uuid"])
 
194
    if "$uuid" in dct:
 
195
        return uuid.UUID(dct["$uuid"])
 
196
    if "$undefined" in dct:
 
197
        return None
 
198
    if "$numberLong" in dct:
 
199
        return Int64(dct["$numberLong"])
 
200
    if "$timestamp" in dct:
 
201
        tsp = dct["$timestamp"]
 
202
        return Timestamp(tsp["t"], tsp["i"])
196
203
    return dct
197
204
 
198
205
 
199
206
def default(obj):
200
207
    # We preserve key order when rendering SON, DBRef, etc. as JSON by
201
 
    # returning a SON for those types instead of a dict. This works with
202
 
    # the "json" standard library in Python 2.6+ and with simplejson
203
 
    # 2.1.0+ in Python 2.5+, because those libraries iterate the SON
204
 
    # using PyIter_Next. Python 2.4 must use simplejson 2.0.9 or older,
205
 
    # and those versions of simplejson use the lower-level PyDict_Next,
206
 
    # which bypasses SON's order-preserving iteration, so we lose key
207
 
    # order in Python 2.4.
 
208
    # returning a SON for those types instead of a dict.
208
209
    if isinstance(obj, ObjectId):
209
210
        return {"$oid": str(obj)}
210
211
    if isinstance(obj, DBRef):
230
231
            flags += "u"
231
232
        if obj.flags & re.VERBOSE:
232
233
            flags += "x"
233
 
        if isinstance(obj.pattern, unicode):
 
234
        if isinstance(obj.pattern, text_type):
234
235
            pattern = obj.pattern
235
236
        else:
236
237
            pattern = obj.pattern.decode('utf-8')
240
241
    if isinstance(obj, MaxKey):
241
242
        return {"$maxKey": 1}
242
243
    if isinstance(obj, Timestamp):
243
 
        return SON([("t", obj.time), ("i", obj.inc)])
 
244
        return {"$timestamp": SON([("t", obj.time), ("i", obj.inc)])}
244
245
    if isinstance(obj, Code):
245
246
        return SON([('$code', str(obj)), ('$scope', obj.scope)])
246
247
    if isinstance(obj, Binary):
247
248
        return SON([
248
249
            ('$binary', base64.b64encode(obj).decode()),
249
250
            ('$type', "%02x" % obj.subtype)])
250
 
    if PY3 and isinstance(obj, binary_type):
 
251
    if PY3 and isinstance(obj, bytes):
251
252
        return SON([
252
253
            ('$binary', base64.b64encode(obj).decode()),
253
254
            ('$type', "00")])
254
 
    if bson.has_uuid() and isinstance(obj, bson.uuid.UUID):
 
255
    if isinstance(obj, uuid.UUID):
255
256
        return {"$uuid": obj.hex}
256
257
    raise TypeError("%r is not JSON serializable" % obj)