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.
master
voussoir 2020-04-15 16:33:16 -07:00
parent 5dffe52830
commit 363d0bac67
1 changed files with 170 additions and 164 deletions

View File

@ -158,6 +158,175 @@ class PDBBookmarkMixin:
return bookmark 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: class PDBPhotoMixin:
def __init__(self): def __init__(self):
super().__init__() super().__init__()
@ -1300,6 +1469,7 @@ class PDBUtilMixin:
class PhotoDB( class PhotoDB(
PDBAlbumMixin, PDBAlbumMixin,
PDBBookmarkMixin, PDBBookmarkMixin,
PDBCacheManagerMixin,
PDBPhotoMixin, PDBPhotoMixin,
PDBSQLMixin, PDBSQLMixin,
PDBTagMixin, PDBTagMixin,
@ -1486,142 +1656,6 @@ class PhotoDB(
self._cached_frozen_children = tag_export.flat_dict(self.get_tags()) self._cached_frozen_children = tag_export.flat_dict(self.get_tags())
return self._cached_frozen_children 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): def load_config(self):
(config, needs_rewrite) = configlayers.load_file( (config, needs_rewrite) = configlayers.load_file(
filepath=self.config_filepath, filepath=self.config_filepath,
@ -1637,34 +1671,6 @@ class PhotoDB(
handle.write(json.dumps(self.config, indent=4, sort_keys=True)) 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__': if __name__ == '__main__':
p = PhotoDB() p = PhotoDB()
print(p) print(p)