implement or explicitly disallow other top-level dict operations on ORM Document, and add more automated tests

This commit is contained in:
Noah Levitt 2017-02-28 17:56:44 -08:00
parent e8db41763b
commit 536bf8d1d8
3 changed files with 111 additions and 27 deletions

View File

@ -47,7 +47,7 @@ class WatchedDict(dict, object):
def popitem(self):
self.callback(self.field)
return dict.popitem()
return dict.popitem(self)
def setdefault(self, *args):
self.callback(self.field)
@ -57,9 +57,8 @@ class WatchedDict(dict, object):
else:
return dict.setdefault(self, *args)
def update(self, *args, **kwargs):
# looks a little tricky
raise Exception('not implemented')
# XXX worth implementing?
update = None
class WatchedList(list, object):
def __init__(self, l, callback, field):
@ -100,9 +99,8 @@ class WatchedList(list, object):
self.callback(self.field)
return list.pop(self, index)
def clear(self):
self.callback(self.field)
return list.clear(self)
# python 2.7 doesn't have this anyway
clear = None
def sort(self, key=None, reverse=False):
self.callback(self.field)
@ -157,14 +155,6 @@ class Document(dict, object):
@classproperty
def table(cls):
'''
Returns default table name, which is the class name, lowercased.
Subclasses can override the table name like so:
class Something(doublethink.Document):
table = 'my_table_name'
'''
return cls.__name__.lower()
@classmethod
@ -214,7 +204,20 @@ class Document(dict, object):
if key in self._updates:
del self._updates[key]
# XXX probably need the other stuff like in WatchedDict
def setdefault(self, *args):
need_update = False
if not args[0] in self:
need_update = True
result = dict.setdefault(self, *args)
if need_update:
self._updated(args[0])
return result
# dict methods we don't want to support
clear = None
pop = None
popitem = None
update = None
def _updated(self, field):
# callback for all updates
@ -277,11 +280,12 @@ class Document(dict, object):
'unexpected result %s from rethinkdb query %s' % (
result, query))
if not should_insert and self._deletes:
self._r.table(self.table).replace(
r.row.without(self._deletes)).run()
query = self._r.table(self.table).replace(
r.row.without(self._deletes))
result = query.run()
if result['errors']: # primary key not found
should_insert = True
elif not result['replaced'] == 0:
elif result['replaced'] != 1:
raise Exception(
'unexpected result %s from rethinkdb query %s' % (
result, query))

View File

@ -3,7 +3,7 @@ import codecs
setuptools.setup(
name='doublethink',
version='0.2.0.dev64',
version='0.2.0.dev65',
packages=['doublethink'],
classifiers=[
'Programming Language :: Python :: 2.7',

View File

@ -321,16 +321,14 @@ def test_orm(r):
assert d._updates == {
'm': 'n', 'c': {'d': 'e', 'o': 'p'}, 'f': ['q', 'h'], 'i': 't'}
# list manipulations
d.f.append(['sublist'])
assert d._updates == {
'm': 'n', 'c': {'d': 'e', 'o': 'p'},
'f': ['q', 'h', ['sublist']], 'i': 't'}
### list.clear not in python 2.7
# d.f[2].clear()
# assert d._updates == {
# 'm': 'n', 'c': {'d': 'e', 'o': 'p'},
# 'f': ['q', 'h', []], 'i': 't'}
with pytest.raises(TypeError):
d.f[2].clear()
result = d.f.pop()
assert result == ['sublist']
@ -364,20 +362,102 @@ def test_orm(r):
'm': 'n', 'c': {'d': 'e', 'o': 'p'},
'f': ['u', 'v', {'w': 'x', 'y': 'z'}], 'i': 't'}
expected = dict(d)
# more nested field dict operations
del d['c']['d']
assert d._updates == {
'm': 'n', 'c': {'o': 'p'},
'f': ['u', 'v', {'w': 'x', 'y': 'z'}], 'i': 't'}
d['c'].clear()
assert d._updates == {
'm': 'n', 'c': {},
'f': ['u', 'v', {'w': 'x', 'y': 'z'}], 'i': 't'}
assert d['c'].setdefault('aa') is None
assert d._updates == {
'm': 'n', 'c': {'aa': None},
'f': ['u', 'v', {'w': 'x', 'y': 'z'}], 'i': 't'}
d['c'].setdefault('aa', 'bb') is None
assert d._updates == {
'm': 'n', 'c': {'aa': None},
'f': ['u', 'v', {'w': 'x', 'y': 'z'}], 'i': 't'}
d['c'].setdefault('cc', 'dd') == 'dd'
assert d._updates == {
'm': 'n', 'c': {'aa': None, 'cc': 'dd'},
'f': ['u', 'v', {'w': 'x', 'y': 'z'}], 'i': 't'}
d['c'].setdefault('cc') == 'dd'
assert d._updates == {
'm': 'n', 'c': {'aa': None, 'cc': 'dd'},
'f': ['u', 'v', {'w': 'x', 'y': 'z'}], 'i': 't'}
d['c'].setdefault('cc', 'ee') == 'dd'
assert d._updates == {
'm': 'n', 'c': {'aa': None, 'cc': 'dd'},
'f': ['u', 'v', {'w': 'x', 'y': 'z'}], 'i': 't'}
assert d['c'].pop('cc') == 'dd'
assert d._updates == {
'm': 'n', 'c': {'aa': None},
'f': ['u', 'v', {'w': 'x', 'y': 'z'}], 'i': 't'}
assert d['f'][2].popitem()
assert d._updates['f'][2] in ({'w':'x'}, {'y':'z'})
d.save()
assert d._updates == {}
assert d._deletes == set()
d_copy = SomeDoc.load(r, d.id)
assert d == d_copy
d['zuh'] = 'toot'
d.save()
assert d != d_copy
d_copy.refresh()
assert d == d_copy
# top level dict operations
with pytest.raises(TypeError):
d.clear()
with pytest.raises(TypeError):
d.pop('m')
with pytest.raises(TypeError):
d.popitem()
with pytest.raises(TypeError):
d.update({'x':'y'})
assert d.setdefault('ee') is None
assert d._updates == {'ee': None}
d.setdefault('ee', 'ff') is None
assert d._updates == {'ee': None}
d.setdefault('gg', 'hh') == 'hh'
assert d._updates == {'ee': None, 'gg': 'hh'}
d.setdefault('gg') == 'hh'
assert d._updates == {'ee': None, 'gg': 'hh'}
d.setdefault('gg', 'ii') == 'hh'
assert d._updates == {'ee': None, 'gg': 'hh'}
d.save()
assert d._updates == {}
assert d._deletes == set()
d_copy = SomeDoc.load(r, d.id)
assert d == d_copy
d['yuh'] = 'soot'
d.save()
assert d != d_copy
d_copy.refresh()
assert d == d_copy
def test_orm_pk(r):
class NonstandardPrimaryKey(doublethink.Document):
@classmethod