Move THING_CLASSES and thing-related methods to new mixin.
I realized there's no need to have that on the global scope when it is only used for the thing-methods.
This commit is contained in:
parent
5dffe52830
commit
363d0bac67
1 changed files with 170 additions and 164 deletions
|
@ -158,6 +158,175 @@ class PDBBookmarkMixin:
|
|||
return bookmark
|
||||
|
||||
|
||||
class PDBCacheManagerMixin:
|
||||
_THING_CLASSES = {
|
||||
'album':
|
||||
{
|
||||
'class': objects.Album,
|
||||
'exception': exceptions.NoSuchAlbum,
|
||||
},
|
||||
'bookmark':
|
||||
{
|
||||
'class': objects.Bookmark,
|
||||
'exception': exceptions.NoSuchBookmark,
|
||||
},
|
||||
'photo':
|
||||
{
|
||||
'class': objects.Photo,
|
||||
'exception': exceptions.NoSuchPhoto,
|
||||
},
|
||||
'tag':
|
||||
{
|
||||
'class': objects.Tag,
|
||||
'exception': exceptions.NoSuchTag,
|
||||
},
|
||||
'user':
|
||||
{
|
||||
'class': objects.User,
|
||||
'exception': exceptions.NoSuchUser,
|
||||
}
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def get_cached_instance(self, thing_type, db_row):
|
||||
'''
|
||||
Check if there is already an instance in the cache and return that.
|
||||
Otherwise, a new instance is created, cached, and returned.
|
||||
|
||||
Note that in order to call this method you have to already have a
|
||||
db_row which means performing some select. If you only have the ID,
|
||||
use get_thing_by_id, as there may already be a cached instance to save
|
||||
you the select.
|
||||
'''
|
||||
thing_map = self._THING_CLASSES[thing_type]
|
||||
|
||||
thing_class = thing_map['class']
|
||||
thing_table = thing_class.table
|
||||
thing_cache = self.caches[thing_type]
|
||||
|
||||
if isinstance(db_row, dict):
|
||||
thing_id = db_row['id']
|
||||
else:
|
||||
thing_index = constants.SQL_INDEX[thing_table]
|
||||
thing_id = db_row[thing_index['id']]
|
||||
|
||||
try:
|
||||
thing = thing_cache[thing_id]
|
||||
except KeyError:
|
||||
thing = thing_class(self, db_row)
|
||||
thing_cache[thing_id] = thing
|
||||
return thing
|
||||
|
||||
def get_root_things(self, thing_type):
|
||||
'''
|
||||
For Groupable types, yield things which have no parent.
|
||||
'''
|
||||
thing_map = self._THING_CLASSES[thing_type]
|
||||
|
||||
thing_class = thing_map['class']
|
||||
thing_table = thing_class.table
|
||||
group_table = thing_class.group_table
|
||||
|
||||
query = f'''
|
||||
SELECT * FROM {thing_table}
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM {group_table}
|
||||
WHERE memberid == {thing_table}.id
|
||||
)
|
||||
'''
|
||||
|
||||
rows = self.sql_select(query)
|
||||
for row in rows:
|
||||
thing = self.get_cached_instance(thing_type, row)
|
||||
yield thing
|
||||
|
||||
def get_thing_by_id(self, thing_type, thing_id):
|
||||
'''
|
||||
This method will first check the cache to see if there is already an
|
||||
instance with that ID, in which case we don't need to perform any SQL
|
||||
select. If it is not in the cache, then a new instance is created,
|
||||
cached, and returned.
|
||||
'''
|
||||
thing_map = self._THING_CLASSES[thing_type]
|
||||
|
||||
thing_class = thing_map['class']
|
||||
if isinstance(thing_id, thing_class):
|
||||
# This could be used to check if your old reference to an object is
|
||||
# still in the cache, or re-select it from the db to make sure it
|
||||
# still exists and re-cache.
|
||||
# Probably an uncommon need but... no harm I think.
|
||||
thing_id = thing_id.id
|
||||
|
||||
thing_cache = self.caches[thing_type]
|
||||
try:
|
||||
return thing_cache[thing_id]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
query = f'SELECT * FROM {thing_class.table} WHERE id == ?'
|
||||
bindings = [thing_id]
|
||||
thing_row = self.sql_select_one(query, bindings)
|
||||
if thing_row is None:
|
||||
raise thing_map['exception'](thing_id)
|
||||
thing = thing_class(self, thing_row)
|
||||
thing_cache[thing_id] = thing
|
||||
return thing
|
||||
|
||||
def get_things(self, thing_type):
|
||||
'''
|
||||
Yield things, unfiltered, in whatever order they appear in the database.
|
||||
'''
|
||||
thing_map = self._THING_CLASSES[thing_type]
|
||||
table = thing_map['class'].table
|
||||
query = f'SELECT * FROM {table}'
|
||||
|
||||
things = self.sql_select(query)
|
||||
for thing_row in things:
|
||||
thing = self.get_cached_instance(thing_type, thing_row)
|
||||
yield thing
|
||||
|
||||
def get_things_by_id(self, thing_type, thing_ids):
|
||||
'''
|
||||
Given multiple IDs, this method will find which ones are in the cache
|
||||
and which ones need to be selected from the db.
|
||||
This is better than calling get_thing_by_id in a loop because we can
|
||||
use a single SQL select to get batches of up to 999 items.
|
||||
'''
|
||||
thing_map = self._THING_CLASSES[thing_type]
|
||||
thing_class = thing_map['class']
|
||||
thing_cache = self.caches[thing_type]
|
||||
|
||||
ids_needed = set()
|
||||
for thing_id in thing_ids:
|
||||
try:
|
||||
thing = thing_cache[thing_id]
|
||||
except KeyError:
|
||||
ids_needed.add(thing_id)
|
||||
else:
|
||||
yield thing
|
||||
|
||||
ids_needed = list(ids_needed)
|
||||
while ids_needed:
|
||||
# SQLite3 has a limit of 999 ? in a query, so we must batch them.
|
||||
id_batch = ids_needed[:999]
|
||||
ids_needed = ids_needed[999:]
|
||||
|
||||
qmarks = ','.join('?' * len(id_batch))
|
||||
qmarks = f'({qmarks})'
|
||||
query = f'SELECT * FROM {thing_class.table} WHERE id IN {qmarks}'
|
||||
more_things = self.sql_select(query, id_batch)
|
||||
for thing_row in more_things:
|
||||
# Normally we would call `get_cached_instance` instead of
|
||||
# constructing here. But we already know for a fact that this
|
||||
# object is not in the cache because it made it past the
|
||||
# previous loop.
|
||||
thing = thing_class(self, db_row=thing_row)
|
||||
thing_cache[thing.id] = thing
|
||||
yield thing
|
||||
|
||||
|
||||
class PDBPhotoMixin:
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
@ -1300,6 +1469,7 @@ class PDBUtilMixin:
|
|||
class PhotoDB(
|
||||
PDBAlbumMixin,
|
||||
PDBBookmarkMixin,
|
||||
PDBCacheManagerMixin,
|
||||
PDBPhotoMixin,
|
||||
PDBSQLMixin,
|
||||
PDBTagMixin,
|
||||
|
@ -1486,142 +1656,6 @@ class PhotoDB(
|
|||
self._cached_frozen_children = tag_export.flat_dict(self.get_tags())
|
||||
return self._cached_frozen_children
|
||||
|
||||
def get_cached_instance(self, thing_type, db_row):
|
||||
'''
|
||||
Check if there is already an instance in the cache and return that.
|
||||
Otherwise, a new instance is created, cached, and returned.
|
||||
|
||||
Note that in order to call this method you have to already have a
|
||||
db_row which means performing some select. If you only have the ID,
|
||||
use get_thing_by_id, as there may already be a cached instance to save
|
||||
you the select.
|
||||
'''
|
||||
thing_map = _THING_CLASSES[thing_type]
|
||||
|
||||
thing_class = thing_map['class']
|
||||
thing_table = thing_class.table
|
||||
thing_cache = self.caches[thing_type]
|
||||
|
||||
if isinstance(db_row, dict):
|
||||
thing_id = db_row['id']
|
||||
else:
|
||||
thing_index = constants.SQL_INDEX[thing_table]
|
||||
thing_id = db_row[thing_index['id']]
|
||||
|
||||
try:
|
||||
thing = thing_cache[thing_id]
|
||||
except KeyError:
|
||||
thing = thing_class(self, db_row)
|
||||
thing_cache[thing_id] = thing
|
||||
return thing
|
||||
|
||||
def get_root_things(self, thing_type):
|
||||
'''
|
||||
For Groupable types, yield things which have no parent.
|
||||
'''
|
||||
thing_map = _THING_CLASSES[thing_type]
|
||||
|
||||
thing_class = thing_map['class']
|
||||
thing_table = thing_class.table
|
||||
group_table = thing_class.group_table
|
||||
|
||||
query = f'''
|
||||
SELECT * FROM {thing_table}
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM {group_table}
|
||||
WHERE memberid == {thing_table}.id
|
||||
)
|
||||
'''
|
||||
|
||||
rows = self.sql_select(query)
|
||||
for row in rows:
|
||||
thing = self.get_cached_instance(thing_type, row)
|
||||
yield thing
|
||||
|
||||
def get_thing_by_id(self, thing_type, thing_id):
|
||||
'''
|
||||
This method will first check the cache to see if there is already an
|
||||
instance with that ID, in which case we don't need to perform any SQL
|
||||
select. If it is not in the cache, then a new instance is created,
|
||||
cached, and returned.
|
||||
'''
|
||||
thing_map = _THING_CLASSES[thing_type]
|
||||
|
||||
thing_class = thing_map['class']
|
||||
if isinstance(thing_id, thing_class):
|
||||
# This could be used to check if your old reference to an object is
|
||||
# still in the cache, or re-select it from the db to make sure it
|
||||
# still exists and re-cache.
|
||||
# Probably an uncommon need but... no harm I think.
|
||||
thing_id = thing_id.id
|
||||
|
||||
thing_cache = self.caches[thing_type]
|
||||
try:
|
||||
return thing_cache[thing_id]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
query = 'SELECT * FROM %s WHERE id == ?' % thing_class.table
|
||||
bindings = [thing_id]
|
||||
thing_row = self.sql_select_one(query, bindings)
|
||||
if thing_row is None:
|
||||
raise thing_map['exception'](thing_id)
|
||||
thing = thing_class(self, thing_row)
|
||||
thing_cache[thing_id] = thing
|
||||
return thing
|
||||
|
||||
def get_things(self, thing_type):
|
||||
'''
|
||||
Yield things, unfiltered, in whatever order they appear in the database.
|
||||
'''
|
||||
thing_map = _THING_CLASSES[thing_type]
|
||||
|
||||
query = 'SELECT * FROM %s' % thing_map['class'].table
|
||||
|
||||
things = self.sql_select(query)
|
||||
for thing_row in things:
|
||||
thing = self.get_cached_instance(thing_type, thing_row)
|
||||
yield thing
|
||||
|
||||
def get_things_by_id(self, thing_type, thing_ids):
|
||||
'''
|
||||
Given multiple IDs, this method will find which ones are in the cache
|
||||
and which ones need to be selected from the db.
|
||||
This is better than calling get_thing_by_id in a loop because we can
|
||||
use a single SQL select to get batches of up to 999 items.
|
||||
'''
|
||||
thing_map = _THING_CLASSES[thing_type]
|
||||
thing_class = thing_map['class']
|
||||
thing_cache = self.caches[thing_type]
|
||||
|
||||
ids_needed = set()
|
||||
for thing_id in thing_ids:
|
||||
try:
|
||||
thing = thing_cache[thing_id]
|
||||
except KeyError:
|
||||
ids_needed.add(thing_id)
|
||||
else:
|
||||
yield thing
|
||||
|
||||
ids_needed = list(ids_needed)
|
||||
while ids_needed:
|
||||
# SQLite3 has a limit of 999 ? in a query, so we must batch them.
|
||||
id_batch = ids_needed[:999]
|
||||
ids_needed = ids_needed[999:]
|
||||
|
||||
qmarks = ','.join('?' * len(id_batch))
|
||||
qmarks = '(%s)' % qmarks
|
||||
query = 'SELECT * FROM %s WHERE id IN %s' % (thing_class.table, qmarks)
|
||||
more_things = self.sql_select(query, id_batch)
|
||||
for thing_row in more_things:
|
||||
# Normally we would call `get_cached_instance` instead of
|
||||
# constructing here. But we already know for a fact that this
|
||||
# object is not in the cache because it made it past the
|
||||
# previous loop.
|
||||
thing = thing_class(self, db_row=thing_row)
|
||||
thing_cache[thing.id] = thing
|
||||
yield thing
|
||||
|
||||
def load_config(self):
|
||||
(config, needs_rewrite) = configlayers.load_file(
|
||||
filepath=self.config_filepath,
|
||||
|
@ -1637,34 +1671,6 @@ class PhotoDB(
|
|||
handle.write(json.dumps(self.config, indent=4, sort_keys=True))
|
||||
|
||||
|
||||
_THING_CLASSES = {
|
||||
'album':
|
||||
{
|
||||
'class': objects.Album,
|
||||
'exception': exceptions.NoSuchAlbum,
|
||||
},
|
||||
'bookmark':
|
||||
{
|
||||
'class': objects.Bookmark,
|
||||
'exception': exceptions.NoSuchBookmark,
|
||||
},
|
||||
'photo':
|
||||
{
|
||||
'class': objects.Photo,
|
||||
'exception': exceptions.NoSuchPhoto,
|
||||
},
|
||||
'tag':
|
||||
{
|
||||
'class': objects.Tag,
|
||||
'exception': exceptions.NoSuchTag,
|
||||
},
|
||||
'user':
|
||||
{
|
||||
'class': objects.User,
|
||||
'exception': exceptions.NoSuchUser,
|
||||
}
|
||||
}
|
||||
|
||||
if __name__ == '__main__':
|
||||
p = PhotoDB()
|
||||
print(p)
|
||||
|
|
Loading…
Reference in a new issue