diff --git a/src/tufup/common.py b/src/tufup/common.py index 8b182e1..edb7332 100644 --- a/src/tufup/common.py +++ b/src/tufup/common.py @@ -21,8 +21,8 @@ class CustomMetadataDict(TypedDict): used by tufup internally """ - user: Optional[dict] - tufup: Optional[dict] + user: dict + tufup: dict def _immutable(value): @@ -79,19 +79,19 @@ def __init__( logger.critical( f'invalid filename "{self.filename}": whitespace not allowed' ) - self._custom = custom + self._custom = custom or dict(user=dict(), tufup=dict()) @property - def custom(self) -> Optional[dict]: - """returns user-specified custom metadata""" + def custom(self) -> dict: + """returns user-specified custom metadata dict""" return self._get_custom_metadata('user') @property - def custom_internal(self) -> Optional[dict]: - """returns tufup-internal custom metadata""" + def custom_internal(self) -> dict: + """returns tufup-internal custom metadata dict""" return self._get_custom_metadata('tufup') - def _get_custom_metadata(self, key: str) -> Optional[dict]: + def _get_custom_metadata(self, _key: str) -> dict: """ get custom metadata in a backward-compatible manner (older versions did not distinguish between user-specified and internal metadata) @@ -100,7 +100,8 @@ def _get_custom_metadata(self, key: str) -> Optional[dict]: # check dict keys for backward compatibility if get_type_hints(CustomMetadataDict).keys() != self._custom.keys(): return self._custom - return self._custom.get(key) + return self._custom.get(_key) + return dict() def __str__(self): return str(self.target_path_str) diff --git a/tests/test_common.py b/tests/test_common.py index 264ab3e..34f61c5 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -24,6 +24,7 @@ def test_immutable(self): bytearray(b'b'), dict(a=1), [dict(a=[dict(c={1})], b=bytearray(b'd'))], + dict(a=dict(b=dict(c=dict()))), ] for case in cases: with self.subTest(msg=case): @@ -66,7 +67,16 @@ def test_str(self): def test_hashable(self): obj = TargetMeta() - self.assertEqual(obj.__hash__(), hash(tuple(vars(obj).items()))) + try: + obj.__hash__() + except Exception as e: + self.fail(f'__hash__ failed unexpectedly: {e}') + expected_obj_hashable = ( + ('target_path_str', 'None-None.tar.gz'), + ('path', pathlib.Path('None-None.tar.gz')), + ('_custom', (('user', ()), ('tufup', ()))), + ) + self.assertEqual(hash(expected_obj_hashable), obj.__hash__()) # we can use the obj as a set member or as dict key self.assertEqual({obj, obj}, {obj}) @@ -171,8 +181,11 @@ def test_custom_metadata_backward_compatibility(self): def test_custom_metadata_not_specified(self): target_meta = TargetMeta() - self.assertIsNone(target_meta.custom) - self.assertIsNone(target_meta.custom_internal) + self.assertIsInstance(target_meta.custom, dict) + self.assertIsInstance(target_meta.custom_internal, dict) + # user overrides _custom attr + target_meta._custom = None + self.assertIsInstance(target_meta.custom, dict) class PatcherTests(TempDirTestCase):