1
# Copyright 2009-2014 MongoDB, Inc.
1
# Copyright 2009-2015 MongoDB, Inc.
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.
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.
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.)
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`
59
.. versionchanged:: 1.9
60
Handle :class:`uuid.UUID` instances, whenever possible.
62
.. versionchanged:: 1.8
63
Handle timezone aware datetime instances on encode, decode to
64
timezone aware datetime instances.
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`.
71
.. versionchanged:: 1.2
72
Added support for encoding/decoding datetimes and regular expressions.
85
import simplejson as json
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
100
from bson.py3compat import PY3, binary_type, string_types
90
from bson.py3compat import PY3, iteritems, string_type, text_type
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.)
124
raise Exception("No json library available")
125
113
return json.dumps(_json_convert(obj), *args, **kwargs)
129
117
"""Helper function that wraps :class:`json.loads`.
131
119
Automatically passes the object_hook for BSON type conversion.
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.
138
.. versionchanged:: 2.7
139
Added ``compile_re`` option.
142
raise Exception("No json library available")
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)
151
127
converted into json.
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))
158
134
return default(obj)
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
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)
151
if not offset or offset == 'Z':
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)
163
# Not RFC-3339 compliant or mongoexport output.
164
raise ValueError("invalid format for offset")
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
173
secs = float(dtm) / 1000.0
170
174
return EPOCH_AWARE + datetime.timedelta(seconds=secs)
171
175
if "$regex" in dct:
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)
178
return re.compile(dct["$regex"], flags)
180
return Regex(dct["$regex"], flags)
180
return Regex(dct["$regex"], flags)
181
181
if "$minKey" in dct:
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"])
195
return uuid.UUID(dct["$uuid"])
196
if "$undefined" in dct:
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"])
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):
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):
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):
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)