Permalink
Browse files

Ensure deepcopy clones using the correct type - PYTHON-459

  • Loading branch information...
1 parent 2435f86 commit 828aad3aad35f60aea5f5a34050a808aadacc2be @rozza rozza committed Jan 8, 2013
Showing with 59 additions and 8 deletions.
  1. +1 −4 bson/__init__.py
  2. +11 −1 bson/son.py
  3. +1 −1 pymongo/cursor.py
  4. +7 −0 test/test_cursor.py
  5. +39 −2 test/test_son.py
View
@@ -32,7 +32,7 @@
from bson.min_key import MinKey
from bson.objectid import ObjectId
from bson.py3compat import b, binary_type
-from bson.son import SON
+from bson.son import SON, RE_TYPE
from bson.timestamp import Timestamp
from bson.tz_util import utc
@@ -52,9 +52,6 @@
PY3 = sys.version_info[0] == 3
-# This sort of sucks, but seems to be as good as it gets...
-RE_TYPE = type(re.compile(""))
-
MAX_INT32 = 2147483647
MIN_INT32 = -2147483648
MAX_INT64 = 9223372036854775807
View
@@ -19,6 +19,10 @@
dictionary."""
import copy
+import re
+
+# This sort of sucks, but seems to be as good as it gets...
+RE_TYPE = type(re.compile(""))
class SON(dict):
@@ -227,6 +231,12 @@ def transform_value(value):
def __deepcopy__(self, memo):
out = SON()
+ val_id = id(self)
+ if val_id in memo:
+ return memo.get(val_id)
+ memo[val_id] = out
for k, v in self.iteritems():
- out[k] = copy.deepcopy(v, memo)
+ if not isinstance(v, RE_TYPE):
+ v = copy.deepcopy(v, memo)
+ out[k] = v
return out
View
@@ -854,7 +854,7 @@ def __deepcopy(self, x, memo=None):
return memo.get(val_id)
memo[val_id] = y
for key, value in x.iteritems():
- if isinstance(value, dict):
+ if isinstance(value, dict) and not isinstance(value, SON):
value = self.__deepcopy(value, memo)
elif not isinstance(value, RE_TYPE):
value = copy.deepcopy(value, memo)
View
@@ -24,6 +24,7 @@
from nose.plugins.skip import SkipTest
from bson.code import Code
+from bson.son import SON
from pymongo import (ASCENDING,
DESCENDING)
from pymongo.database import Database
@@ -522,6 +523,12 @@ class MyClass(dict):
id(cursor2._Cursor__spec))
self.assertEqual(len(cursor2._Cursor__spec), 2)
+ # Ensure hints are cloned as the correct type
+ cursor = self.db.test.find().hint([('z', 1), ("a", 1)])
+ cursor2 = copy.deepcopy(cursor)
+ self.assertTrue(isinstance(cursor2._Cursor__hint, SON))
+ self.assertEqual(cursor._Cursor__hint, cursor2._Cursor__hint)
+
def test_add_remove_option(self):
cursor = self.db.test.find()
self.assertEqual(0, cursor._Cursor__query_options())
View
@@ -14,9 +14,11 @@
"""Tests for the son module."""
-import unittest
-import sys
+import copy
import pickle
+import re
+import sys
+import unittest
sys.path[0:0] = [""]
from nose.plugins.skip import SkipTest
@@ -111,5 +113,40 @@ def test_pickle_backwards_compatability(self):
son_2_1_1 = pickle.loads(pickled_with_2_1_1)
self.assertEqual(son_2_1_1, SON([]))
+ def test_copying(self):
+ simple_son = SON([])
+ complex_son = SON([('son', simple_son),
+ ('list', [simple_son, simple_son])])
+ regex_son = SON([("x", re.compile("^hello.*"))])
+ reflexive_son = SON([('son', simple_son)])
+ reflexive_son["reflexive"] = reflexive_son
+
+ simple_son1 = copy.copy(simple_son)
+ self.assertEqual(simple_son, simple_son1)
+
+ complex_son1 = copy.copy(complex_son)
+ self.assertEqual(complex_son, complex_son1)
+
+ regex_son1 = copy.copy(regex_son)
+ self.assertEqual(regex_son, regex_son1)
+
+ reflexive_son1 = copy.copy(reflexive_son)
+ self.assertEqual(reflexive_son, reflexive_son1)
+
+ # Test deepcopying
+ simple_son1 = copy.deepcopy(simple_son)
+ self.assertEqual(simple_son, simple_son1)
+
+ regex_son1 = copy.deepcopy(regex_son)
+ self.assertEqual(regex_son, regex_son1)
+
+ complex_son1 = copy.deepcopy(complex_son)
+ self.assertEqual(complex_son, complex_son1)
+
+ reflexive_son1 = copy.deepcopy(reflexive_son)
+ self.assertEqual(reflexive_son.keys(), reflexive_son1.keys())
+ self.assertEqual(id(reflexive_son1), id(reflexive_son1["reflexive"]))
+
+
if __name__ == "__main__":
unittest.main()

0 comments on commit 828aad3

Please sign in to comment.