Permalink
Browse files

Fixed an issue where _private vars would overwrite other entries in '…

…couchable:' due to how the object __dict__ was getting updated. Was subtle.
  • Loading branch information...
1 parent bbca7df commit d251050410486e83c1447c646b998ddc588bac75 Eli Stevens committed May 17, 2011
Showing with 90 additions and 14 deletions.
  1. +26 −13 couchable/core.py
  2. +64 −1 couchable/testing/test_couchable.py
View
@@ -263,7 +263,7 @@ def addClassView(self, cls, name, keys=None, multikeys=None, value='1', reduce=N
@type cls: type
@param cls: The class of objects that the view should be restricted to. Note that sub/superclasses are not considered.
@type name: string
- @param name: The string to suffix the name of the view with (byclass:module.class:name).
+ @param name: The string to suffix the name of the view with (byclass+module.class:name).
@type keys: list of strings
@param keys: A list of unescaped javascript expressions to use as the key for the view.
@type multikeys: list of list of strings
@@ -273,7 +273,7 @@ def addClassView(self, cls, name, keys=None, multikeys=None, value='1', reduce=N
@type reduce: string
@param reduce: A CouchDB reduce function. Can be None, javascript, or the built-in '_sum' kind of reduce function.
@rtype: str
- @return: The full name of the view (byclass:module.class:name).
+ @return: The full name of the view (byclass+module.class+name).
"""
multikeys = multikeys or [keys]
emit_js = '\n'.join(['''emit([{}], {});'''.format(', '.join([('info.private.' + key if key[0] == '_' else 'doc.' + key) for key in keys]), value) for keys in multikeys])
@@ -290,7 +290,7 @@ def addClassView(self, cls, name, keys=None, multikeys=None, value='1', reduce=N
byclass_js = string.Template(byclass_js).safe_substitute(module=cls.__module__, cls=cls.__name__, emit=emit_js, value=value)
- fullName = 'byclass:{}.{}:{}'.format(cls.__module__, cls.__name__, name)
+ fullName = 'byclass-{}-{}--{}'.format(cls.__module__, cls.__name__, name)
couchdb.design.ViewDefinition('couchable', fullName, byclass_js, reduce).sync(self.db)
return fullName
@@ -567,9 +567,13 @@ def _pack_object(self, parent_doc, data, attachment_dict, name, isKey, topLevel=
doc = parent_doc
else:
doc = {}
+
+ self._objInfo_doc(data, doc)
+ update_dict = self._pack_dict_keyMeansObject(parent_doc, data.__dict__, attachment_dict, name, True, topLevel)
+
+ assert set(doc).intersection(set(update_dict)) == set(), repr(set(doc).intersection(set(update_dict)))
- doc.update(self._pack_dict_keyMeansObject(parent_doc, data.__dict__, attachment_dict, name, True))
- doc = self._objInfo_doc(data, doc)
+ doc.update(update_dict)
if isinstance(data, dict) and type(data) is not dict:
doc[FIELD_NAME]['dict'] = self._pack_dict_keyMeansObject(parent_doc, dict(dict.items(data)), attachment_dict, name, False)
@@ -713,7 +717,7 @@ def _pack_consargs_keyAsKey(self, parent_doc, data, attachment_dict, name, isKey
"""
if isKey:
key_str = '{}{}:{}:{!r}'.format(FIELD_NAME, 'key', typestr(data), data)
-
+
parent_doc.setdefault(FIELD_NAME, {})
parent_doc[FIELD_NAME].setdefault('keys', {})
parent_doc[FIELD_NAME]['keys'][key_str] = self._pack_consargs_keyAsKey(parent_doc, data, attachment_dict, name, False)
@@ -761,7 +765,7 @@ def _pack_list_noKey(self, parent_doc, data, attachment_dict, name, isKey):
return [self._pack(parent_doc, x, attachment_dict, '{}[{}]'.format(name, i), False) for i, x in enumerate(data)]
@_packer(dict)
- def _pack_dict_keyMeansObject(self, parent_doc, data, attachment_dict, name, isObjDict):
+ def _pack_dict_keyMeansObject(self, parent_doc, data, attachment_dict, name, isObjDict, topLevel=False):
"""
>>> cdb=CouchableDb('testing')
>>> parent_doc = {}
@@ -808,24 +812,33 @@ def _pack_dict_keyMeansObject(self, parent_doc, data, attachment_dict, name, isO
if isObjDict:
- private_keys = {k for k in data.keys() if k.startswith('_') and k not in ('_id', '_rev', '_attachments', '_cdb')}
nameFormat_str = '{}.{}'
else:
- private_keys = set()
nameFormat_str = '{}[{}]'
+ if topLevel:
+ private_keys = {k for k in data.keys() if k.startswith('_') and k not in ('_id', '_rev', '_attachments', '_cdb')}
+ else:
+ private_keys = set()
+
doc = {self._pack(parent_doc, k, attachment_dict, '{}>{}'.format(name, str(k)), True):
self._pack(parent_doc, v, attachment_dict, nameFormat_str.format(name, str(k)), False)
for k,v in data.items() if k not in private_keys and k not in set(['_attachments', '_cdb'])}
#assert '_attachments' not in doc, ', '.join([str(data), str(isObjDict)])
if private_keys:
- doc.setdefault(FIELD_NAME, {})
+ if topLevel:
+ private_doc = parent_doc
+ else:
+ private_doc = doc
+
+ private_doc.setdefault(FIELD_NAME, {})
#doc[FIELD_NAME].setdefault('private', {})
- doc[FIELD_NAME]['private'] = {self._pack(parent_doc, k, attachment_dict, '{}>{}'.format(name, str(k)), True):
- self._pack(parent_doc, v, attachment_dict, '{}.{}'.format(name, str(k)), False)
- for k,v in data.items() if k in private_keys}
+ private_doc[FIELD_NAME]['private'] = {
+ self._pack(parent_doc, k, attachment_dict, '{}>{}'.format(name, str(k)), True):
+ self._pack(parent_doc, v, attachment_dict, '{}.{}'.format(name, str(k)), False)
+ for k,v in data.items() if k in private_keys}
#parent_doc.setdefault(FIELD_NAME, {})
#parent_doc[FIELD_NAME]['private'] = {self._pack(parent_doc, k, attachment_dict, '{}>{}'.format(name, str(k)), True):
# self._pack(parent_doc, v, attachment_dict, '{}.{}'.format(name, str(k)), False)
@@ -68,6 +68,12 @@ def __eq__(self, other):
def __hash__(self):
return hash(frozenset(self.__dict__.keys()))
+
+class SimplePickle(Simple):
+ pass
+
+couchable.registerPickleType(SimplePickle)
+
class SimpleDoc(couchable.CouchableDoc):
def __init__(self, **kwargs):
for name, value in kwargs.items():
@@ -328,7 +334,7 @@ def test_32_types(self):
self.assertEqual(obj.int, int)
- @attr('couchable', 'elis')
+ @attr('couchable')
def test_40_external_edits(self):
obj = Simple(i=1)
_id = self.cdb.store(obj)
@@ -345,6 +351,63 @@ def test_40_external_edits(self):
#assert False
+ @attr('couchable', 'elis')
+ def test_40_nested_docs_with_tuples(self):
+ nt = NamedTupleABC(1,2,3)
+
+ targetobj = Simple(aaa=nt, target={nt: 'bbb'}, zzz=nt, _foo=1)
+ obj = Simple(sub={nt: targetobj}, abc2={nt: 'abc2'}, _foo=1)
+
+ #subobj.o = obj
+
+ self.cdb.store(targetobj)
+ _id = self.cdb.store(obj)
+
+
+ target_id = targetobj._id
+
+ del obj
+ del targetobj
+ gc.collect()
+ self.assertFalse(self.cdb._obj_by_id)
+
+ #obj = self.cdb.load(_id)
+
+ doc = self.cdb.db[target_id]
+
+ self.assertIn('keys', doc['couchable:'])
+
+ #self.assertEqual(type(obj.sub.abc), NamedTupleABC)
+ #self.assertEqual(obj.sub.abc.a, 1)
+ #
+ #self.assertEqual(type(obj.sub.ts), TupleSubclass)
+ #self.assertEqual(obj.sub.ts[3], 4)
+
+ #assert False
+
+
+ @attr('couchable', 'elis')
+ def test_40_pickles(self):
+ pk = SimplePickle(a=1, b=2, c=3)
+
+ obj = Simple(d={'pk': pk})
+
+ _id = self.cdb.store(obj)
+
+ del obj
+ del pk
+ gc.collect()
+
+ #for v in self.cdb._obj_by_id.values():
+ # print v
+ # print gc.get_referrers(v)
+ self.assertFalse(self.cdb._obj_by_id, repr(self.cdb._obj_by_id.items()))
+
+ obj = self.cdb.load(_id)
+
+ self.assertEqual(obj.d['pk'].a, 1)
+
+ #assert False
@attr('couchable')

0 comments on commit d251050

Please sign in to comment.