Skip to content

Commit a36b836

Browse files
committed
Clone needs to handle regular expressions as well PYTHON-421
1 parent 8dd2670 commit a36b836

File tree

3 files changed

+41
-7
lines changed

3 files changed

+41
-7
lines changed

bson/json_util.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@
8383
json_lib = False
8484

8585
import bson
86-
from bson import EPOCH_AWARE
86+
from bson import EPOCH_AWARE, RE_TYPE
8787
from bson.binary import Binary
8888
from bson.code import Code
8989
from bson.dbref import DBRef
@@ -94,9 +94,6 @@
9494

9595
from bson.py3compat import PY3, binary_type, string_types
9696

97-
# TODO share this with bson.py?
98-
_RE_TYPE = type(re.compile("foo"))
99-
10097

10198
def dumps(obj, *args, **kwargs):
10299
"""Helper function that wraps :class:`json.dumps`.
@@ -174,7 +171,7 @@ def default(obj):
174171
millis = int(calendar.timegm(obj.timetuple()) * 1000 +
175172
obj.microsecond / 1000)
176173
return {"$date": millis}
177-
if isinstance(obj, _RE_TYPE):
174+
if isinstance(obj, RE_TYPE):
178175
flags = ""
179176
if obj.flags & re.IGNORECASE:
180177
flags += "i"

pymongo/cursor.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import copy
1717
from collections import deque
1818

19+
from bson import RE_TYPE
1920
from bson.code import Code
2021
from bson.son import SON
2122
from pymongo import helpers, message, read_preferences
@@ -184,7 +185,7 @@ def __clone(self, deepcopy=True):
184185
data = dict((k, v) for k, v in self.__dict__.iteritems()
185186
if k.startswith('_Cursor__') and k[9:] in values_to_clone)
186187
if deepcopy:
187-
data = copy.deepcopy(data)
188+
data = self.__deepcopy(data)
188189
clone.__dict__.update(data)
189190
return clone
190191

@@ -823,3 +824,24 @@ def __deepcopy__(self, memo):
823824
.. versionadded:: 2.3+
824825
"""
825826
return self.__clone(deepcopy=True)
827+
828+
def __deepcopy(self, x, memo=None):
829+
"""Deepcopy helper for the data dictionary.
830+
831+
Regular expressions cannot be deep copied but as they are immutable we
832+
don't have to copy them when cloning.
833+
"""
834+
y = {}
835+
if memo is None:
836+
memo = {}
837+
val_id = id(x)
838+
if val_id in memo:
839+
return memo.get(val_id)
840+
memo[val_id] = y
841+
for key, value in x.iteritems():
842+
if isinstance(value, dict):
843+
value = self.__deepcopy(value, memo)
844+
elif not isinstance(value, RE_TYPE):
845+
value = copy.deepcopy(value, memo)
846+
y[copy.deepcopy(key, memo)] = value
847+
return y

test/test_cursor.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import copy
1717
import itertools
1818
import random
19+
import re
1920
import sys
2021
import unittest
2122
sys.path[0:0] = [""]
@@ -464,7 +465,8 @@ class MyClass(dict):
464465
self.assertEqual(type(MyClass()), type(cursor[0]))
465466

466467
# Just test attributes
467-
cursor = self.db.test.find(skip=1,
468+
cursor = self.db.test.find({"x": re.compile("^hello.*")},
469+
skip=1,
468470
timeout=False,
469471
snapshot=True,
470472
tailable=True,
@@ -508,6 +510,19 @@ class MyClass(dict):
508510
cursor4._Cursor__fields['cursor4'] = False
509511
self.assertFalse('cursor4' in cursor._Cursor__fields)
510512

513+
# Test memo when deepcopying queries
514+
query = {"hello": "world"}
515+
query["reflexive"] = query
516+
cursor = self.db.test.find(query)
517+
518+
cursor2 = copy.deepcopy(cursor)
519+
520+
self.assertNotEqual(id(cursor._Cursor__spec),
521+
id(cursor2._Cursor__spec))
522+
self.assertEqual(id(cursor2._Cursor__spec['reflexive']),
523+
id(cursor2._Cursor__spec))
524+
self.assertEqual(len(cursor2._Cursor__spec), 2)
525+
511526
def test_add_remove_option(self):
512527
cursor = self.db.test.find()
513528
self.assertEqual(0, cursor._Cursor__query_options())

0 commit comments

Comments
 (0)