Move dynamic method generation to class instead of object level

This commit is contained in:
Kovid Goyal 2013-07-14 09:43:18 +05:30
parent 9c64054826
commit 867f46db2b
3 changed files with 138 additions and 104 deletions

View File

@ -117,4 +117,5 @@ Various things that require other things before they can be migrated:
5. In the new API refresh() does not re-read from disk. That might break a
few things, for example content server reloading on db change as well as
dump/restore of db?
6. grep the sources for TODO
'''

View File

@ -7,7 +7,6 @@ __license__ = 'GPL v3'
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
import os, traceback, types
from functools import partial
from future_builtins import zip
from calibre import force_unicode
@ -52,110 +51,8 @@ class LibraryDatabase(object):
self.get_property = self.data.get_property
MT = lambda func: types.MethodType(func, self, LibraryDatabase)
for prop in (
'author_sort', 'authors', 'comment', 'comments', 'publisher',
'rating', 'series', 'series_index', 'tags', 'title', 'title_sort',
'timestamp', 'uuid', 'pubdate', 'ondevice',
'metadata_last_modified', 'languages',
):
fm = {'comment':'comments', 'metadata_last_modified':
'last_modified', 'title_sort':'sort'}.get(prop, prop)
setattr(self, prop, partial(self.get_property,
loc=self.FIELD_MAP[fm]))
self.has_cover = MT(lambda self, book_id:self.new_api.field_for('cover', book_id))
self.get_identifiers = MT(
lambda self, index, index_is_id=False: self.new_api.field_for('identifiers', index if index_is_id else self.data.index_to_id(index)))
for meth in ('get_next_series_num_for', 'has_book', 'author_sort_from_authors'):
setattr(self, meth, getattr(self.new_api, meth))
# Legacy API to get information about many-(one, many) fields
for field in ('authors', 'tags', 'publisher', 'series'):
def getter(field):
def func(self):
return self.new_api.all_field_names(field)
return func
name = field[:-1] if field in {'authors', 'tags'} else field
setattr(self, 'all_%s_names' % name, MT(getter(field)))
self.all_formats = MT(lambda self:self.new_api.all_field_names('formats'))
for func, field in {'all_authors':'authors', 'all_titles':'title', 'all_tags2':'tags', 'all_series':'series', 'all_publishers':'publisher'}.iteritems():
setattr(self, func, partial(self.field_id_map, field))
self.all_tags = MT(lambda self: list(self.all_tag_names()))
self.get_authors_with_ids = MT(
lambda self: [[aid, adata['name'], adata['sort'], adata['link']] for aid, adata in self.new_api.author_data().iteritems()])
for field in ('tags', 'series', 'publishers', 'ratings', 'languages'):
def getter(field):
fname = field[:-1] if field in {'publishers', 'ratings'} else field
def func(self):
return [[tid, tag] for tid, tag in self.new_api.get_id_map(fname).iteritems()]
return func
setattr(self, 'get_%s_with_ids' % field,
MT(getter(field)))
for field in ('author', 'tag', 'series'):
def getter(field):
field = field if field == 'series' else (field+'s')
def func(self, item_id):
return self.new_api.get_item_name(field, item_id)
return func
setattr(self, '%s_name' % field, MT(getter(field)))
for field in ('publisher', 'series', 'tag'):
def getter(field):
fname = 'tags' if field == 'tag' else field
def func(self, item_id):
self.new_api.remove_items(fname, (item_id,))
return func
setattr(self, 'delete_%s_using_id' % field, MT(getter(field)))
# Legacy field API
for func in (
'standard_field_keys', 'custom_field_keys', 'all_field_keys',
'searchable_fields', 'sortable_field_keys',
'search_term_to_field_key', 'custom_field_metadata',
'all_metadata'):
setattr(self, func, getattr(self.field_metadata, func))
self.metadata_for_field = self.field_metadata.get
# Legacy setter API
for field in (
'!authors', 'author_sort', 'comment', 'has_cover', 'identifiers', 'languages',
'pubdate', '!publisher', 'rating', '!series', 'series_index', 'timestamp', 'uuid',
):
def setter(field):
has_case_change = field.startswith('!')
field = {'comment':'comments',}.get(field, field)
if has_case_change:
field = field[1:]
acc = field == 'series'
def func(self, book_id, val, notify=True, commit=True, allow_case_change=acc):
ret = self.new_api.set_field(field, {book_id:val}, allow_case_change=allow_case_change)
if notify:
self.notify([book_id])
return ret
elif field == 'has_cover':
def func(self, book_id, val):
self.new_api.set_field('cover', {book_id:bool(val)})
else:
def func(self, book_id, val, notify=True, commit=True):
if not val and field == 'uuid':
return
ret = self.new_api.set_field(field, {book_id:val})
if notify:
self.notify([book_id])
return ret if field == 'languages' else None
return func
setattr(self, 'set_%s' % field.replace('!', ''), MT(setter(field)))
self.last_update_check = self.last_modified()
self.book_on_device_func = None
# Cleaning is not required anymore
self.clean = self.clean_custom = MT(lambda self:None)
self.clean_standard_field = MT(lambda self, field, commit=False:None)
# apsw operates in autocommit mode
self.commit = MT(lambda self:None)
def close(self):
self.backend.close()
@ -456,3 +353,139 @@ class LibraryDatabase(object):
# }}}
MT = lambda func: types.MethodType(func, None, LibraryDatabase)
# Legacy getter API {{{
for prop in ('author_sort', 'authors', 'comment', 'comments', 'publisher',
'rating', 'series', 'series_index', 'tags', 'title', 'title_sort',
'timestamp', 'uuid', 'pubdate', 'ondevice', 'metadata_last_modified', 'languages',):
def getter(prop):
fm = {'comment':'comments', 'metadata_last_modified':
'last_modified', 'title_sort':'sort'}.get(prop, prop)
def func(self, index, index_is_id=False):
return self.get_property(index, index_is_id=index_is_id, loc=self.FIELD_MAP[fm])
return func
setattr(LibraryDatabase, prop, MT(getter(prop)))
LibraryDatabase.has_cover = MT(lambda self, book_id:self.new_api.field_for('cover', book_id))
LibraryDatabase.get_identifiers = MT(
lambda self, index, index_is_id=False: self.new_api.field_for('identifiers', index if index_is_id else self.data.index_to_id(index)))
# }}}
# Legacy setter API {{{
for field in (
'!authors', 'author_sort', 'comment', 'has_cover', 'identifiers', 'languages',
'pubdate', '!publisher', 'rating', '!series', 'series_index', 'timestamp', 'uuid',
):
def setter(field):
has_case_change = field.startswith('!')
field = {'comment':'comments',}.get(field, field)
if has_case_change:
field = field[1:]
acc = field == 'series'
def func(self, book_id, val, notify=True, commit=True, allow_case_change=acc):
ret = self.new_api.set_field(field, {book_id:val}, allow_case_change=allow_case_change)
if notify:
self.notify([book_id])
return ret
elif field == 'has_cover':
def func(self, book_id, val):
self.new_api.set_field('cover', {book_id:bool(val)})
else:
def func(self, book_id, val, notify=True, commit=True):
if not val and field == 'uuid':
return
ret = self.new_api.set_field(field, {book_id:val})
if notify:
self.notify([book_id])
return ret if field == 'languages' else None
return func
setattr(LibraryDatabase, 'set_%s' % field.replace('!', ''), MT(setter(field)))
# }}}
# Legacy API to get information about many-(one, many) fields {{{
for field in ('authors', 'tags', 'publisher', 'series'):
def getter(field):
def func(self):
return self.new_api.all_field_names(field)
return func
name = field[:-1] if field in {'authors', 'tags'} else field
setattr(LibraryDatabase, 'all_%s_names' % name, MT(getter(field)))
LibraryDatabase.all_formats = MT(lambda self:self.new_api.all_field_names('formats'))
for func, field in {'all_authors':'authors', 'all_titles':'title', 'all_tags2':'tags', 'all_series':'series', 'all_publishers':'publisher'}.iteritems():
def getter(field):
def func(self):
return self.field_id_map(field)
return func
setattr(LibraryDatabase, func, MT(getter(field)))
LibraryDatabase.all_tags = MT(lambda self: list(self.all_tag_names()))
LibraryDatabase.get_authors_with_ids = MT(
lambda self: [[aid, adata['name'], adata['sort'], adata['link']] for aid, adata in self.new_api.author_data().iteritems()])
for field in ('tags', 'series', 'publishers', 'ratings', 'languages'):
def getter(field):
fname = field[:-1] if field in {'publishers', 'ratings'} else field
def func(self):
return [[tid, tag] for tid, tag in self.new_api.get_id_map(fname).iteritems()]
return func
setattr(LibraryDatabase, 'get_%s_with_ids' % field, MT(getter(field)))
for field in ('author', 'tag', 'series'):
def getter(field):
field = field if field == 'series' else (field+'s')
def func(self, item_id):
return self.new_api.get_item_name(field, item_id)
return func
setattr(LibraryDatabase, '%s_name' % field, MT(getter(field)))
for field in ('publisher', 'series', 'tag'):
def getter(field):
fname = 'tags' if field == 'tag' else field
def func(self, item_id):
self.new_api.remove_items(fname, (item_id,))
return func
setattr(LibraryDatabase, 'delete_%s_using_id' % field, MT(getter(field)))
# }}}
# Legacy field API {{{
for func in (
'standard_field_keys', '!custom_field_keys', 'all_field_keys',
'searchable_fields', 'sortable_field_keys',
'search_term_to_field_key', '!custom_field_metadata',
'all_metadata'):
def getter(func):
if func.startswith('!'):
func = func[1:]
def meth(self, include_composites=True):
return getattr(self.field_metadata, func)(include_composites=include_composites)
elif func == 'search_term_to_field_key':
def meth(self, term):
return self.field_metadata.search_term_to_field_key(term)
else:
def meth(self):
return getattr(self.field_metadata, func)()
return meth
setattr(LibraryDatabase, func.replace('!', ''), MT(getter(func)))
LibraryDatabase.metadata_for_field = MT(lambda self, field:self.field_metadata.get(field))
# }}}
# Miscellaneous API {{{
for meth in ('get_next_series_num_for', 'has_book', 'author_sort_from_authors'):
def getter(meth):
def func(self, x):
return getattr(self.new_api, meth)(x)
return func
setattr(LibraryDatabase, meth, MT(getter(meth)))
# Cleaning is not required anymore
LibraryDatabase.clean = LibraryDatabase.clean_custom = MT(lambda self:None)
LibraryDatabase.clean_standard_field = MT(lambda self, field, commit=False:None)
# apsw operates in autocommit mode
LibraryDatabase.commit = MT(lambda self:None)
# }}}
del MT

View File

@ -334,7 +334,7 @@ class LegacyTest(BaseTest):
'windows_check_if_files_in_use',
}
SKIP_ARGSPEC = {
'__init__', 'get_next_series_num_for', 'has_book', 'author_sort_from_authors',
'__init__',
}
missing = []