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:
|
if extra is not None:
|
||||||
cache[field + '_index'] = val
|
cache[field + '_index'] = val
|
||||||
|
|
||||||
def get_user_metadata(self, field, make_copy=False):
|
# Replacements (overrides) for methods in the Metadata base class.
|
||||||
um = ga(self, '_user_metadata')
|
# ProxyMetadata cannot set attributes.
|
||||||
try:
|
|
||||||
ans = um[field]
|
def _unimplemented_exception(self, method, add_txt):
|
||||||
except KeyError:
|
raise NotImplementedError(f"{method}() cannot be used in this context. "
|
||||||
pass
|
"{'ProxyMetadata is read only' if add_txt else ''}")
|
||||||
else:
|
|
||||||
if make_copy:
|
# Metadata returns a seemingly arbitrary set of items. Rather than attempt
|
||||||
ans = deepcopy(ans)
|
# compatibility, flag __iter__ as unimplemented. This won't break anything
|
||||||
return ans
|
# 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):
|
def get_extra(self, field, default=None):
|
||||||
um = ga(self, '_user_metadata')
|
um = ga(self, '_user_metadata')
|
||||||
@ -393,10 +407,37 @@ class ProxyMetadata(Metadata):
|
|||||||
raise AttributeError(
|
raise AttributeError(
|
||||||
'Metadata object has no attribute named: '+ repr(field))
|
'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):
|
def custom_field_keys(self):
|
||||||
um = ga(self, '_user_metadata')
|
um = ga(self, '_user_metadata')
|
||||||
return iter(um.custom_field_keys())
|
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):
|
def get_standard_metadata(self, field, make_copy=False):
|
||||||
field_metadata = ga(self, '_user_metadata')
|
field_metadata = ga(self, '_user_metadata')
|
||||||
if field in field_metadata and field_metadata[field]['kind'] == 'field':
|
if field in field_metadata and field_metadata[field]['kind'] == 'field':
|
||||||
@ -405,9 +446,47 @@ class ProxyMetadata(Metadata):
|
|||||||
return field_metadata[field]
|
return field_metadata[field]
|
||||||
return None
|
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')
|
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
|
@property
|
||||||
def _proxy_metadata(self):
|
def _proxy_metadata(self):
|
||||||
|
@ -649,6 +649,65 @@ class ReadingTest(BaseTest):
|
|||||||
mi, pmi = cache.get_metadata(1), cache.get_proxy_metadata(1)
|
mi, pmi = cache.get_metadata(1), cache.get_proxy_metadata(1)
|
||||||
self.assertEqual(mi.get('#comp1'), pmi.get('#comp1'))
|
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): # {{{
|
def test_marked_field(self): # {{{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user