mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Add Metadata methods to ProxyMetadata when reasonable to avoid AttributeError exceptions.
Add tests for the new methods.
This commit is contained in:
parent
fedafa4978
commit
fe5a546367
@ -372,16 +372,30 @@ class ProxyMetadata(Metadata):
|
||||
if extra is not None:
|
||||
cache[field + '_index'] = val
|
||||
|
||||
def get_user_metadata(self, field, make_copy=False):
|
||||
um = ga(self, '_user_metadata')
|
||||
try:
|
||||
ans = um[field]
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
if make_copy:
|
||||
ans = deepcopy(ans)
|
||||
return ans
|
||||
# Replacements (overrides) for methods in the Metadata base class.
|
||||
# ProxyMetadata cannot set attributes.
|
||||
|
||||
def _unimplemented_exception(self, method, add_txt):
|
||||
raise NotImplementedError(f"{method}() cannot be used in this context. "
|
||||
"{'ProxyMetadata is read only' if add_txt else ''}")
|
||||
|
||||
# Metadata returns a seemingly arbitrary set of items. Rather than attempt
|
||||
# compatibility, flag __iter__ as unimplemented. This won't break anything
|
||||
# because the Metadata version raises AttributeError
|
||||
def __iter__(self):
|
||||
raise NotImplementedError(f"__iter__() cannot be used in this context. "
|
||||
"Use the explicit methods such as all_field_keys()")
|
||||
|
||||
def has_key(self, key):
|
||||
return key in self.all_field_keys()
|
||||
|
||||
def deepcopy(self, **kwargs):
|
||||
self._unimplemented_exception('deepcopy', add_txt=False)
|
||||
|
||||
def deepcopy_metadata(self):
|
||||
return deepcopy(ga('_user_metadata'))
|
||||
|
||||
# def get(self, field, default=None)
|
||||
|
||||
def get_extra(self, field, default=None):
|
||||
um = ga(self, '_user_metadata')
|
||||
@ -393,10 +407,37 @@ class ProxyMetadata(Metadata):
|
||||
raise AttributeError(
|
||||
'Metadata object has no attribute named: '+ repr(field))
|
||||
|
||||
def set(self, *args, **kwargs):
|
||||
self._unimplemented_exception('set', add_txt=True)
|
||||
|
||||
def get_identifiers(self):
|
||||
res = self.get('identifiers')
|
||||
return {} if res is None else res
|
||||
|
||||
def set_identifiers(self, *args):
|
||||
self._unimplemented_exception('set_identifiers', add_txt=True)
|
||||
|
||||
def set_identifier(self, *args):
|
||||
self._unimplemented_exception('set_identifier', add_txt=True)
|
||||
|
||||
def has_identifier(self, typ):
|
||||
return typ in self.get('identifiers', {})
|
||||
|
||||
# def standard_field_keys(self)
|
||||
|
||||
def custom_field_keys(self):
|
||||
um = ga(self, '_user_metadata')
|
||||
return iter(um.custom_field_keys())
|
||||
|
||||
def all_field_keys(self):
|
||||
um = ga(self, '_user_metadata')
|
||||
return ALL_METADATA_FIELDS.union(frozenset(um.all_field_keys()))
|
||||
|
||||
def all_non_none_fields(self):
|
||||
self._unimplemented_exception('all_non_none_fields', add_txt=False)
|
||||
|
||||
# This version can return custom column metadata while the Metadata version
|
||||
# won't.
|
||||
def get_standard_metadata(self, field, make_copy=False):
|
||||
field_metadata = ga(self, '_user_metadata')
|
||||
if field in field_metadata and field_metadata[field]['kind'] == 'field':
|
||||
@ -405,9 +446,47 @@ class ProxyMetadata(Metadata):
|
||||
return field_metadata[field]
|
||||
return None
|
||||
|
||||
def all_field_keys(self):
|
||||
# def get_all_standard_metadata(self, make_copy)
|
||||
|
||||
def get_all_user_metadata(self, make_copy):
|
||||
um = ga(self, '_user_metadata')
|
||||
return frozenset(ALL_METADATA_FIELDS.union(frozenset(um)))
|
||||
if make_copy:
|
||||
res = {k: deepcopy(um[k]) for k in um.custom_field_keys()}
|
||||
else:
|
||||
res = {k: um[k] for k in um.custom_field_keys()}
|
||||
return res
|
||||
|
||||
# The Metadata version of this method works only with custom field keys. It
|
||||
# isn't clear how this method differs from get_standard_metadata other than
|
||||
# it will return non-'field' metadata. Leave it in case someone depends on
|
||||
# that.
|
||||
def get_user_metadata(self, field, make_copy=False):
|
||||
um = ga(self, '_user_metadata')
|
||||
try:
|
||||
ans = um[field]
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
if make_copy:
|
||||
ans = deepcopy(ans)
|
||||
return ans
|
||||
|
||||
def set_all_user_metadata(self, *args):
|
||||
self._unimplemented_exception('set_all_user_metadata', add_txt=True)
|
||||
|
||||
def set_user_metadata(self, *args):
|
||||
self._unimplemented_exception('set_user_metadata', add_txt=True)
|
||||
|
||||
def remove_stale_user_metadata(self, *args):
|
||||
self._unimplemented_exception('remove_stale_user_metadata', add_txt=True)
|
||||
|
||||
def template_to_attribute(self, *args):
|
||||
self._unimplemented_exception('template_to_attribute', add_txt=True)
|
||||
|
||||
def smart_update(self, *args, **kwargs):
|
||||
self._unimplemented_exception('smart_update', add_txt=True)
|
||||
|
||||
# The rest of the methods in Metadata can be used as is.
|
||||
|
||||
@property
|
||||
def _proxy_metadata(self):
|
||||
|
@ -649,6 +649,65 @@ class ReadingTest(BaseTest):
|
||||
mi, pmi = cache.get_metadata(1), cache.get_proxy_metadata(1)
|
||||
self.assertEqual(mi.get('#comp1'), pmi.get('#comp1'))
|
||||
|
||||
# Test overridden Metadata methods
|
||||
|
||||
self.assertTrue(pmi.has_key('tags') == mi.has_key('tags'))
|
||||
|
||||
self.assertFalse(pmi.has_key('taggs'), 'taggs attribute')
|
||||
self.assertTrue(pmi.has_key('taggs') == mi.has_key('taggs'))
|
||||
|
||||
self.assertSetEqual(set(pmi.custom_field_keys()), set(mi.custom_field_keys()))
|
||||
|
||||
self.assertEqual(pmi.get_extra('#series', 0), 3)
|
||||
self.assertEqual(pmi.get_extra('#series', 0), mi.get_extra('#series', 0))
|
||||
|
||||
self.assertDictEqual(pmi.get_identifiers(), {'test': 'two'})
|
||||
self.assertDictEqual(pmi.get_identifiers(), mi.get_identifiers())
|
||||
|
||||
self.assertTrue(pmi.has_identifier('test'))
|
||||
self.assertTrue(pmi.has_identifier('test') == mi.has_identifier('test'))
|
||||
|
||||
self.assertListEqual(list(pmi.custom_field_keys()), list(mi.custom_field_keys()))
|
||||
|
||||
# ProxyMetadata has the virtual fields while Metadata does not.
|
||||
self.assertSetEqual(set(pmi.all_field_keys())-{'id', 'series_sort', 'path',
|
||||
'in_tag_browser', 'sort', 'ondevice',
|
||||
'au_map', 'marked', '#series_index'},
|
||||
set(mi.all_field_keys()))
|
||||
|
||||
# mi.get_standard_metadata() doesn't include the rec_index metadata key
|
||||
fm_pmi = pmi.get_standard_metadata('series')
|
||||
fm_pmi.pop('rec_index')
|
||||
self.assertDictEqual(fm_pmi, mi.get_standard_metadata('series', make_copy=False))
|
||||
|
||||
# The ProxyMetadata versions don't include the values. Note that the mi
|
||||
# version of get_standard_metadata won't return custom columns while the
|
||||
# ProxyMetadata version will
|
||||
fm_mi = mi.get_user_metadata('#series', make_copy=False)
|
||||
fm_mi.pop('#extra#')
|
||||
fm_mi.pop('#value#')
|
||||
self.assertDictEqual(pmi.get_standard_metadata('#series'), fm_mi)
|
||||
self.assertDictEqual(pmi.get_user_metadata('#series'), fm_mi)
|
||||
|
||||
fm_mi = mi.get_all_user_metadata(make_copy=False)
|
||||
for one in fm_mi:
|
||||
fm_mi[one].pop('#extra#', None)
|
||||
fm_mi[one].pop('#value#', None)
|
||||
self.assertDictEqual(pmi.get_all_user_metadata(make_copy=False), fm_mi)
|
||||
|
||||
# Check the unimplemented methods
|
||||
self.assertRaises(NotImplementedError, lambda: 'foo' in pmi)
|
||||
self.assertRaises(NotImplementedError, pmi.set, 'a', 'a')
|
||||
self.assertRaises(NotImplementedError, pmi.set_identifiers, 'a', 'a')
|
||||
self.assertRaises(NotImplementedError, pmi.set_identifier, 'a', 'a')
|
||||
self.assertRaises(NotImplementedError, pmi.all_non_none_fields)
|
||||
self.assertRaises(NotImplementedError, pmi.set_all_user_metadata, {})
|
||||
self.assertRaises(NotImplementedError, pmi.set_user_metadata, 'a', {})
|
||||
self.assertRaises(NotImplementedError, pmi.remove_stale_user_metadata, {})
|
||||
self.assertRaises(NotImplementedError, pmi.template_to_attribute, {}, {})
|
||||
self.assertRaises(NotImplementedError, pmi.smart_update, {})
|
||||
|
||||
|
||||
# }}}
|
||||
|
||||
def test_marked_field(self): # {{{
|
||||
|
Loading…
x
Reference in New Issue
Block a user