Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nested #85

Closed
wants to merge 2 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 60 additions & 7 deletions sqlitedict.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ def exec_(_code_, _globs_=None, _locs_=None):
exec_("def reraise(tp, value, tb=None):\n"
" raise tp, value, tb\n")
else:
unicode = str
def reraise(tp, value, tb=None):
if value is None:
value = tp()
Expand Down Expand Up @@ -104,12 +105,13 @@ def decode(obj):
"""Deserialize objects retrieved from SQLite."""
return loads(bytes(obj))


class SqliteDict(DictClass):
VALID_FLAGS = ['c', 'r', 'w', 'n']

def __init__(self, filename=None, tablename='unnamed', flag='c',
autocommit=False, journal_mode="DELETE", encode=encode, decode=decode):
autocommit=False, journal_mode="DELETE",
encode=encode, decode=decode,
_parent=None,_subtable=None):
"""
Initialize a thread-safe sqlite-backed dictionary. The dictionary will
be a table `tablename` in database file `filename`. A single file (=database)
Expand Down Expand Up @@ -141,6 +143,24 @@ def __init__(self, filename=None, tablename='unnamed', flag='c',
The default is to use pickle.

"""
# Handle if it has a parent and skip the rest
self._parent = _parent
self._subtable = _subtable
if self._parent is not None:
if self._subtable:
self.tablename = self._subtable
else:
self.tablename = _parent.tablename + '_' + hex(random.randint(0, 0xffffff))[2:]

for attr in ['filename','autocommit','journal_mode',
'_encode','_decode','conn','flag']:
setattr(self,attr,getattr(_parent,attr))
logger.info("opening Sqlite table %r in %s" % (tablename, filename))
MAKE_TABLE = 'CREATE TABLE IF NOT EXISTS "%s" (key TEXT PRIMARY KEY, value BLOB)' % self.tablename
self._parent.commit()
self.conn.execute(MAKE_TABLE)
return

self.in_temp = filename is None
if self.in_temp:
randpart = hex(random.randint(0, 0xffffff))[2:]
Expand All @@ -165,8 +185,8 @@ def __init__(self, filename=None, tablename='unnamed', flag='c',
self.tablename = tablename
self.autocommit = autocommit
self.journal_mode = journal_mode
self.encode = encode
self.decode = decode
self._encode = encode
self._decode = decode

logger.info("opening Sqlite table %r in %s" % (tablename, filename))
MAKE_TABLE = 'CREATE TABLE IF NOT EXISTS "%s" (key TEXT PRIMARY KEY, value BLOB)' % self.tablename
Expand Down Expand Up @@ -250,14 +270,22 @@ def __setitem__(self, key, value):
raise RuntimeError('Refusing to write to read-only SqliteDict')

ADD_ITEM = 'REPLACE INTO "%s" (key, value) VALUES (?,?)' % self.tablename
self.conn.execute(ADD_ITEM, (key, self.encode(value)))

val = self.encode(value)
self.conn.execute(ADD_ITEM, (key, val))

def __delitem__(self, key):
if self.flag == 'r':
raise RuntimeError('Refusing to delete from read-only SqliteDict')

if key not in self:
raise KeyError(key)

# Handle if subdict
obj = self[key]
if isinstance(obj,SqliteDict) and obj._parent is not None:
DEL_TAB = 'DROP TABLE "%s"' % obj.tablename
self.conn.execute(DEL_TAB, tuple())

DEL_ITEM = 'DELETE FROM "%s" WHERE key = ?' % self.tablename
self.conn.execute(DEL_ITEM, (key,))

Expand Down Expand Up @@ -347,6 +375,10 @@ def terminate(self):
logger.exception("failed to delete %s" % (self.filename))

def __del__(self):
# Not sure why but when creating a subdict, it tries to close itself
# so ignore subdicts
if self._parent is not None:
return
# like close(), but assume globals are gone by now (do not log!)
try:
self.close(do_log=False, force=True)
Expand All @@ -355,14 +387,35 @@ def __del__(self):
# closed after connection lost (exceptions are always ignored
# in __del__ method.
pass

def encode(self,obj):
if isinstance(obj,SqliteDict) and obj._parent is not None:
# Just store a reference to the tablename
obj = '__nested:' + obj.tablename
return self._encode(obj)

def decode(self,obj):
obj = self._decode(obj)
if not (isinstance(obj,(str,unicode)) and obj.startswith('__nested:')):
return obj
_subtable = obj[len('__nested:'):]
return self.subdict(_subtable=_subtable)

def subdict(self,_subtable=None):
"""
Return a dictionary that is *also* a SqliteDict
"""
# Note that all other attributed are automatically taken from
# the parent so no need to enumerate them here
return SqliteDict(_parent=self,_subtable=_subtable)


# Adding extra methods for python 2 compatibility (at import time)
if major_version == 2:
SqliteDict.__nonzero__ = SqliteDict.__bool__
del SqliteDict.__bool__ # not needed and confusing
#endclass SqliteDict


class SqliteMultithread(Thread):
"""
Wrap sqlite connection in a way that allows concurrent requests from multiple threads.
Expand Down