From c584def5bb7145805cd93db25fec1082af4f7031 Mon Sep 17 00:00:00 2001 From: Simeon Warner Date: Thu, 12 Dec 2024 12:10:57 -0500 Subject: [PATCH 01/10] Testing and checks in layout_0003 --- ocfl/layout.py | 18 +++++ ocfl/layout_0003_hash_and_id_n_tuple.py | 23 ++++++- tests/test_layout_0003-hash-and-id-n-tuple.py | 66 +++++++++++++++++++ 3 files changed, 106 insertions(+), 1 deletion(-) diff --git a/ocfl/layout.py b/ocfl/layout.py index 500be49..e1631fc 100644 --- a/ocfl/layout.py +++ b/ocfl/layout.py @@ -71,6 +71,15 @@ def config(self): """ return None + def check_full_config(self): + """Check full configuration in instance variables. + + Trivial implementation that does nothing. It is intended that + sub-classes will override to do real checks if necessary. No + return value, raise a LayoutException on error. + """ + return + def strip_root(self, path, root): """Remove root from path, throw exception on failure.""" root = root.rstrip(os.sep) # ditch any trailing path separator @@ -130,8 +139,15 @@ def check_and_set_layout_params(self, config, require_extension_name=True): require_extension_name: boolean, True by default. If set False then the extensionName paramater is not required + Raises: + LayoutException: if the extensionName is missig from the config, if + support for the named extension isn't implemented, or if there + is an error in the parameters or full configuration. + For each parameter that is recognized, the appropriate check and set method in self.PARAMS is called. The methods set instance attributes. + Finally, the check_full_config method is called to check anything that + might required all of the configuration to be known. """ # Check the extensionName if required and/or specified if "extensionName" not in config: @@ -142,6 +158,8 @@ def check_and_set_layout_params(self, config, require_extension_name=True): # Read and check the parameters (ignore any extra params) for key, method in self.PARAMS.items(): method(config.get(key)) + # Finally, check full config + self.check_full_config() def write_layout_params(self, root_fs=None): """Write the config.json file with layout parameters if need for this layout. diff --git a/ocfl/layout_0003_hash_and_id_n_tuple.py b/ocfl/layout_0003_hash_and_id_n_tuple.py index 7231f89..7bf634f 100644 --- a/ocfl/layout_0003_hash_and_id_n_tuple.py +++ b/ocfl/layout_0003_hash_and_id_n_tuple.py @@ -76,7 +76,7 @@ def check_digest_algorithm(self, value): if value is None: raise LayoutException("digestAlgorithm parameter must be specified") try: - string_digest("aa", digest_type=value) + string_digest("dummy_data", digest_type=value) except ValueError as e: raise LayoutException("digestAlgorithm parameter specifies unknown or unsupported digests %s (%s)" % (value, str(e))) self.digest_algorithm = value @@ -114,6 +114,27 @@ def check_number_of_tuples(self, value): raise LayoutException("numberOfTuples parameter must be aninteger between 0 and 32 inclusive") self.number_of_tuples = value + def check_full_config(self): + """Check combined configuration parameters. + + From extension: + If tupleSize is set to 0, then no tuples are created and numberOfTuples + MUST also equal 0. + The product of tupleSize and numberOfTuples MUST be less than or equal + to the number of characters in the hex encoded digest. + + Raises: + LayoutException: in the case that there is an error. + """ + # Both zero if one zero + if ((self.tuple_size == 0 and self.number_of_tuples != 0) + or (self.tuple_size != 0 and self.number_of_tuples == 0)): + raise LayoutException("Bad layout configuration: If tupleSize is set to 0, then numberOfTuples MUST also equal 0.") + # Enough chars in digest + n = len(string_digest("dummy_data", digest_type=self.digest_algorithm)) + if self.tuple_size * self.number_of_tuples > n: + raise LayoutException("Bad layout configuration: The product of tupleSize and numberOfTuples MUST be less than or equal to the number of characters in the hex encoded digest.") + @property def config(self): """Dictionary with config.json configuration for the layout extenstion.""" diff --git a/tests/test_layout_0003-hash-and-id-n-tuple.py b/tests/test_layout_0003-hash-and-id-n-tuple.py index 12d5466..d99ade0 100644 --- a/tests/test_layout_0003-hash-and-id-n-tuple.py +++ b/tests/test_layout_0003-hash-and-id-n-tuple.py @@ -1,5 +1,6 @@ """0003: Hashed Truncated N-tuple Trees with Object ID layout tests.""" import unittest +from ocfl.layout import LayoutException from ocfl.layout_0003_hash_and_id_n_tuple import Layout_0003_Hash_And_Id_N_Tuple, _percent_encode, _id_to_path @@ -38,6 +39,71 @@ def test__id_to_path(self): self.assertEqual(_id_to_path(identifier=long_object_id_101, digest_algorithm="sha256", tuple_size=3, number_of_tuples=3), f"5cc/73e/648/abcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghij-{long_object_id_101_digest}") + def test_check_digest_algorithm(self): + """Test check_digest_algorithm method.""" + layout = Layout_0003_Hash_And_Id_N_Tuple() + self.assertRaises(LayoutException, layout.check_digest_algorithm, None) + self.assertRaises(LayoutException, layout.check_digest_algorithm, "no-a-digest-algorithm") + self.assertRaises(LayoutException, layout.check_digest_algorithm, "") # stil not! + self.assertEqual(layout.check_digest_algorithm("md5"), None) + self.assertEqual(layout.digest_algorithm, "md5") + + def test_check_tuple_size(self): + """Test check_tuple_size method.""" + layout = Layout_0003_Hash_And_Id_N_Tuple() + self.assertRaises(LayoutException, layout.check_tuple_size, None) + self.assertRaises(LayoutException, layout.check_tuple_size, "string-not-num") + self.assertRaises(LayoutException, layout.check_tuple_size, -1) + self.assertRaises(LayoutException, layout.check_tuple_size, 33) + self.assertEqual(layout.check_tuple_size(0), None) + self.assertEqual(layout.tuple_size, 0) + self.assertEqual(layout.check_tuple_size(32), None) + self.assertEqual(layout.tuple_size, 32) + + def test_check_number_of_tuples(self): + """Test check_number_of_tuples method.""" + layout = Layout_0003_Hash_And_Id_N_Tuple() + self.assertRaises(LayoutException, layout.check_number_of_tuples, None) + self.assertRaises(LayoutException, layout.check_number_of_tuples, "string-not-num") + self.assertRaises(LayoutException, layout.check_number_of_tuples, -1) + self.assertRaises(LayoutException, layout.check_number_of_tuples, 33) + self.assertEqual(layout.check_number_of_tuples(0), None) + self.assertEqual(layout.number_of_tuples, 0) + self.assertEqual(layout.check_number_of_tuples(32), None) + self.assertEqual(layout.number_of_tuples, 32) + + def test_check_full_config(self): + """Test check_full_config method.""" + layout = Layout_0003_Hash_And_Id_N_Tuple() + layout.check_and_set_layout_params(config={"digestAlgorithm": "md5", + "tupleSize": 0, + "numberOfTuples": 0}, + require_extension_name=False) + self.assertRaises(LayoutException, + layout.check_and_set_layout_params, + config={"digestAlgorithm": "md5", + "tupleSize": 3, + "numberOfTuples": 0}, + require_extension_name=False) + # An md5 digest is 32 hex digits long + layout.check_and_set_layout_params(config={"digestAlgorithm": "md5", + "tupleSize": 4, + "numberOfTuples": 8}, + require_extension_name=False) + self.assertRaises(LayoutException, + layout.check_and_set_layout_params, + config={"digestAlgorithm": "md5", + "tupleSize": 4, + "numberOfTuples": 9}, + require_extension_name=False) + + + def test_config(self): + """Test config property.""" + layout = Layout_0003_Hash_And_Id_N_Tuple() + self.assertIn("extensionName", layout.config) + self.assertIn("tupleSize", layout.config) + def test_name(self): """Test canonical name.""" self.assertEqual(Layout_0003_Hash_And_Id_N_Tuple().NAME, "0003-hash-and-id-n-tuple-storage-layout") From 0b0063d17c5a5eb2cb73c6e2e98ddc942ed96ae6 Mon Sep 17 00:00:00 2001 From: Simeon Warner Date: Thu, 12 Dec 2024 12:31:43 -0500 Subject: [PATCH 02/10] Tidy layout_0002 --- ocfl/layout.py | 84 ++++++++++++++++++++++++++------- ocfl/layout_0002_flat_direct.py | 15 +++++- 2 files changed, 81 insertions(+), 18 deletions(-) diff --git a/ocfl/layout.py b/ocfl/layout.py index e1631fc..609af05 100644 --- a/ocfl/layout.py +++ b/ocfl/layout.py @@ -65,9 +65,9 @@ def config_file(self): def config(self): """Dictionary with config.json configuration for the layout extenstion. - Returns a dict with values based on the current attributes (to be - serialized with json.dump()), else None indicates that there is no - config.json this layout. + Dict values are based on the current attributes (as would be serialized + with json.dump()), else None indicates that there is no config.json + this layout. """ return None @@ -81,40 +81,86 @@ def check_full_config(self): return def strip_root(self, path, root): - """Remove root from path, throw exception on failure.""" + """Remove root from path, throw exception on failure. + + Arguments: + path (str): file path from which root will be stripped + root (str): root path that will be stripped from path, also + any leading path separator is removed. + + Raises: + LayoutException: if the path is not within the given root and thus + root cannot be stripped from it + """ root = root.rstrip(os.sep) # ditch any trailing path separator if os.path.commonprefix((path, root)) == root: return os.path.relpath(path, start=root) raise LayoutException("Path %s is not in root %s" % (path, root)) def is_valid(self, identifier): # pylint: disable=unused-argument - """Return True if identifier is valid, always True in this base implementation.""" + """Check validity of identifier for this layout. + + Arguments: + identifier (str): identifier to check + + Returns: + bool: True if valid, False otherwise. Always True in this base + implementation. + """ return True def encode(self, identifier): - """Encode identifier to get rid of unsafe chars.""" + """Encode identifier to get rid of unsafe chars. + + Arguments: + identifier (str): identifier to encode + + Returns: + str: encoded identifier + """ return quote_plus(identifier) def decode(self, identifier): - """Decode identifier to put back unsafe chars.""" + """Decode identifier to put back unsafe chars. + + Arguments: + identifier (str): identifier to decode + + Returns: + str: decoded identifier + """ return unquote_plus(identifier) def identifier_to_path(self, identifier): - """Convert identifier to path relative to some root.""" + """Convert identifier to path relative to some root. + + Arguments: + identifier (str): identifier to encode + + Returns: + str: object path for this identifier + + Raises: + LayoutException: if the identifer cannot be used to create an object + path. In this base implementation, an exception is always raised. + The method should be overridded with the same signature + """ raise LayoutException("No yet implemented") def read_layout_params(self, root_fs=None, params_required=False): """Look for and read and layout configuration parameters. Arguments: - root_fs: the storage root fs object - params_required: if True then throw exception for params file not present + root_fs (str): the storage root fs object + params_required (bool): if True then throw exception for params file + not present - Returns None, sets instance data in accord with the configuration using - the methods in self.PARAMS to parse for each key. + Raises: + LayoutException: if the config can't be read or if required by + params_required but not present - Raises LayoutException if the config can't be read or if required by - params_required but not present. + Sets instance data in accord with the configuration using the methods + in self.PARAMS to parse for each key. """ config = None logging.debug("Reading extension config file %s", self.config_file) @@ -164,10 +210,14 @@ def check_and_set_layout_params(self, config, require_extension_name=True): def write_layout_params(self, root_fs=None): """Write the config.json file with layout parameters if need for this layout. - Does nothing if there is no config.json content defined for this layout. + Arguments: + root_fs (str): the storage root fs object - Raises a LayoutException if there is an error trying to write the config.json - file, including if one already exists. + Raises: + LayoutException: if there is an error trying to write the config.json + file, including if one already exists. + + Does nothing if there is no config.json content defined for this layout. """ config = self.config if config is None: diff --git a/ocfl/layout_0002_flat_direct.py b/ocfl/layout_0002_flat_direct.py index 646d97d..d764aa0 100644 --- a/ocfl/layout_0002_flat_direct.py +++ b/ocfl/layout_0002_flat_direct.py @@ -18,7 +18,20 @@ def __init__(self): self.PARAMS = None # No parameters def identifier_to_path(self, identifier): - """Convert identifier to path relative to root.""" + """Convert identifier to path relative to root. + + Argument: + identifier (str): object identifier + + Returns: + str: object path for this layout + + Raises: + LayoutException: if the identifier cannot be converted to a valid + object path. For the direct layout it is not allowd to have + identifiers that are blank, '.', '..' or include a filesystem + path separator + """ if identifier in ("", ".", "..") or os.sep in identifier: raise LayoutException("Identifier '%s' unsafe for %s layout" % (identifier, self.NAME)) return identifier From b4dc92dec407e894a81e978bc200c5c367618a4e Mon Sep 17 00:00:00 2001 From: Simeon Warner Date: Thu, 12 Dec 2024 12:31:57 -0500 Subject: [PATCH 03/10] Tidy layout_nnnn_flat_quoted --- ocfl/layout_nnnn_flat_quoted.py | 21 +++++++++++++++++++-- tests/test_layout_nnnn_flat_quoted.py | 9 +++++---- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/ocfl/layout_nnnn_flat_quoted.py b/ocfl/layout_nnnn_flat_quoted.py index 692cdc0..3b1c92a 100644 --- a/ocfl/layout_nnnn_flat_quoted.py +++ b/ocfl/layout_nnnn_flat_quoted.py @@ -1,5 +1,5 @@ """Layout_NNNN_Flat_Quoted mapping of identifier to directory structure.""" -from .layout import Layout +from .layout import Layout, LayoutException class Layout_NNNN_Flat_Quoted(Layout): @@ -13,5 +13,22 @@ def __init__(self): self.PARAMS = None # No parameters def identifier_to_path(self, identifier): - """Convert identifier to path relative to root.""" + """Convert identifier to path relative to root. + + Argument: + identifier (str): object identifier + + Returns: + str: object path for this layout + + Raises: + LayoutException: if the identifier cannot be converted to a valid + object path. Currently just a check for blank + + Uses Layout.encode() to generate a safe directory name from any + identifier. Length is not checked but could cause operating system + errors. + """ + if identifier == "": + raise LayoutException("Identifier '%s' unsafe for %s layout" % (identifier, self.NAME)) return self.encode(identifier) diff --git a/tests/test_layout_nnnn_flat_quoted.py b/tests/test_layout_nnnn_flat_quoted.py index 3a31009..3f74a18 100644 --- a/tests/test_layout_nnnn_flat_quoted.py +++ b/tests/test_layout_nnnn_flat_quoted.py @@ -1,5 +1,6 @@ """Layout_NNNN_Flat_Quoted layout tests.""" import unittest +from ocfl.layout import LayoutException from ocfl.layout_nnnn_flat_quoted import Layout_NNNN_Flat_Quoted @@ -8,7 +9,7 @@ class TestAll(unittest.TestCase): def test_identifier_to_path(self): """Test identifier_to_path.""" - d = Layout_NNNN_Flat_Quoted() - self.assertEqual(d.identifier_to_path(""), "") - self.assertEqual(d.identifier_to_path("abc"), "abc") - self.assertEqual(d.identifier_to_path("this n that"), "this+n+that") + layout = Layout_NNNN_Flat_Quoted() + self.assertRaises(LayoutException, layout.identifier_to_path, "") + self.assertEqual(layout.identifier_to_path("abc"), "abc") + self.assertEqual(layout.identifier_to_path("this n that"), "this+n+that") From 64c9c3c9c974dbb1ebb096e029dbe49a067d559e Mon Sep 17 00:00:00 2001 From: Simeon Warner Date: Thu, 12 Dec 2024 13:30:04 -0500 Subject: [PATCH 04/10] Tidy layout_nnnn_tuple_tree --- ocfl/layout_0003_hash_and_id_n_tuple.py | 24 ++++++++ ocfl/layout_nnnn_tuple_tree.py | 50 ++++++++++++++-- tests/test_layout_0003-hash-and-id-n-tuple.py | 1 - tests/test_layout_nnnn_tuple_tree.py | 60 ++++++++++++------- 4 files changed, 106 insertions(+), 29 deletions(-) diff --git a/ocfl/layout_0003_hash_and_id_n_tuple.py b/ocfl/layout_0003_hash_and_id_n_tuple.py index 7bf634f..8fdbd78 100644 --- a/ocfl/layout_0003_hash_and_id_n_tuple.py +++ b/ocfl/layout_0003_hash_and_id_n_tuple.py @@ -72,6 +72,14 @@ def check_digest_algorithm(self, value): Type: string Constraints: Must not be empty Default: sha256 + + Argument: + value (str): digest algorithm name + + Raises: + LayoutException: if the digest algorithm is not supported + + Sets the digest_algorithm property of this object as a side effect. """ if value is None: raise LayoutException("digestAlgorithm parameter must be specified") @@ -91,6 +99,14 @@ def check_tuple_size(self, value): Type: number Constraints: An integer between 0 and 32 inclusive Default: 3 + + Argument: + value (int): integer value for tuple size in characters + + Raises: + LayoutException: if the tuple size is not allowed + + Sets the tuple_size property of this object as a side effect. """ if value is None: raise LayoutException("tupleSize parameter must be specified") @@ -107,6 +123,14 @@ def check_number_of_tuples(self, value): Type: number Constraints: An integer between 0 and 32 inclusive Default: 3 + + Argument: + value (int): integer value for number of tuples + + Raises: + LayoutException: if the number of tuples is not allowed + + Sets the number_of_tuples property of this object as a side effect. """ if value is None: raise LayoutException("numberOfTuples parameter must be specified") diff --git a/ocfl/layout_nnnn_tuple_tree.py b/ocfl/layout_nnnn_tuple_tree.py index cfd6e06..1b26994 100644 --- a/ocfl/layout_nnnn_tuple_tree.py +++ b/ocfl/layout_nnnn_tuple_tree.py @@ -44,8 +44,16 @@ def check_tuple_size(self, value): Description: Indicates the size of the segments (in characters) that the digest is split into Type: number - Constraints: An integer between 0 and 32 inclusive - Default: 3 + Constraints: An integer between 2 and 6 inclusive + Default: 2 + + Argument: + value (int): integer value for tuple size in characters + + Raises: + LayoutException: if the tuple size is not allowed + + Sets the tuple_size property of this object as a side effect. """ if value is None: raise LayoutException("tupleSize parameter must be specified") @@ -54,15 +62,45 @@ def check_tuple_size(self, value): self.tuple_size = value def encode(self, identifier): - """Pairtree encode identifier.""" + """Pairtree encode identifier. + + Argument: + identifier (str): object identifier to encode + + Returns: + str: encoded identifier + """ return id_encode(identifier) def decode(self, identifier): - """Pairtree decode identifier.""" + """Pairtree decode identifier. + + Argument: + identifier (str): object identifier to decode + + Returns: + str: decoded identifier + """ return id_decode(identifier) def identifier_to_path(self, identifier): - """Convert identifier to path relative to root.""" + """Convert identifier to path relative to root. + + Argument: + identifier (str): object identifier + + Returns: + str: object path for this layout + + Raises: + LayoutException: if the identifier cannot be converted to a valid + object path. Currently just a check for blank + + Uses Layout.encode() to generate a safe directory name from any + identifier. + """ + if identifier == "": + raise LayoutException("Identifier '%s' unsafe for %s layout" % (identifier, self.NAME)) identifier = self.encode(identifier) id_remains = identifier segments = [] @@ -72,4 +110,4 @@ def identifier_to_path(self, identifier): segments.append(id_remains) # the statement means that segments will always have at least one element # Use full identifier to encapsulate segments.append(identifier) - return os.path.join(*segments) # pylint: disable=no-value-for-parameter + return os.path.join(*segments) diff --git a/tests/test_layout_0003-hash-and-id-n-tuple.py b/tests/test_layout_0003-hash-and-id-n-tuple.py index d99ade0..5cf1c36 100644 --- a/tests/test_layout_0003-hash-and-id-n-tuple.py +++ b/tests/test_layout_0003-hash-and-id-n-tuple.py @@ -97,7 +97,6 @@ def test_check_full_config(self): "numberOfTuples": 9}, require_extension_name=False) - def test_config(self): """Test config property.""" layout = Layout_0003_Hash_And_Id_N_Tuple() diff --git a/tests/test_layout_nnnn_tuple_tree.py b/tests/test_layout_nnnn_tuple_tree.py index 74c1d91..b3e9cd3 100644 --- a/tests/test_layout_nnnn_tuple_tree.py +++ b/tests/test_layout_nnnn_tuple_tree.py @@ -1,34 +1,50 @@ -"""Digest tests.""" +"""Layout_NNNN_Tuple_Tree layout tests.""" import unittest +from ocfl.layout import LayoutException from ocfl.layout_nnnn_tuple_tree import Layout_NNNN_Tuple_Tree class TestAll(unittest.TestCase): """TestAll class to run tests.""" - def test01_encode(self): + def test_encode(self): """Test encode.""" - tt = Layout_NNNN_Tuple_Tree() - self.assertEqual(tt.encode(""), "") - self.assertEqual(tt.encode("a"), "a") - self.assertEqual(tt.encode("a/b:?"), "a=b+^3f") + layout = Layout_NNNN_Tuple_Tree() + self.assertEqual(layout.encode(""), "") + self.assertEqual(layout.encode("a"), "a") + self.assertEqual(layout.encode("a/b:?"), "a=b+^3f") - def test02_decode(self): + def test_decode(self): """Test decode.""" - tt = Layout_NNNN_Tuple_Tree() - self.assertEqual(tt.decode(""), "") - self.assertEqual(tt.decode("a"), "a") - self.assertEqual(tt.decode("a=b+^3f"), "a/b:?") + layout = Layout_NNNN_Tuple_Tree() + self.assertEqual(layout.decode(""), "") + self.assertEqual(layout.decode("a"), "a") + self.assertEqual(layout.decode("a=b+^3f"), "a/b:?") - def test03_identifier_to_path(self): + def test_config(self): + """Test config property.""" + layout = Layout_NNNN_Tuple_Tree() + self.assertEqual(set(layout.config.keys()), set(("extensionName", "tupleSize"))) + + def test_check_tuple_size(self): + """Test check_tuple_size method.""" + layout = Layout_NNNN_Tuple_Tree() + self.assertRaises(LayoutException, layout.check_tuple_size, None) + self.assertRaises(LayoutException, layout.check_tuple_size, "string-not-num") + self.assertRaises(LayoutException, layout.check_tuple_size, 1) + self.assertRaises(LayoutException, layout.check_tuple_size, 7) + self.assertEqual(layout.check_tuple_size(4), None) + self.assertEqual(layout.tuple_size, 4) + + def test_identifier_to_path(self): """Test path creation.""" - tt = Layout_NNNN_Tuple_Tree(tuple_size=2) - self.assertEqual(tt.identifier_to_path(""), "") - self.assertEqual(tt.identifier_to_path("a"), "a/a") - self.assertEqual(tt.identifier_to_path("ab"), "ab/ab") - self.assertEqual(tt.identifier_to_path("abc"), "ab/c/abc") - self.assertEqual(tt.identifier_to_path("abcde"), "ab/cd/e/abcde") - tt = Layout_NNNN_Tuple_Tree(tuple_size=3) - self.assertEqual(tt.identifier_to_path("abcdefg"), "abc/def/g/abcdefg") - self.assertEqual(tt.identifier_to_path("abcdefgh"), "abc/def/gh/abcdefgh") - self.assertEqual(tt.identifier_to_path("abcdefghi"), "abc/def/ghi/abcdefghi") + layout = Layout_NNNN_Tuple_Tree(tuple_size=2) + self.assertRaises(LayoutException, layout.identifier_to_path, "") + self.assertEqual(layout.identifier_to_path("a"), "a/a") + self.assertEqual(layout.identifier_to_path("ab"), "ab/ab") + self.assertEqual(layout.identifier_to_path("abc"), "ab/c/abc") + self.assertEqual(layout.identifier_to_path("abcde"), "ab/cd/e/abcde") + layout = Layout_NNNN_Tuple_Tree(tuple_size=3) + self.assertEqual(layout.identifier_to_path("abcdefg"), "abc/def/g/abcdefg") + self.assertEqual(layout.identifier_to_path("abcdefgh"), "abc/def/gh/abcdefgh") + self.assertEqual(layout.identifier_to_path("abcdefghi"), "abc/def/ghi/abcdefghi") From 8fbd175b0e5e077c9ca28bb48e7837039916946a Mon Sep 17 00:00:00 2001 From: Simeon Warner Date: Thu, 12 Dec 2024 13:46:57 -0500 Subject: [PATCH 05/10] Tidy layout_nnnn_uuid_quadtree --- ocfl/layout_nnnn_uuid_quadtree.py | 42 +++++++++++++++++++++---- tests/test_layout_nnnn_uuid_quadtree.py | 19 +++++++++-- 2 files changed, 53 insertions(+), 8 deletions(-) diff --git a/ocfl/layout_nnnn_uuid_quadtree.py b/ocfl/layout_nnnn_uuid_quadtree.py index ba1414d..7139738 100644 --- a/ocfl/layout_nnnn_uuid_quadtree.py +++ b/ocfl/layout_nnnn_uuid_quadtree.py @@ -29,26 +29,56 @@ def config(self): def check_prefix(self, value): """Check prefix paremeter. - Any string is allowed. + Argument: + value (str): prefix string + + Raises: + LayoutException: if the prefix is not allowed + + Any non-empty string is allowed. Sets the prefix property as a side-effect. """ if value is None: raise LayoutException("prefix parameter must be specified") - if not isinstance(value, str): - raise LayoutException("prefix parameter must be a string") + if not isinstance(value, str) or value == "": + raise LayoutException("prefix parameter must be a non-empty string") self.prefix = value def encode(self, identifier): - """NOOP encode identifier.""" + """NOOP encode identifier. + + Arguments: + identifier (str): identifier + + Returns: + str: unchanged identifier + """ return identifier def decode(self, identifier): - """NOOP decode identifier.""" + """NOOP decode identifier. + + Arguments: + identifier (str): identifier + + Returns: + str: unchanged identifier + """ return identifier def identifier_to_path(self, identifier): """Convert identifier to path relative to root. - Must match prefix:6ba7b810-9dad-11d1-80b4-00c04fd430c8 + Argument: + identifier (str): object identifier + + Returns: + str: object path for this layout + + Raises: + LayoutException: if the identifier cannot be converted to a valid + object path. + + Format is "prefix:6ba7b810-9dad-11d1-80b4-00c04fd430c8" """ if identifier.startswith(self.prefix): identifier = identifier[len(self.prefix):] diff --git a/tests/test_layout_nnnn_uuid_quadtree.py b/tests/test_layout_nnnn_uuid_quadtree.py index 9d98a39..a9ba5ec 100644 --- a/tests/test_layout_nnnn_uuid_quadtree.py +++ b/tests/test_layout_nnnn_uuid_quadtree.py @@ -1,19 +1,34 @@ """Digest tests.""" import unittest +from ocfl.layout import LayoutException from ocfl.layout_nnnn_uuid_quadtree import Layout_NNNN_UUID_Quadtree class TestAll(unittest.TestCase): """TestAll class to run tests.""" - def test01_encode(self): + def test_config(self): + """Test config property.""" + layout = Layout_NNNN_UUID_Quadtree() + self.assertEqual(set(layout.config.keys()), set(("extensionName", "prefix"))) + + def test_check_prefix(self): + """Test check_prefix method.""" + layout = Layout_NNNN_UUID_Quadtree() + self.assertRaises(LayoutException, layout.check_prefix, None) + self.assertRaises(LayoutException, layout.check_prefix, 53) + self.assertRaises(LayoutException, layout.check_prefix, "") # stil not! + self.assertEqual(layout.check_prefix("Pref"), None) + self.assertEqual(layout.prefix, "Pref") + + def test_encode(self): """Test NOOP encode.""" uuqt = Layout_NNNN_UUID_Quadtree() self.assertEqual(uuqt.encode(""), "") self.assertEqual(uuqt.encode("a"), "a") self.assertEqual(uuqt.encode("a/b:?"), "a/b:?") - def test02_decode(self): + def test_decode(self): """Test NOOP decode.""" uuqt = Layout_NNNN_UUID_Quadtree() self.assertEqual(uuqt.decode(""), "") From 18a3a314923127b14e5db8500630090945777400 Mon Sep 17 00:00:00 2001 From: Simeon Warner Date: Thu, 12 Dec 2024 14:39:14 -0500 Subject: [PATCH 06/10] Use DEFAULT_SPEC_VERSION --- ocfl.py | 4 +++- ocfl/inventory.py | 2 +- ocfl/inventory_validator.py | 11 ++++++----- ocfl/object.py | 8 +++++--- ocfl/storage_root.py | 4 ++-- ocfl/validation_logger.py | 17 ++++++++++------- ocfl/validator.py | 14 ++++++++------ tests/test_object.py | 4 ++-- 8 files changed, 37 insertions(+), 27 deletions(-) diff --git a/ocfl.py b/ocfl.py index 5df722f..94a4fc7 100755 --- a/ocfl.py +++ b/ocfl.py @@ -8,6 +8,7 @@ import ocfl # pylint: disable=import-self; this isn"t actually self import from ocfl.command_line_utils import add_version_arg, add_verbosity_args, \ check_version_arg, check_verbosity_args +from ocfl.constants import DEFAULT_SPEC_VERSION def add_common_args(parser): @@ -38,7 +39,8 @@ def parse_arguments(): "create", help="Create and initialize storage root") add_common_args(create_parser) - create_parser.add_argument("--spec-version", "--spec", action="store", default="1.1", + create_parser.add_argument("--spec-version", "--spec", action="store", + default=DEFAULT_SPEC_VERSION, help="OCFL specification version to adhere to") create_parser.add_argument("--layout-params", action="store", default=None, help="Specify parameters for the selected storage layout as a JSON string (including the extensionName is optional)") diff --git a/ocfl/inventory.py b/ocfl/inventory.py index b776869..911aaaa 100644 --- a/ocfl/inventory.py +++ b/ocfl/inventory.py @@ -16,7 +16,7 @@ >>> import ocfl >>> inv = ocfl.Inventory(filepath="fixtures/1.1/good-objects/spec-ex-full/inventory.json") >>> inv.spec_version - '1.1' + "1.1" >>> inv.version_numbers [1, 2, 3] >>> v2 = inv.version("v2") diff --git a/ocfl/inventory_validator.py b/ocfl/inventory_validator.py index e697acf..08b3af5 100644 --- a/ocfl/inventory_validator.py +++ b/ocfl/inventory_validator.py @@ -15,7 +15,7 @@ >>> iv.validate(inv) True >>> iv.spec_version - '1.1' + "1.1" >>> with open("fixtures/1.1/bad-objects/E025_wrong_digest_algorithm/inventory.json") as fh: ... inv = json.load(fh) ... @@ -27,7 +27,7 @@ """ import re -from .constants import SPEC_VERSIONS_SUPPORTED, DEFAULT_CONTENT_DIRECTORY +from .constants import SPEC_VERSIONS_SUPPORTED, DEFAULT_SPEC_VERSION, DEFAULT_CONTENT_DIRECTORY from .digest import digest_regex, normalized_digest from .validation_logger import ValidationLogger from .w3c_datetime import str_to_datetime @@ -55,7 +55,7 @@ class InventoryValidator(): """Class for OCFL Inventory Validator.""" def __init__(self, *, log=None, where="???", - lax_digests=False, default_spec_version="1.1"): + lax_digests=False, default_spec_version=DEFAULT_SPEC_VERSION): """Initialize OCFL Inventory Validator. It is expected that a new InventoryValidator object be created for @@ -75,8 +75,9 @@ def __init__(self, *, log=None, where="???", lax_digests: True to allow any digest to be used for content addressing, as opposed to only those allowed by the specification. - default_spec_version: string (default "1.1") indicating - the specification version to assume if it is not set. + default_spec_version: string (defaults to + ocfl.constants.DEFAULT_SPEC_VERSION) indicating + the specification version to assume if it is not set. """ self.log = ValidationLogger() if log is None else log self.where = where diff --git a/ocfl/object.py b/ocfl/object.py index b447437..60ba818 100755 --- a/ocfl/object.py +++ b/ocfl/object.py @@ -16,7 +16,7 @@ import fs.path import fs.copy -from .constants import INVENTORY_FILENAME, DEFAULT_CONTENT_DIRECTORY +from .constants import INVENTORY_FILENAME, DEFAULT_SPEC_VERSION, DEFAULT_CONTENT_DIRECTORY from .digest import file_digest from .inventory import Inventory from .inventory_validator import InventoryValidator @@ -81,7 +81,8 @@ class Object(): # pylint: disable=too-many-public-methods def __init__(self, *, identifier=None, content_directory=DEFAULT_CONTENT_DIRECTORY, digest_algorithm="sha512", content_path_normalization="uri", - spec_version="1.1", forward_delta=True, dedupe=True, + spec_version=DEFAULT_SPEC_VERSION, + forward_delta=True, dedupe=True, lax_digests=False, fixity=None, obj_fs=None, path=None, create=False): """Initialize OCFL object. @@ -91,7 +92,8 @@ def __init__(self, *, identifier=None, content_directory: allow override of the default "content" digest_algorithm: allow override of the default "sha512" content_path_normalization: allow override of default "uri" - spec_version: OCFL specification version + spec_version: OCFL specification version, default value is + taken from ocfl.constants.DEFAULT_SPEC_VERSION forward_delta: set False to turn off foward delta. With forward delta turned off, the same content will be repeated in a new version rather than simply being included by reference through the diff --git a/ocfl/storage_root.py b/ocfl/storage_root.py index bdb7fb0..755f462 100644 --- a/ocfl/storage_root.py +++ b/ocfl/storage_root.py @@ -10,7 +10,7 @@ import fs from fs.copy import copy_dir -from .constants import SPEC_VERSIONS_SUPPORTED +from .constants import DEFAULT_SPEC_VERSION, SPEC_VERSIONS_SUPPORTED from .namaste import find_namastes, Namaste from .object import Object from .pyfs import pyfs_openfs, pyfs_walk, pyfs_opendir @@ -85,7 +85,7 @@ def __init__(self, root=None, layout_name=None, lax_digests=False, self.structure_error = None self.traversal_errors = None - def check_spec_version(self, spec_version, default="1.1"): + def check_spec_version(self, spec_version, default=DEFAULT_SPEC_VERSION): """Check the OCFL specification version is supported.""" if spec_version is None and self.spec_version is None: spec_version = default diff --git a/ocfl/validation_logger.py b/ocfl/validation_logger.py index 5643c5e..1c313a5 100644 --- a/ocfl/validation_logger.py +++ b/ocfl/validation_logger.py @@ -28,6 +28,8 @@ import os.path import re +from ocfl.constants import DEFAULT_SPEC_VERSION + class ValidationLogger(): """Class for OCFL ValidationLogger. @@ -40,24 +42,25 @@ class ValidationLogger(): validation_codes = None def __init__(self, *, log_warnings=False, log_errors=True, - spec_version="1.1", lang="en", validation_codes=None): + spec_version=DEFAULT_SPEC_VERSION, + lang="en", validation_codes=None): """Initialize OCFL validation logger. - Keyword arguments: + Arguments: log_warnings (bool): True to log warnings via the - warning() method. Default False. + warning() method. Default False log_errors (bool): True to logs errors via the error() - method. Default True. + method. Default True spec_version (str): Specification version being validated - against, default "1.1". + against, default taken from ocfl.constants.DEFAULT_SPEC_VERSION lang (str): Language code to look up description strings - with, default "en". + with, default "en" validation_codes (dict): Default None. Usual behavior is to not use this argument in which case the validation codes and description data are loaded from the normal location on first use of this class. Subsequent instantiations use the same class data. Allows an override to supply the - data explicitly. + data explicitly """ self.log_warnings = log_warnings self.log_errors = log_errors diff --git a/ocfl/validator.py b/ocfl/validator.py index bc1edd9..908c9c9 100644 --- a/ocfl/validator.py +++ b/ocfl/validator.py @@ -14,7 +14,7 @@ import fs from .constants import INVENTORY_FILENAME, SPEC_VERSIONS_SUPPORTED, \ - DEFAULT_CONTENT_DIRECTORY + DEFAULT_SPEC_VERSION, DEFAULT_CONTENT_DIRECTORY from .digest import file_digest, normalized_digest from .inventory_validator import InventoryValidator from .namaste import find_namastes @@ -31,7 +31,8 @@ class Validator(): def __init__(self, *, log_warnings=False, log_errors=True, check_digests=True, lax_digests=False, - force_spec_version=None, default_spec_version="1.1", + force_spec_version=None, + default_spec_version=DEFAULT_SPEC_VERSION, log=None, lang="en"): """Initialize OCFL Object validator object. @@ -44,18 +45,19 @@ def __init__(self, *, log_warnings=False, log_errors=True, lax_digests: default is False. Set True to allow digests beyond those included in the specification for fixity and to allow non-preferred digest algorithms for content references in the - object. + object force_spec_version: string of specification version to force validation at, else None (default) to not force a specific version. If force_spec_version is set then a declaration within - the object that doesn't match will be reported as an error. + the object that doesn't match will be reported as an error default_spec_version: string of default specification version to - assume where not specified (default "1.1") + assume where not specified. Default is taken from + ocfl.constants.DEFAULT_SPEC_VERSION log: None (default) to create new ValidationLogger instance, or else use the specified instance which is the appropriate case for validation of multiple objects within a storage root. lang: language string (default "en") to pass to the validation - logger. + logger """ self.check_digests = check_digests self.lax_digests = lax_digests diff --git a/tests/test_object.py b/tests/test_object.py index 19651f0..3db0c2c 100644 --- a/tests/test_object.py +++ b/tests/test_object.py @@ -195,7 +195,7 @@ def test_validate(self): self.assertFalse(passed) self.assertIn("[E036a]", validator.status_str()) # - oo = Object(spec_version='1.1') + oo = Object(spec_version="1.1") (passed, validator) = oo.validate(objdir='fixtures/1.1/good-objects/minimal_one_version_one_file') self.assertTrue(passed) self.assertEqual(validator.status_str(), "") @@ -213,7 +213,7 @@ def test_validate_inventory(self): self.assertFalse(oo.validate_inventory(path='fixtures/1.0/bad-objects/E036_no_id/inventory.json')[0]) self.assertFalse(oo.validate_inventory(path='tests/testdata/namaste/0=frog')[0]) # not JSON # - oo = Object(spec_version='1.1') + oo = Object(spec_version="1.1") (passed, validator) = oo.validate_inventory(path='fixtures/1.1/good-objects/minimal_one_version_one_file/inventory.json') self.assertTrue(passed) self.assertEqual(validator.status_str(), '') From d0053a579ce40fe90b5ea4c7d5ad4f1738c24db4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 12 Dec 2024 19:42:24 +0000 Subject: [PATCH 07/10] Update error codes and demo docs [actions skip] --- docs/demo_using_bagit_bags.md | 14 +-- docs/validation_status.md | 216 +++++++++++++++++----------------- 2 files changed, 115 insertions(+), 115 deletions(-) diff --git a/docs/demo_using_bagit_bags.md b/docs/demo_using_bagit_bags.md index 9ce51d0..6c7588e 100644 --- a/docs/demo_using_bagit_bags.md +++ b/docs/demo_using_bagit_bags.md @@ -63,7 +63,7 @@ INFO:bagit:Verifying checksum for file /home/runner/work/ocfl-py/ocfl-py/tests/t INFO:bagit:Verifying checksum for file /home/runner/work/ocfl-py/ocfl-py/tests/testdata/bags/uaa_v2/bag-info.txt INFO:bagit:Verifying checksum for file /home/runner/work/ocfl-py/ocfl-py/tests/testdata/bags/uaa_v2/manifest-sha512.txt INFO:root:Updated OCFL object info:bb123cd4567 by adding v2 -### +### Updated object info:bb123cd4567 to v2 ``` @@ -104,7 +104,7 @@ INFO:bagit:Verifying checksum for file /home/runner/work/ocfl-py/ocfl-py/tests/t INFO:bagit:Verifying checksum for file /home/runner/work/ocfl-py/ocfl-py/tests/testdata/bags/uaa_v3/bag-info.txt INFO:bagit:Verifying checksum for file /home/runner/work/ocfl-py/ocfl-py/tests/testdata/bags/uaa_v3/manifest-sha512.txt INFO:root:Updated OCFL object info:bb123cd4567 by adding v3 -### +### Updated object info:bb123cd4567 to v3 ``` @@ -150,7 +150,7 @@ INFO:bagit:Verifying checksum for file /home/runner/work/ocfl-py/ocfl-py/tests/t INFO:bagit:Verifying checksum for file /home/runner/work/ocfl-py/ocfl-py/tests/testdata/bags/uaa_v4/bag-info.txt INFO:bagit:Verifying checksum for file /home/runner/work/ocfl-py/ocfl-py/tests/testdata/bags/uaa_v4/manifest-sha512.txt INFO:root:Updated OCFL object info:bb123cd4567 by adding v4 -### +### Updated object info:bb123cd4567 to v4 ``` @@ -164,8 +164,8 @@ Taking the newly created OCFL object `/tmp/obj` we can `--extract` the `v4` cont INFO:root:Extracted v4 into tmp/extracted_v4 INFO:bagit:Creating bag for directory tmp/extracted_v4 INFO:bagit:Creating data directory -INFO:bagit:Moving my_content to tmp/extracted_v4/tmp6gveuavx/my_content -INFO:bagit:Moving tmp/extracted_v4/tmp6gveuavx to data +INFO:bagit:Moving my_content to tmp/extracted_v4/tmpeornl0iv/my_content +INFO:bagit:Moving tmp/extracted_v4/tmpeornl0iv to data INFO:bagit:Using 1 processes to generate manifests: sha512 INFO:bagit:Generating manifest lines for file data/my_content/dracula.txt INFO:bagit:Generating manifest lines for file data/my_content/dunwich.txt @@ -187,12 +187,12 @@ We note that the OCFL object had only one `content` file in `v4` but the extract diff -r tmp/extracted_v4/bag-info.txt tests/testdata/bags/uaa_v4/bag-info.txt 1,2c1 < Bag-Software-Agent: bagit.py v1.8.1 -< Bagging-Date: 2024-12-06 +< Bagging-Date: 2024-12-12 --- > Bagging-Date: 2020-01-04 diff -r tmp/extracted_v4/tagmanifest-sha512.txt tests/testdata/bags/uaa_v4/tagmanifest-sha512.txt 2c2 -< 7e23b308ac51b064e7471d7b8e5ba1f758891631ad8c8fb57799a39018d7d77e893a8236a608a8087117000c55efde9529cb76cdb63bacc5642b38ab459b30d5 bag-info.txt +< f4b54148ef84efafaaa8f062695a1bc07a1e876a15f6164f6e89d979f09c91baa5b1d76002b2bffb6193445b0642a0d6237731623d0ab4050bde5558fcbcca4e bag-info.txt --- > 10624e6d45462def7af66d1a0d977606c7b073b01809c1d42258cfab5c34a275480943cbe78044416aee1f23822cc3762f92247b8f39b5c6ddc5ae32a8f94ce5 bag-info.txt ``` diff --git a/docs/validation_status.md b/docs/validation_status.md index 9263efe..f4247de 100644 --- a/docs/validation_status.md +++ b/docs/validation_status.md @@ -7,129 +7,130 @@ The following tables show the implementation status of all errors and warnings i | Code | Specification text (or suffixed code) | Implementation status and message/links | | --- | --- | --- | | [E001](https://ocfl.io/1.1/spec#E001) | 'The OCFL Object Root must not contain files or directories other than those specified in the following sections.' | **Missing description** \[[ocfl/validation_logger.py#L5](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validation_logger.py#L5)\] | -| | E001a | OCFL Object root contains unexpected file: %s \[[ocfl/validation_logger.py#L10](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validation_logger.py#L10) [ocfl/validator.py#L249](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L249)\] | -| | E001b | OCFL Object root contains unexpected directory: %s \[[ocfl/validation_logger.py#L16](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validation_logger.py#L16) [ocfl/validator.py#L266](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L266)\] | -| | E001c | OCFL Object root contains unexpected entry that isn't a file or directory: %s \[[ocfl/validator.py#L268](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L268)\] | +| | E001a | OCFL Object root contains unexpected file: %s \[[ocfl/validation_logger.py#L10](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validation_logger.py#L10) [ocfl/validator.py#L251](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L251)\] | +| | E001b | OCFL Object root contains unexpected directory: %s \[[ocfl/validation_logger.py#L16](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validation_logger.py#L16) [ocfl/validator.py#L268](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L268)\] | +| | E001c | OCFL Object root contains unexpected entry that isn't a file or directory: %s \[[ocfl/validator.py#L270](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L270)\] | | [E002](https://ocfl.io/1.1/spec#E002) | 'The version declaration must be formatted according to the NAMASTE specification.' | NOTE - E002 is redundant to more specific errors E003, E004, E005, E006. \[_Not implemented_\] | | [E003](https://ocfl.io/1.1/spec#E003) | 'There must be exactly one version declaration file in the base directory of the OCFL Object Root giving the OCFL version in the filename.' | _See multiple cases identified with suffixes below_ | -| | E003a | OCFL Object version declaration file is missing (assuming version %s) \[[ocfl/validator.py#L132](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L132)\] | -| | E003b | OCFL Object includes more that one file that looks like an object declaration (got %s), using version %s \[[ocfl/validator.py#L154](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L154)\] | -| | E003c | OCFL Object includes one or more object declaration files (starting 0=) but no valid version number could be extracted from any of them (assuming version %s) \[[ocfl/validator.py#L149](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L149)\] | +| | E003a | OCFL Object version declaration file is missing (assuming version %s) \[[ocfl/validator.py#L134](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L134)\] | +| | E003b | OCFL Object includes more that one file that looks like an object declaration (got %s), using version %s \[[ocfl/validator.py#L156](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L156)\] | +| | E003c | OCFL Object includes one or more object declaration files (starting 0=) but no valid version number could be extracted from any of them (assuming version %s) \[[ocfl/validator.py#L151](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L151)\] | | | E003d | OCFL Storage Root hierarchy includes directory %s with more that one file that looks like an object declaration, ignoring \[[ocfl/storage_root.py#L250](https://github.com/zimeon/ocfl-py/blob/main/ocfl/storage_root.py#L250)\] | -| | E003e | No OCFL Object to validate at path %s. The root of an OCFL Object must be a directory containing an object declaration \[[ocfl/validator.py#L126](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L126)\] | +| | E003e | No OCFL Object to validate at path %s. The root of an OCFL Object must be a directory containing an object declaration \[[ocfl/validator.py#L128](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L128)\] | | [E004](https://ocfl.io/1.1/spec#E004) | 'The [version declaration] filename MUST conform to the pattern T=dvalue, where T must be 0, and dvalue must be ocfl_object_, followed by the OCFL specification version number.' | _See multiple cases identified with suffixes below_ | | | E004a | OCFL Storage Root hierarchy includes directory %s with an object declaration giving unknown version %s, ignoring \[[ocfl/storage_root.py#L257](https://github.com/zimeon/ocfl-py/blob/main/ocfl/storage_root.py#L257)\] | | | E004b | OCFL Storage Root hierarchy includes directory %s with an unrecognized object declaration %s, ignoring \[[ocfl/storage_root.py#L259](https://github.com/zimeon/ocfl-py/blob/main/ocfl/storage_root.py#L259)\] | | [E005](https://ocfl.io/1.1/spec#E005) | 'The [version declaration] filename must conform to the pattern T=dvalue, where T MUST be 0, and dvalue must be ocfl_object_, followed by the OCFL specification version number.' | _Not implemented_ | -| [E006](https://ocfl.io/1.1/spec#E006) | 'The [version declaration] filename must conform to the pattern T=dvalue, where T must be 0, and dvalue MUST be ocfl_object_, followed by the OCFL specification version number.' | The OCFL object conformance declaration filename must be 0=ocfl_object_, followed by the OCFL specification version number, got %s instead \[[ocfl/validator.py#L143](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L143)\] | -| [E007](https://ocfl.io/1.1/spec#E007) | 'The text contents of the [version declaration] file must be the same as dvalue, followed by a newline (\n).' | OCFL Object declaration file %s contents do not match file name without leading 0= (the 'dvalue') \[[ocfl/validator.py#L147](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L147)\] | -| [E008](https://ocfl.io/1.1/spec#E008) | 'OCFL Object content must be stored as a sequence of one or more versions.' | OCFL Object %s inventory versions block does not contain any versions, there must be at least version 1 \[[ocfl/inventory_validator.py#L191](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L191) [ocfl/inventory_validator.py#L361](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L361)\] | -| [E009](https://ocfl.io/1.1/spec#E009) | 'The version number sequence MUST start at 1 and must be continuous without missing integers.' | OCFL Object %s inventory versions block does not contain v1 or a zero padded equivalent \[[ocfl/inventory_validator.py#L381](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L381)\] | -| [E010](https://ocfl.io/1.1/spec#E010) | 'The version number sequence must start at 1 and MUST be continuous without missing integers.' | OCFL Object %s inventory versions block includes an out-of-sequence version \[[ocfl/inventory_validator.py#L392](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L392)\] | -| [E011](https://ocfl.io/1.1/spec#E011) | 'If zero-padded version directory numbers are used then they must start with the prefix v and then a zero.' | **Missing description** \[[ocfl/inventory_validator.py#L399](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L399)\] | +| [E006](https://ocfl.io/1.1/spec#E006) | 'The [version declaration] filename must conform to the pattern T=dvalue, where T must be 0, and dvalue MUST be ocfl_object_, followed by the OCFL specification version number.' | The OCFL object conformance declaration filename must be 0=ocfl_object_, followed by the OCFL specification version number, got %s instead \[[ocfl/validator.py#L145](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L145)\] | +| [E007](https://ocfl.io/1.1/spec#E007) | 'The text contents of the [version declaration] file must be the same as dvalue, followed by a newline (\n).' | OCFL Object declaration file %s contents do not match file name without leading 0= (the 'dvalue') \[[ocfl/validator.py#L149](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L149)\] | +| [E008](https://ocfl.io/1.1/spec#E008) | 'OCFL Object content must be stored as a sequence of one or more versions.' | OCFL Object %s inventory versions block does not contain any versions, there must be at least version 1 \[[ocfl/inventory_validator.py#L192](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L192) [ocfl/inventory_validator.py#L362](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L362)\] | +| [E009](https://ocfl.io/1.1/spec#E009) | 'The version number sequence MUST start at 1 and must be continuous without missing integers.' | OCFL Object %s inventory versions block does not contain v1 or a zero padded equivalent \[[ocfl/inventory_validator.py#L382](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L382)\] | +| [E010](https://ocfl.io/1.1/spec#E010) | 'The version number sequence must start at 1 and MUST be continuous without missing integers.' | OCFL Object %s inventory versions block includes an out-of-sequence version \[[ocfl/inventory_validator.py#L393](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L393)\] | +| [E011](https://ocfl.io/1.1/spec#E011) | 'If zero-padded version directory numbers are used then they must start with the prefix v and then a zero.' | **Missing description** \[[ocfl/inventory_validator.py#L400](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L400)\] | | [E012](https://ocfl.io/1.1/spec#E012) | 'All version directories of an object must use the same naming convention: either a non-padded version directory number, or a zero-padded version directory number of consistent length.' | _Not implemented_ | | [E013](https://ocfl.io/1.1/spec#E013) | 'Operations that add a new version to an object must follow the version directory naming convention established by earlier versions.' | _Not implemented_ | | [E014](https://ocfl.io/1.1/spec#E014) | 'In all cases, references to files inside version directories from inventory files must use the actual version directory names.' | _Not implemented_ | -| [E015](https://ocfl.io/1.1/spec#E015) | 'There must be no other files as children of a version directory, other than an inventory file and a inventory digest.' | OCFL Object version directory %s includes an illegal file (%s) \[[ocfl/validator.py#L426](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L426)\] | +| [E015](https://ocfl.io/1.1/spec#E015) | 'There must be no other files as children of a version directory, other than an inventory file and a inventory digest.' | OCFL Object version directory %s includes an illegal file (%s) \[[ocfl/validator.py#L428](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L428)\] | | [E016](https://ocfl.io/1.1/spec#E016) | 'Version directories must contain a designated content sub-directory if the version contains files to be preserved, and should not contain this sub-directory otherwise.' | _Not implemented_ | -| [E017](https://ocfl.io/1.1/spec#E017) | 'The contentDirectory value MUST NOT contain the forward slash (/) path separator and must not be either one or two periods (. or ..).' | OCFL Object %s inventory contentDirectory must be a string and must not contain a forward slash (/) \[[ocfl/inventory_validator.py#L161](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L161)\] | -| [E018](https://ocfl.io/1.1/spec#E018) | 'The contentDirectory value must not contain the forward slash (/) path separator and MUST NOT be either one or two periods (. or ..).' | OCFL Object %s inventory contentDirectory must not be either . or .. \[[ocfl/inventory_validator.py#L163](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L163)\] | -| [E019](https://ocfl.io/1.1/spec#E019) | 'If the key contentDirectory is set, it MUST be set in the first version of the object and must not change between versions of the same object.' | OCFL Object %s inventory sets contentDirectory whereas it was not set in the first version inventory \[[ocfl/validator.py#L337](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L337) [ocfl/validator.py#L345](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L345)\] | -| [E020](https://ocfl.io/1.1/spec#E020) | 'If the key contentDirectory is set, it must be set in the first version of the object and MUST NOT change between versions of the same object.' | OCFL Object %s inventory contentDirectory %s does not match root contentDirectory %s \[[ocfl/validator.py#L348](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L348)\] | +| [E017](https://ocfl.io/1.1/spec#E017) | 'The contentDirectory value MUST NOT contain the forward slash (/) path separator and must not be either one or two periods (. or ..).' | OCFL Object %s inventory contentDirectory must be a string and must not contain a forward slash (/) \[[ocfl/inventory_validator.py#L162](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L162)\] | +| [E018](https://ocfl.io/1.1/spec#E018) | 'The contentDirectory value must not contain the forward slash (/) path separator and MUST NOT be either one or two periods (. or ..).' | OCFL Object %s inventory contentDirectory must not be either . or .. \[[ocfl/inventory_validator.py#L164](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L164)\] | +| [E019](https://ocfl.io/1.1/spec#E019) | 'If the key contentDirectory is set, it MUST be set in the first version of the object and must not change between versions of the same object.' | OCFL Object %s inventory sets contentDirectory whereas it was not set in the first version inventory \[[ocfl/validator.py#L339](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L339) [ocfl/validator.py#L347](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L347)\] | +| [E020](https://ocfl.io/1.1/spec#E020) | 'If the key contentDirectory is set, it must be set in the first version of the object and MUST NOT change between versions of the same object.' | OCFL Object %s inventory contentDirectory %s does not match root contentDirectory %s \[[ocfl/validator.py#L350](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L350)\] | | [E021](https://ocfl.io/1.1/spec#E021) | 'If the key contentDirectory is not present in the inventory file then the name of the designated content sub-directory must be content.' | _Not implemented_ | | [E022](https://ocfl.io/1.1/spec#E022) | 'OCFL-compliant tools (including any validators) must ignore all directories in the object version directory except for the designated content directory.' | _Not implemented_ | | [E023](https://ocfl.io/1.1/spec#E023) | 'Every file within a version\'s content directory must be referenced in the manifest section of the inventory.' | _See multiple cases identified with suffixes below_ | -| | E023a | OCFL Object includes one or more files that are not mentioned in the %s inventory manifest (%s) \[[ocfl/validator.py#L464](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L464)\] | -| | E023b | OCFL Object %s manifest does not include files listed in previous version manifests (%s) \[[ocfl/validator.py#L358](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L358)\] | -| [E024](https://ocfl.io/1.1/spec#E024) | 'There must not be empty directories within a version\'s content directory.' | OCFL Object version %s content directory includes empty path %s \[[ocfl/validator.py#L417](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L417)\] | +| | E023a | OCFL Object includes one or more files that are not mentioned in the %s inventory manifest (%s) \[[ocfl/validator.py#L466](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L466)\] | +| | E023b | OCFL Object %s manifest does not include files listed in previous version manifests (%s) \[[ocfl/validator.py#L360](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L360)\] | +| [E024](https://ocfl.io/1.1/spec#E024) | 'There must not be empty directories within a version\'s content directory.' | OCFL Object version %s content directory includes empty path %s \[[ocfl/validator.py#L419](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L419)\] | | [E025](https://ocfl.io/1.1/spec#E025) | 'For content-addressing, OCFL Objects must use either sha512 or sha256, and should use sha512.' | _See multiple cases identified with suffixes below_ | -| | E025a | OCFL Object %s inventory `digestAlgorithm` attribute not an allowed digest type (got '%s') \[[ocfl/inventory_validator.py#L155](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L155)\] | -| | E025b | OCFL Object %s inventory manifest block includes a digest (%s) that doesn't have the correct form for the %s algorithm \[[ocfl/inventory_validator.py#L280](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L280)\] | +| | E025a | OCFL Object %s inventory `digestAlgorithm` attribute not an allowed digest type (got '%s') \[[ocfl/inventory_validator.py#L156](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L156)\] | +| | E025b | OCFL Object %s inventory manifest block includes a digest (%s) that doesn't have the correct form for the %s algorithm \[[ocfl/inventory_validator.py#L281](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L281)\] | | [E026](https://ocfl.io/1.1/spec#E026) | 'For storage of additional fixity values, or to support legacy content migration, implementers must choose from the following controlled vocabulary of digest algorithms, or from a list of additional algorithms given in the [Digest-Algorithms-Extension].' | _See multiple cases identified with suffixes below_ | -| | E026a | OCFL Object %s inventory uses unknown digest type %s \[[ocfl/inventory_validator.py#L523](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L523)\] | +| | E026a | OCFL Object %s inventory uses unknown digest type %s \[[ocfl/inventory_validator.py#L524](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L524)\] | | [E027](https://ocfl.io/1.1/spec#E027) | 'OCFL clients must support all fixity algorithms given in the table below, and may support additional algorithms from the extensions.' | _Not implemented_ | | [E028](https://ocfl.io/1.1/spec#E028) | 'Optional fixity algorithms that are not supported by a client must be ignored by that client.' | _Not implemented_ | | [E029](https://ocfl.io/1.1/spec#E029) | 'SHA-1 algorithm defined by [FIPS-180-4] and must be encoded using hex (base16) encoding [RFC4648].' | _Not implemented_ | | [E030](https://ocfl.io/1.1/spec#E030) | 'SHA-256 algorithm defined by [FIPS-180-4] and must be encoded using hex (base16) encoding [RFC4648].' | _Not implemented_ | | [E031](https://ocfl.io/1.1/spec#E031) | 'SHA-512 algorithm defined by [FIPS-180-4] and must be encoded using hex (base16) encoding [RFC4648].' | _Not implemented_ | | [E032](https://ocfl.io/1.1/spec#E032) | '[blake2b-512] must be encoded using hex (base16) encoding [RFC4648].' | _Not implemented_ | -| [E033](https://ocfl.io/1.1/spec#E033) | 'An OCFL Object Inventory MUST follow the [JSON] structure described in this section and must be named inventory.json.' | OCFL Object %s inventory is not valid JSON (%s) \[[ocfl/object.py#L643](https://github.com/zimeon/ocfl-py/blob/main/ocfl/object.py#L643) [ocfl/validator.py#L201](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L201)\] | +| [E033](https://ocfl.io/1.1/spec#E033) | 'An OCFL Object Inventory MUST follow the [JSON] structure described in this section and must be named inventory.json.' | OCFL Object %s inventory is not valid JSON (%s) \[[ocfl/object.py#L645](https://github.com/zimeon/ocfl-py/blob/main/ocfl/object.py#L645) [ocfl/validator.py#L203](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L203)\] | | [E034](https://ocfl.io/1.1/spec#E034) | 'An OCFL Object Inventory must follow the [JSON] structure described in this section and MUST be named inventory.json.' | _Not implemented_ | | [E035](https://ocfl.io/1.1/spec#E035) | 'The forward slash (/) path separator must be used in content paths in the manifest and fixity blocks within the inventory.' | _Not implemented_ | | [E036](https://ocfl.io/1.1/spec#E036) | 'An OCFL Object Inventory must include the following keys: [id, type, digestAlgorithm, head]' | _See multiple cases identified with suffixes below_ | -| | E036a | OCFL Object %s inventory missing `id` attribute \[[ocfl/inventory_validator.py#L127](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L127)\] | -| | E036b | OCFL Object %s inventory missing `type` attribute \[[ocfl/inventory_validator.py#L129](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L129)\] | -| | E036c | OCFL Object %s inventory missing `digestAlgorithm` attribute \[[ocfl/inventory_validator.py#L146](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L146)\] | -| | E036d | OCFL Object %s inventory missing `head` attribute \[[ocfl/inventory_validator.py#L179](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L179)\] | +| | E036a | OCFL Object %s inventory missing `id` attribute \[[ocfl/inventory_validator.py#L128](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L128)\] | +| | E036b | OCFL Object %s inventory missing `type` attribute \[[ocfl/inventory_validator.py#L130](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L130)\] | +| | E036c | OCFL Object %s inventory missing `digestAlgorithm` attribute \[[ocfl/inventory_validator.py#L147](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L147)\] | +| | E036d | OCFL Object %s inventory missing `head` attribute \[[ocfl/inventory_validator.py#L180](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L180)\] | | [E037](https://ocfl.io/1.1/spec#E037) | '[id] must be unique in the local context, and should be a URI [RFC3986].' | _See multiple cases identified with suffixes below_ | -| | E037a | OCFL Object %s inventory `id` attribute is empty or badly formed \[[ocfl/inventory_validator.py#L119](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L119)\] | -| | E037b | OCFL Object %s inventory id `%s` does not match the value in the root inventory `%s` \[[ocfl/validator.py#L333](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L333)\] | +| | E037a | OCFL Object %s inventory `id` attribute is empty or badly formed \[[ocfl/inventory_validator.py#L120](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L120)\] | +| | E037b | OCFL Object %s inventory id `%s` does not match the value in the root inventory `%s` \[[ocfl/validator.py#L335](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L335)\] | | [E038](https://ocfl.io/1.1/spec#E038) | 'In the object root inventory [the type value] must be the URI of the inventory section of the specification version matching the object conformance declaration.' | _See multiple cases identified with suffixes below_ | -| | E038a | OCFL Object %s inventory `type` attribute has wrong value (expected %s, got %s) \[[ocfl/inventory_validator.py#L134](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L134)\] | -| | E038b | OCFL Object %s inventory `type` attribute does not look like a valid specification URI (got %s), will proceed as if using version %s \[[ocfl/inventory_validator.py#L139](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L139)\] | -| | E038c | OCFL Object %s inventory `type` attribute has an unsupported specification version number (%s), will proceed as if using version %s \[[ocfl/inventory_validator.py#L144](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L144)\] | +| | E038a | OCFL Object %s inventory `type` attribute has wrong value (expected %s, got %s) \[[ocfl/inventory_validator.py#L135](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L135)\] | +| | E038b | OCFL Object %s inventory `type` attribute does not look like a valid specification URI (got %s), will proceed as if using version %s \[[ocfl/inventory_validator.py#L140](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L140)\] | +| | E038c | OCFL Object %s inventory `type` attribute has an unsupported specification version number (%s), will proceed as if using version %s \[[ocfl/inventory_validator.py#L145](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L145)\] | +| | E038d | OCFL Object %s inventory `type` attribute does not have a string value \[[ocfl/inventory_validator.py#L132](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L132)\] | | [E039](https://ocfl.io/1.1/spec#E039) | '[digestAlgorithm] must be the algorithm used in the manifest and state blocks.' | _Not implemented_ | -| [E040](https://ocfl.io/1.1/spec#E040) | [head] must be the version directory name with the highest version number.' | OCFL Object %s inventory head attribute doesn't match versions (got %s, expected %s) \[[ocfl/inventory_validator.py#L183](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L183)\] | +| [E040](https://ocfl.io/1.1/spec#E040) | [head] must be the version directory name with the highest version number.' | OCFL Object %s inventory head attribute doesn't match versions (got %s, expected %s) \[[ocfl/inventory_validator.py#L184](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L184)\] | | [E041](https://ocfl.io/1.1/spec#E041) | 'In addition to these keys, there must be two other blocks present, manifest and versions, which are discussed in the next two sections.' | _See multiple cases identified with suffixes below_ | -| | E041a | OCFL Object %s inventory missing `manifest` attribute \[[ocfl/inventory_validator.py#L169](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L169)\] | -| | E041b | OCFL Object %s inventory missing `versions` attribute \[[ocfl/inventory_validator.py#L174](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L174)\] | -| | E041c | OCFL Object %s inventory manifest block is not a JSON object \[[ocfl/inventory_validator.py#L273](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L273)\] | +| | E041a | OCFL Object %s inventory missing `manifest` attribute \[[ocfl/inventory_validator.py#L170](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L170)\] | +| | E041b | OCFL Object %s inventory missing `versions` attribute \[[ocfl/inventory_validator.py#L175](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L175)\] | +| | E041c | OCFL Object %s inventory manifest block is not a JSON object \[[ocfl/inventory_validator.py#L274](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L274)\] | | [E042](https://ocfl.io/1.1/spec#E042) | 'Content paths within a manifest block must be relative to the OCFL Object Root.' | _See multiple cases identified with suffixes below_ | -| | E042a | OCFL Object %s inventory manifest includes invalid content path %s \[[ocfl/inventory_validator.py#L558](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L558)\] | -| | E042b | OCFL Object %s inventory manifest includes content path %s with invalid version directory \[[ocfl/inventory_validator.py#L504](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L504)\] | -| | E042c | OCFL Object %s inventory manifest includes invalid content path %s that doesn't match the content directory name %s \[[ocfl/inventory_validator.py#L561](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L561)\] | +| | E042a | OCFL Object %s inventory manifest includes invalid content path %s \[[ocfl/inventory_validator.py#L559](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L559)\] | +| | E042b | OCFL Object %s inventory manifest includes content path %s with invalid version directory \[[ocfl/inventory_validator.py#L505](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L505)\] | +| | E042c | OCFL Object %s inventory manifest includes invalid content path %s that doesn't match the content directory name %s \[[ocfl/inventory_validator.py#L562](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L562)\] | | [E043](https://ocfl.io/1.1/spec#E043) | 'An OCFL Object Inventory must include a block for storing versions.' | _Not implemented_ | -| [E044](https://ocfl.io/1.1/spec#E044) | 'This block MUST have the key of versions within the inventory, and it must be a JSON object.' | OCFL Object %s inventory versions block is not a JSON object \[[ocfl/inventory_validator.py#L358](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L358)\] | +| [E044](https://ocfl.io/1.1/spec#E044) | 'This block MUST have the key of versions within the inventory, and it must be a JSON object.' | OCFL Object %s inventory versions block is not a JSON object \[[ocfl/inventory_validator.py#L359](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L359)\] | | [E045](https://ocfl.io/1.1/spec#E045) | 'This block must have the key of versions within the inventory, and it MUST be a JSON object.' | _Not implemented_ | | [E046](https://ocfl.io/1.1/spec#E046) | 'The keys of [the versions object] must correspond to the names of the version directories used.' | _See multiple cases identified with suffixes below_ | -| | E046a | OCFL Object root inventory describes version %s but no corresponding version directory is present \[[ocfl/validator.py#L428](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L428)\] | -| | E046b | OCFL Object includes directory %s that looks like a version directory but isn't a valid version in the inventory \[[ocfl/validator.py#L263](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L263)\] | +| | E046a | OCFL Object root inventory describes version %s but no corresponding version directory is present \[[ocfl/validator.py#L430](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L430)\] | +| | E046b | OCFL Object includes directory %s that looks like a version directory but isn't a valid version in the inventory \[[ocfl/validator.py#L265](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L265)\] | | [E047](https://ocfl.io/1.1/spec#E047) | 'Each value [of the versions object] must be another JSON object that characterizes the version, as described in the 3.5.3.1 Version section.' | _Not implemented_ | -| [E048](https://ocfl.io/1.1/spec#E048) | 'A JSON object to describe one OCFL Version, which must include the following keys: [created, state]' | OCFL Object %s inventory %s version block does not include a created date or it is malformed \[[ocfl/inventory_validator.py#L420](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L420)\] | -| | E048c | OCFL Object %s inventory %s version block does not include a state block \[[ocfl/inventory_validator.py#L436](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L436)\] | +| [E048](https://ocfl.io/1.1/spec#E048) | 'A JSON object to describe one OCFL Version, which must include the following keys: [created, state]' | OCFL Object %s inventory %s version block does not include a created date or it is malformed \[[ocfl/inventory_validator.py#L421](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L421)\] | +| | E048c | OCFL Object %s inventory %s version block does not include a state block \[[ocfl/inventory_validator.py#L437](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L437)\] | | [E049](https://ocfl.io/1.1/spec#E049) | '[the value of the "created" key] must be expressed in the Internet Date/Time Format defined by [RFC3339].' | _See multiple cases identified with suffixes below_ | -| | E049a | OCFL Object %s inventory %s version block created date SHOULD include a timezone designator \[[ocfl/inventory_validator.py#L428](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L428)\] | -| | E049b | OCFL Object %s inventory %s version block created date SHOULD be granular to the seconds level \[[ocfl/inventory_validator.py#L430](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L430)\] | -| | E049c | OCFL Object %s inventory %s version block has bad created date, must be IS8601 (%s) \[[ocfl/inventory_validator.py#L432](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L432)\] | -| | E049d | OCFL Object %s inventory %s version block created value is not a JSON string \[[ocfl/inventory_validator.py#L422](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L422)\] | +| | E049a | OCFL Object %s inventory %s version block created date SHOULD include a timezone designator \[[ocfl/inventory_validator.py#L429](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L429)\] | +| | E049b | OCFL Object %s inventory %s version block created date SHOULD be granular to the seconds level \[[ocfl/inventory_validator.py#L431](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L431)\] | +| | E049c | OCFL Object %s inventory %s version block has bad created date, must be IS8601 (%s) \[[ocfl/inventory_validator.py#L433](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L433)\] | +| | E049d | OCFL Object %s inventory %s version block created value is not a JSON string \[[ocfl/inventory_validator.py#L423](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L423)\] | | [E050](https://ocfl.io/1.1/spec#E050) | 'The keys of [the "state" JSON object] are digest values, each of which must correspond to an entry in the manifest of the inventory.' | _See multiple cases identified with suffixes below_ | -| | E050a | OCFL Object %s inventory state refers to one or more digests that are not in the manifest (%s) \[[ocfl/inventory_validator.py#L512](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L512)\] | -| | E050c | OCFL Object %s inventory %s version block state block is not a JSON object \[[ocfl/inventory_validator.py#L469](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L469)\] | -| | E050d | OCFL Object %s inventory %s version state block includes a bad digest (%s) \[[ocfl/inventory_validator.py#L474](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L474)\] | -| | E050e | OCFL Object %s inventory %s version block state block value for digest %s is not list \[[ocfl/inventory_validator.py#L476](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L476)\] | -| | E050f | OCFL Object %s inventory version %s state includes digest value %s that is not listed in the manifest block \[[ocfl/inventory_validator.py#L485](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L485)\] | +| | E050a | OCFL Object %s inventory state refers to one or more digests that are not in the manifest (%s) \[[ocfl/inventory_validator.py#L513](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L513)\] | +| | E050c | OCFL Object %s inventory %s version block state block is not a JSON object \[[ocfl/inventory_validator.py#L470](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L470)\] | +| | E050d | OCFL Object %s inventory %s version state block includes a bad digest (%s) \[[ocfl/inventory_validator.py#L475](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L475)\] | +| | E050e | OCFL Object %s inventory %s version block state block value for digest %s is not list \[[ocfl/inventory_validator.py#L477](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L477)\] | +| | E050f | OCFL Object %s inventory version %s state includes digest value %s that is not listed in the manifest block \[[ocfl/inventory_validator.py#L486](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L486)\] | | [E051](https://ocfl.io/1.1/spec#E051) | 'The logical path [value of a "state" digest key] must be interpreted as a set of one or more path elements joined by a / path separator.' | NOTE - E051 is essentially a processing instruction and can't be tested for. \[_Not implemented_\] | -| [E052](https://ocfl.io/1.1/spec#E052) | '[logical] Path elements must not be ., .., or empty (//).' | OCFL Object %s inventory %s version block state block includes an invalid path %s that starts or ends with / \[[ocfl/inventory_validator.py#L540](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L540)\] | -| [E053](https://ocfl.io/1.1/spec#E053) | 'Additionally, a logical path must not begin or end with a forward slash (/).' | OCFL Object %s inventory %s version block state block includes an invalid path %s that includes . .. or // \[[ocfl/inventory_validator.py#L535](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L535)\] | +| [E052](https://ocfl.io/1.1/spec#E052) | '[logical] Path elements must not be ., .., or empty (//).' | OCFL Object %s inventory %s version block state block includes an invalid path %s that starts or ends with / \[[ocfl/inventory_validator.py#L541](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L541)\] | +| [E053](https://ocfl.io/1.1/spec#E053) | 'Additionally, a logical path must not begin or end with a forward slash (/).' | OCFL Object %s inventory %s version block state block includes an invalid path %s that includes . .. or // \[[ocfl/inventory_validator.py#L536](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L536)\] | | [E054](https://ocfl.io/1.1/spec#E054) | 'The value of the user key must contain a user name key, "name" and should contain an address key, "address".' | _See multiple cases identified with suffixes below_ | -| | E054a | OCFL Object %s inventory %s version block has user key with value that isn't a JSON object \[[ocfl/inventory_validator.py#L446](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L446)\] | -| | E054b | OCFL Object %s inventory %s version block has user/name key with value that isn't a string \[[ocfl/inventory_validator.py#L449](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L449)\] | -| | E054c | OCFL Object %s inventory %s version block has user/address key with value that isn't a string \[[ocfl/inventory_validator.py#L453](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L453)\] | +| | E054a | OCFL Object %s inventory %s version block has user key with value that isn't a JSON object \[[ocfl/inventory_validator.py#L447](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L447)\] | +| | E054b | OCFL Object %s inventory %s version block has user/name key with value that isn't a string \[[ocfl/inventory_validator.py#L450](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L450)\] | +| | E054c | OCFL Object %s inventory %s version block has user/address key with value that isn't a string \[[ocfl/inventory_validator.py#L454](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L454)\] | | [E055](https://ocfl.io/1.1/spec#E055) | 'If present, [the fixity] block must have the key of fixity within the inventory.' | _Not implemented_ | | [E056](https://ocfl.io/1.1/spec#E056) | 'The fixity block must contain keys corresponding to the controlled vocabulary given in the digest algorithms listed in the Digests section, or in a table given in an Extension.' | _See multiple cases identified with suffixes below_ | -| | E056a | OCFL Object %s inventory includes a fixity key with value that isn't a JSON object \[[ocfl/inventory_validator.py#L311](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L311)\] | -| | E056b | OCFL Object %s inventory fixity block includes a key that is not a known digest algorithm name: %s \[[ocfl/inventory_validator.py#L319](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L319)\] | +| | E056a | OCFL Object %s inventory includes a fixity key with value that isn't a JSON object \[[ocfl/inventory_validator.py#L312](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L312)\] | +| | E056b | OCFL Object %s inventory fixity block includes a key that is not a known digest algorithm name: %s \[[ocfl/inventory_validator.py#L320](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L320)\] | | [E057](https://ocfl.io/1.1/spec#E057) | 'The value of the fixity block for a particular digest algorithm must follow the structure of the manifest block; that is, a key corresponding to the digest value, and an array of content paths that match that digest.' | _See multiple cases identified with suffixes below_ | -| | E057a | OCFL Object %s inventory fixity block entry for digest algorithm name %s is not a JSON object \[[ocfl/inventory_validator.py#L326](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L326)\] | -| | E057b | OCFL Object %s inventory fixity block entry for digest algorithm %s includes digest %s which has the wrong form \[[ocfl/inventory_validator.py#L332](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L332)\] | -| | E057c | OCFL Object %s inventory fixity block entry for digest algorithm %s, digest %s is not a JSON list \[[ocfl/inventory_validator.py#L334](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L334)\] | -| | E057d | OCFL Object %s inventory fixity block entry for digest algorithm %s, digest %s includes a content path %s that is not in the manifest \[[ocfl/inventory_validator.py#L347](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L347)\] | +| | E057a | OCFL Object %s inventory fixity block entry for digest algorithm name %s is not a JSON object \[[ocfl/inventory_validator.py#L327](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L327)\] | +| | E057b | OCFL Object %s inventory fixity block entry for digest algorithm %s includes digest %s which has the wrong form \[[ocfl/inventory_validator.py#L333](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L333)\] | +| | E057c | OCFL Object %s inventory fixity block entry for digest algorithm %s, digest %s is not a JSON list \[[ocfl/inventory_validator.py#L335](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L335)\] | +| | E057d | OCFL Object %s inventory fixity block entry for digest algorithm %s, digest %s includes a content path %s that is not in the manifest \[[ocfl/inventory_validator.py#L348](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L348)\] | | [E058](https://ocfl.io/1.1/spec#E058) | 'Every occurrence of an inventory file must have an accompanying sidecar file stating its digest.' | _See multiple cases identified with suffixes below_ | -| | E058a | OCFL Object %s inventory is missing sidecar digest file at %s \[[ocfl/validator.py#L213](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L213)\] | -| | E058b | Cannot extract digest type from inventory digest file name %s \[[ocfl/validator.py#L236](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L236)\] | +| | E058a | OCFL Object %s inventory is missing sidecar digest file at %s \[[ocfl/validator.py#L215](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L215)\] | +| | E058b | Cannot extract digest type from inventory digest file name %s \[[ocfl/validator.py#L238](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L238)\] | | [E059](https://ocfl.io/1.1/spec#E059) | 'This value must match the value given for the digestAlgorithm key in the inventory.' | _Not implemented_ | -| [E060](https://ocfl.io/1.1/spec#E060) | 'The digest sidecar file must contain the digest of the inventory file.' | Mismatch between actual and recorded inventory digests for %s (calculated %s but read %s from %s) \[[ocfl/validator.py#L232](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L232)\] | -| [E061](https://ocfl.io/1.1/spec#E061) | '[The digest sidecar file] must follow the format: DIGEST inventory.json' | Cannot extract digest from inventory digest file (%s) \[[ocfl/validator.py#L234](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L234)\] | +| [E060](https://ocfl.io/1.1/spec#E060) | 'The digest sidecar file must contain the digest of the inventory file.' | Mismatch between actual and recorded inventory digests for %s (calculated %s but read %s from %s) \[[ocfl/validator.py#L234](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L234)\] | +| [E061](https://ocfl.io/1.1/spec#E061) | '[The digest sidecar file] must follow the format: DIGEST inventory.json' | Cannot extract digest from inventory digest file (%s) \[[ocfl/validator.py#L236](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L236)\] | | [E062](https://ocfl.io/1.1/spec#E062) | 'The digest of the inventory must be computed only after all changes to the inventory have been made, and thus writing the digest sidecar file is the last step in the versioning process.' | _Not implemented_ | -| [E063](https://ocfl.io/1.1/spec#E063) | 'Every OCFL Object must have an inventory file within the OCFL Object Root, corresponding to the state of the OCFL Object at the current version.' | OCFL Object root inventory is missing \[[ocfl/validator.py#L158](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L158)\] | -| [E064](https://ocfl.io/1.1/spec#E064) | 'Where an OCFL Object contains inventory.json in version directories, the inventory file in the OCFL Object Root must be the same as the file in the most recent version.' | Object root inventory and copy in last version MUST be identical but are not (%s and %s) \[[ocfl/validator.py#L315](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L315)\] | +| [E063](https://ocfl.io/1.1/spec#E063) | 'Every OCFL Object must have an inventory file within the OCFL Object Root, corresponding to the state of the OCFL Object at the current version.' | OCFL Object root inventory is missing \[[ocfl/validator.py#L160](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L160)\] | +| [E064](https://ocfl.io/1.1/spec#E064) | 'Where an OCFL Object contains inventory.json in version directories, the inventory file in the OCFL Object Root must be the same as the file in the most recent version.' | Object root inventory and copy in last version MUST be identical but are not (%s and %s) \[[ocfl/validator.py#L317](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L317)\] | | [E066](https://ocfl.io/1.1/spec#E066) | 'Each version block in each prior inventory file must represent the same object state as the corresponding version block in the current inventory file.' | _See multiple cases identified with suffixes below_ | -| | E066a | OCFL Object inventory for %s doesn't have a subset of version blocks of inventory for %s \[[ocfl/inventory_validator.py#L216](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L216)\] | -| | E066b | OCFL Object %s inventory %s version state doesn't have same logical paths as same version in %s inventory (paths %s only in %s inventory) \[[ocfl/inventory_validator.py#L236](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L236) [ocfl/inventory_validator.py#L238](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L238)\] | -| | E066c | OCFL Object %s inventory %s version state has logical path %s that maps to content path %s, but in %s inventory it maps to content path %s \[[ocfl/inventory_validator.py#L243](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L243)\] | -| | E066d | OCFL Object %s inventory %s version state has digest %s (mapping to logical files %s) that does not appear in the %s inventory \[[ocfl/inventory_validator.py#L595](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L595)\] | -| | E066e | OCFL Object %s inventory %s version state has digest %s (mapping to logical files %s) that does not appear in the %s inventory \[[ocfl/inventory_validator.py#L598](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L598)\] | -| [E067](https://ocfl.io/1.1/spec#E067) | 'The extensions directory must not contain any files or sub-directories other than extension sub-directories.' | OCFL Object extensions direct contains an unexpected non-directory entry: %s \[[ocfl/validator.py#L284](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L284)\] | +| | E066a | OCFL Object inventory for %s doesn't have a subset of version blocks of inventory for %s \[[ocfl/inventory_validator.py#L217](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L217)\] | +| | E066b | OCFL Object %s inventory %s version state doesn't have same logical paths as same version in %s inventory (paths %s only in %s inventory) \[[ocfl/inventory_validator.py#L237](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L237) [ocfl/inventory_validator.py#L239](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L239)\] | +| | E066c | OCFL Object %s inventory %s version state has logical path %s that maps to content path %s, but in %s inventory it maps to content path %s \[[ocfl/inventory_validator.py#L244](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L244)\] | +| | E066d | OCFL Object %s inventory %s version state has digest %s (mapping to logical files %s) that does not appear in the %s inventory \[[ocfl/inventory_validator.py#L596](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L596)\] | +| | E066e | OCFL Object %s inventory %s version state has digest %s (mapping to logical files %s) that does not appear in the %s inventory \[[ocfl/inventory_validator.py#L599](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L599)\] | +| [E067](https://ocfl.io/1.1/spec#E067) | 'The extensions directory must not contain any files or sub-directories other than extension sub-directories.' | OCFL Object extensions direct contains an unexpected non-directory entry: %s \[[ocfl/validator.py#L286](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L286)\] | | [E069](https://ocfl.io/1.1/spec#E069) | 'An OCFL Storage Root MUST contain a Root Conformance Declaration identifying it as such.' | _Not implemented_ | | [E070](https://ocfl.io/1.1/spec#E070) | 'If present, [the ocfl_layout.json document] MUST include the following two keys in the root JSON object: [extension, description]' | _Not implemented_ | | [E071](https://ocfl.io/1.1/spec#E071) | 'The value of the [ocfl_layout.json] extension key must be the registered extension name for the extension defining the arrangement under the storage root.' | _Not implemented_ | @@ -153,57 +154,56 @@ The following tables show the implementation status of all errors and warnings i | [E089](https://ocfl.io/1.1/spec#E089) | 'If the preservation of non-OCFL-compliant features is required then the content MUST be wrapped in a suitable disk or filesystem image format which OCFL can treat as a regular file.' | _Not implemented_ | | [E090](https://ocfl.io/1.1/spec#E090) | 'Hard and soft (symbolic) links are not portable and MUST NOT be used within OCFL Storage hierarchies.' | NOTE - E090 is a processing instruction and can't be tested for \[_Not implemented_\] | | E091 | **Not in specification** | OCFL Object %s inventory manifest file list for digest %s is not a JSON array \[_Not implemented_\] | -| [E092](https://ocfl.io/1.1/spec#E092) | 'The value for each key in the manifest must be an array containing the content paths of files in the OCFL Object that have content with the given digest.' | **Missing description** \[[ocfl/inventory_validator.py#L282](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L282)\] | -| | E092a | OCFL Object %s inventory manifest using digest algorithm %s has digest %s for file %s which doesn't match calculated digest %s for that file \[[ocfl/validator.py#L454](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L454) [ocfl/validator.py#L459](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L459)\] | -| | E092b | OCFL Object %s inventory manifest refers to a file path that is not present in the object (%s) \[[ocfl/validator.py#L449](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L449)\] | +| [E092](https://ocfl.io/1.1/spec#E092) | 'The value for each key in the manifest must be an array containing the content paths of files in the OCFL Object that have content with the given digest.' | **Missing description** \[[ocfl/inventory_validator.py#L283](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L283)\] | +| | E092a | OCFL Object %s inventory manifest using digest algorithm %s has digest %s for file %s which doesn't match calculated digest %s for that file \[[ocfl/validator.py#L456](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L456) [ocfl/validator.py#L461](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L461)\] | +| | E092b | OCFL Object %s inventory manifest refers to a file path that is not present in the object (%s) \[[ocfl/validator.py#L451](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L451)\] | | [E093](https://ocfl.io/1.1/spec#E093) | 'Where included in the fixity block, the digest values given must match the digests of the files at the corresponding content paths.' | _See multiple cases identified with suffixes below_ | -| | E093a | OCFL Object %s inventory fixity block for digest algorithm %s has digest %s for file %s which doesn't match calculated digest %s for that file \[[ocfl/validator.py#L457](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L457) [ocfl/validator.py#L460](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L460)\] | -| | E093b | OCFL Object %s inventory fixity block for digest algorithm %s has digest %s for a file %s which does not exist in the object \[[ocfl/validator.py#L443](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L443)\] | -| [E094](https://ocfl.io/1.1/spec#E094) | 'The value of [the message] key is freeform text, used to record the rationale for creating this version. It must be a JSON string.' | OCFL Object %s inventory %s version block has message key with value that isn't a string \[[ocfl/inventory_validator.py#L440](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L440)\] | +| | E093a | OCFL Object %s inventory fixity block for digest algorithm %s has digest %s for file %s which doesn't match calculated digest %s for that file \[[ocfl/validator.py#L459](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L459) [ocfl/validator.py#L462](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L462)\] | +| | E093b | OCFL Object %s inventory fixity block for digest algorithm %s has digest %s for a file %s which does not exist in the object \[[ocfl/validator.py#L445](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L445)\] | +| [E094](https://ocfl.io/1.1/spec#E094) | 'The value of [the message] key is freeform text, used to record the rationale for creating this version. It must be a JSON string.' | OCFL Object %s inventory %s version block has message key with value that isn't a string \[[ocfl/inventory_validator.py#L441](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L441)\] | | [E095](https://ocfl.io/1.1/spec#E095) | 'Within a version, logical paths must be unique and non-conflicting, so the logical path for a file cannot appear as the initial part of another logical path.' | _See multiple cases identified with suffixes below_ | -| | E095a | OCFL Object %s inventory version %s state has logical path %s used more than once \[[ocfl/inventory_validator.py#L480](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L480)\] | -| | E095b | OCFL Object %s inventory version %s state has logical path %s used as both a directory and a file path. \[[ocfl/inventory_validator.py#L491](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L491)\] | -| [E096](https://ocfl.io/1.1/spec#E096) | 'As JSON keys are case sensitive, while digests may not be, there is an additional requirement that each digest value must occur only once in the manifest regardless of case.' | OCFL Object %s inventory manifest block includes digest %s more than once with different normalizations \[[ocfl/inventory_validator.py#L288](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L288)\] | -| [E097](https://ocfl.io/1.1/spec#E097) | 'As JSON keys are case sensitive, while digests may not be, there is an additional requirement that each digest value must occur only once in the fixity block for any digest algorithm, regardless of case.' | OCFL Object %s inventory fixity block for digest algorithm %s, includes digest %s more than once with different normalizations \[[ocfl/inventory_validator.py#L342](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L342)\] | +| | E095a | OCFL Object %s inventory version %s state has logical path %s used more than once \[[ocfl/inventory_validator.py#L481](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L481)\] | +| | E095b | OCFL Object %s inventory version %s state has logical path %s used as both a directory and a file path. \[[ocfl/inventory_validator.py#L492](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L492)\] | +| [E096](https://ocfl.io/1.1/spec#E096) | 'As JSON keys are case sensitive, while digests may not be, there is an additional requirement that each digest value must occur only once in the manifest regardless of case.' | OCFL Object %s inventory manifest block includes digest %s more than once with different normalizations \[[ocfl/inventory_validator.py#L289](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L289)\] | +| [E097](https://ocfl.io/1.1/spec#E097) | 'As JSON keys are case sensitive, while digests may not be, there is an additional requirement that each digest value must occur only once in the fixity block for any digest algorithm, regardless of case.' | OCFL Object %s inventory fixity block for digest algorithm %s, includes digest %s more than once with different normalizations \[[ocfl/inventory_validator.py#L343](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L343)\] | | [E098](https://ocfl.io/1.1/spec#E098) | 'The content path must be interpreted as a set of one or more path elements joined by a / path separator.' | _Not implemented_ | -| [E099](https://ocfl.io/1.1/spec#E099) | '[content] path elements must not be ., .., or empty (//).' | OCFL Object %s inventory manifest content path %s includes invalid element ., .., or empty (//). \[[ocfl/inventory_validator.py#L566](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L566)\] | -| [E100](https://ocfl.io/1.1/spec#E100) | 'A content path must not begin or end with a forward slash (/).' | OCFL Object %s inventory manifest content path %s must not begin or end with /. \[[ocfl/inventory_validator.py#L554](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L554)\] | +| [E099](https://ocfl.io/1.1/spec#E099) | '[content] path elements must not be ., .., or empty (//).' | OCFL Object %s inventory manifest content path %s includes invalid element ., .., or empty (//). \[[ocfl/inventory_validator.py#L567](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L567)\] | +| [E100](https://ocfl.io/1.1/spec#E100) | 'A content path must not begin or end with a forward slash (/).' | OCFL Object %s inventory manifest content path %s must not begin or end with /. \[[ocfl/inventory_validator.py#L555](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L555)\] | | [E101](https://ocfl.io/1.1/spec#E101) | 'Within an inventory, content paths must be unique and non-conflicting, so the content path for a file cannot appear as the initial part of another content path.' | _See multiple cases identified with suffixes below_ | -| | E101a | OCFL Object %s inventory manifest content path %s is repeated \[[ocfl/inventory_validator.py#L570](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L570)\] | -| | E101b | OCFL Object %s inventory manifest content path %s used as both a directory and a file path \[[ocfl/inventory_validator.py#L298](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L298)\] | +| | E101a | OCFL Object %s inventory manifest content path %s is repeated \[[ocfl/inventory_validator.py#L571](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L571)\] | +| | E101b | OCFL Object %s inventory manifest content path %s used as both a directory and a file path \[[ocfl/inventory_validator.py#L299](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L299)\] | | [E102](https://ocfl.io/1.1/spec#E102) | 'An inventory file must not contain keys that are not specified.' | _Not implemented_ | -| [E103](https://ocfl.io/1.1/spec#E103) | 'Each version directory within an OCFL Object MUST conform to either the same or a later OCFL specification version as the preceding version directory.' | OCFL Object %s inventory conforms to specification version %s which is an earlier version than the %s inventory which conforms to specification version %s \[[ocfl/validator.py#L390](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L390)\] | +| [E103](https://ocfl.io/1.1/spec#E103) | 'Each version directory within an OCFL Object MUST conform to either the same or a later OCFL specification version as the preceding version directory.' | OCFL Object %s inventory conforms to specification version %s which is an earlier version than the %s inventory which conforms to specification version %s \[[ocfl/validator.py#L392](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L392)\] | | [E104](https://ocfl.io/1.1/spec#E104) | 'Version directory names MUST be constructed by prepending v to the version number.' | _Not implemented_ | | [E105](https://ocfl.io/1.1/spec#E105) | 'The version number MUST be taken from the sequence of positive, base-ten integers: 1, 2, 3, etc.' | _Not implemented_ | | [E106](https://ocfl.io/1.1/spec#E106) | 'The value of the manifest key MUST be a JSON object.' | _Not implemented_ | -| [E107](https://ocfl.io/1.1/spec#E107) | 'The value of the manifest key must be a JSON object, and each key MUST correspond to a digest value key found in one or more state blocks of the current and/or previous version blocks of the OCFL Object.' | **Missing description** \[[ocfl/inventory_validator.py#L515](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L515)\] | +| [E107](https://ocfl.io/1.1/spec#E107) | 'The value of the manifest key must be a JSON object, and each key MUST correspond to a digest value key found in one or more state blocks of the current and/or previous version blocks of the OCFL Object.' | **Missing description** \[[ocfl/inventory_validator.py#L516](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L516)\] | | [E108](https://ocfl.io/1.1/spec#E108) | 'The contentDirectory value MUST represent a direct child directory of the version directory in which it is found.' | _Not implemented_ | | [E110](https://ocfl.io/1.1/spec#E110) | 'A unique identifier for the OCFL Object MUST NOT change between versions of the same object.' | _Not implemented_ | | [E111](https://ocfl.io/1.1/spec#E111) | 'If present, [the value of the fixity key] MUST be a JSON object, which may be empty.' | OCFL Object %s inventory includes a fixity key with value that isn't a JSON object \[_Not implemented_\] | | [E112](https://ocfl.io/1.1/spec#E112) | 'The extensions directory must not contain any files or sub-directories other than extension sub-directories.' | _Not implemented_ | -| E999 | **Not in specification** | **Missing description** \[[ocfl/inventory_validator.py#L131](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L131)\] | ## Warnings | Code | Specification text (or suffixed code) | Implementation status and message/links | | --- | --- | --- | -| [W001](https://ocfl.io/1.1/spec#W001) | 'Implementations SHOULD use version directory names constructed without zero-padding the version number, ie. v1, v2, v3, etc.'' | OCFL Object %s inventory version numbers SHOULD NOT be zero-padded \[[ocfl/inventory_validator.py#L384](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L384)\] | -| [W002](https://ocfl.io/1.1/spec#W002) | 'The version directory SHOULD NOT contain any directories other than the designated content sub-directory. Once created, the contents of a version directory are expected to be immutable.' | OCFL Object version directory %s SHOULD NOT contain any directory except the designated content directory (found %s) \[[ocfl/validator.py#L424](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L424)\] | -| [W003](https://ocfl.io/1.1/spec#W003) | 'Version directories must contain a designated content sub-directory if the version contains files to be preserved, and SHOULD NOT contain this sub-directory otherwise.' | OCFL Object version directory %s SHOULD NOT contain an empty content directory \[[ocfl/validator.py#L422](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L422)\] | -| [W004](https://ocfl.io/1.1/spec#W004) | 'For content-addressing, OCFL Objects SHOULD use sha512.' | OCFL Object %s inventory SHOULD use sha512 but uses sha256 as the DigestAlgorithm \[[ocfl/inventory_validator.py#L152](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L152)\] | -| [W005](https://ocfl.io/1.1/spec#W005) | 'The OCFL Object Inventory id SHOULD be a URI.' | OCFL Object %s inventory id SHOULD be a URI (got %s) \[[ocfl/inventory_validator.py#L124](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L124)\] | +| [W001](https://ocfl.io/1.1/spec#W001) | 'Implementations SHOULD use version directory names constructed without zero-padding the version number, ie. v1, v2, v3, etc.'' | OCFL Object %s inventory version numbers SHOULD NOT be zero-padded \[[ocfl/inventory_validator.py#L385](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L385)\] | +| [W002](https://ocfl.io/1.1/spec#W002) | 'The version directory SHOULD NOT contain any directories other than the designated content sub-directory. Once created, the contents of a version directory are expected to be immutable.' | OCFL Object version directory %s SHOULD NOT contain any directory except the designated content directory (found %s) \[[ocfl/validator.py#L426](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L426)\] | +| [W003](https://ocfl.io/1.1/spec#W003) | 'Version directories must contain a designated content sub-directory if the version contains files to be preserved, and SHOULD NOT contain this sub-directory otherwise.' | OCFL Object version directory %s SHOULD NOT contain an empty content directory \[[ocfl/validator.py#L424](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L424)\] | +| [W004](https://ocfl.io/1.1/spec#W004) | 'For content-addressing, OCFL Objects SHOULD use sha512.' | OCFL Object %s inventory SHOULD use sha512 but uses sha256 as the DigestAlgorithm \[[ocfl/inventory_validator.py#L153](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L153)\] | +| [W005](https://ocfl.io/1.1/spec#W005) | 'The OCFL Object Inventory id SHOULD be a URI.' | OCFL Object %s inventory id SHOULD be a URI (got %s) \[[ocfl/inventory_validator.py#L125](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L125)\] | | [W007](https://ocfl.io/1.1/spec#W007) | 'In the OCFL Object Inventory, the JSON object describing an OCFL Version, SHOULD include the message and user keys.' | _See multiple cases identified with suffixes below_ | -| | W007a | OCFL Object %s inventory %s version block SHOULD include a message key \[[ocfl/inventory_validator.py#L438](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L438)\] | -| | W007b | OCFL Object %s inventory %s version block SHOULD include a user key \[[ocfl/inventory_validator.py#L442](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L442)\] | -| [W008](https://ocfl.io/1.1/spec#W008) | 'In the OCFL Object Inventory, in the version block, the value of the user key SHOULD contain an address key, address.' | OCFL Object %s inventory %s version block user description SHOULD have an address \[[ocfl/inventory_validator.py#L451](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L451)\] | -| [W009](https://ocfl.io/1.1/spec#W009) | 'In the OCFL Object Inventory, in the version block, the address value SHOULD be a URI: either a mailto URI [RFC6068] with the e-mail address of the user or a URL to a personal identifier, e.g., an ORCID iD.' | OCFL Object %s inventory %s version block user description SHOULD be a mailto: or person identifier URI \[[ocfl/inventory_validator.py#L455](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L455)\] | -| [W010](https://ocfl.io/1.1/spec#W010) | 'In addition to the inventory in the OCFL Object Root, every version directory SHOULD include an inventory file that is an Inventory of all content for versions up to and including that particular version.' | OCFL Object %s SHOULD have an inventory file but does not \[[ocfl/validator.py#L307](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L307)\] | -| [W011](https://ocfl.io/1.1/spec#W011) | 'In the case that prior version directories include an inventory file, the values of the created, message and user keys in each version block in each prior inventory file SHOULD have the same values as the corresponding keys in the corresponding version block in the current inventory file.' | OCFL Object version metadata '%s' for %s in %s inventory does not match that in %s inventory \[[ocfl/inventory_validator.py#L251](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L251)\] | +| | W007a | OCFL Object %s inventory %s version block SHOULD include a message key \[[ocfl/inventory_validator.py#L439](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L439)\] | +| | W007b | OCFL Object %s inventory %s version block SHOULD include a user key \[[ocfl/inventory_validator.py#L443](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L443)\] | +| [W008](https://ocfl.io/1.1/spec#W008) | 'In the OCFL Object Inventory, in the version block, the value of the user key SHOULD contain an address key, address.' | OCFL Object %s inventory %s version block user description SHOULD have an address \[[ocfl/inventory_validator.py#L452](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L452)\] | +| [W009](https://ocfl.io/1.1/spec#W009) | 'In the OCFL Object Inventory, in the version block, the address value SHOULD be a URI: either a mailto URI [RFC6068] with the e-mail address of the user or a URL to a personal identifier, e.g., an ORCID iD.' | OCFL Object %s inventory %s version block user description SHOULD be a mailto: or person identifier URI \[[ocfl/inventory_validator.py#L456](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L456)\] | +| [W010](https://ocfl.io/1.1/spec#W010) | 'In addition to the inventory in the OCFL Object Root, every version directory SHOULD include an inventory file that is an Inventory of all content for versions up to and including that particular version.' | OCFL Object %s SHOULD have an inventory file but does not \[[ocfl/validator.py#L309](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L309)\] | +| [W011](https://ocfl.io/1.1/spec#W011) | 'In the case that prior version directories include an inventory file, the values of the created, message and user keys in each version block in each prior inventory file SHOULD have the same values as the corresponding keys in the corresponding version block in the current inventory file.' | OCFL Object version metadata '%s' for %s in %s inventory does not match that in %s inventory \[[ocfl/inventory_validator.py#L252](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L252)\] | | [W012](https://ocfl.io/1.1/spec#W012) | 'Implementers SHOULD use the logs directory, if present, for storing files that contain a record of actions taken on the object.' | _Not implemented_ | -| [W013](https://ocfl.io/1.1/spec#W013) | 'In an OCFL Object, extension sub-directories SHOULD be named according to a registered extension name.' | OCFL Object includes unregistered extension directory '%s' \[[ocfl/validator.py#L282](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L282)\] | +| [W013](https://ocfl.io/1.1/spec#W013) | 'In an OCFL Object, extension sub-directories SHOULD be named according to a registered extension name.' | OCFL Object includes unregistered extension directory '%s' \[[ocfl/validator.py#L284](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L284)\] | | [W014](https://ocfl.io/1.1/spec#W014) | 'Storage hierarchies within the same OCFL Storage Root SHOULD use just one layout pattern.' | _Not implemented_ | | [W015](https://ocfl.io/1.1/spec#W015) | 'Storage hierarchies within the same OCFL Storage Root SHOULD consistently use either a directory hierarchy of OCFL Objects or top-level OCFL Objects.' | _Not implemented_ | | [W016](https://ocfl.io/1.1/spec#W016) | 'In the Storage Root, extension sub-directories SHOULD be named according to a registered extension name.' | _Not implemented_ | | W901 | **Not in specification** | OCFL Storage Root includes unregistered extension directory '%s' \[[ocfl/storage_root.py#L275](https://github.com/zimeon/ocfl-py/blob/main/ocfl/storage_root.py#L275)\] | -_Generated by `extract_codes.py` at 2024-12-06 00:46:27.116525_ \ No newline at end of file +_Generated by `extract_codes.py` at 2024-12-12 19:42:10.420730_ \ No newline at end of file From 91decc1fab6ad0f3e1b1509212dd1d2bd6364906 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 12 Dec 2024 21:25:32 +0000 Subject: [PATCH 08/10] Update error codes and demo docs [actions skip] --- docs/demo_using_bagit_bags.md | 27 +++++---------------------- docs/validation_status.md | 13 +------------ 2 files changed, 6 insertions(+), 34 deletions(-) diff --git a/docs/demo_using_bagit_bags.md b/docs/demo_using_bagit_bags.md index f8b17c2..6f5121f 100644 --- a/docs/demo_using_bagit_bags.md +++ b/docs/demo_using_bagit_bags.md @@ -63,11 +63,7 @@ INFO:bagit:Verifying checksum for file /home/runner/work/ocfl-py/ocfl-py/tests/t INFO:bagit:Verifying checksum for file /home/runner/work/ocfl-py/ocfl-py/tests/testdata/bags/uaa_v2/bag-info.txt INFO:bagit:Verifying checksum for file /home/runner/work/ocfl-py/ocfl-py/tests/testdata/bags/uaa_v2/manifest-sha512.txt INFO:root:Updated OCFL object info:bb123cd4567 by adding v2 -<<<<<<< HEAD -### -======= -### ->>>>>>> main +### Updated object info:bb123cd4567 to v2 ``` @@ -108,11 +104,7 @@ INFO:bagit:Verifying checksum for file /home/runner/work/ocfl-py/ocfl-py/tests/t INFO:bagit:Verifying checksum for file /home/runner/work/ocfl-py/ocfl-py/tests/testdata/bags/uaa_v3/bag-info.txt INFO:bagit:Verifying checksum for file /home/runner/work/ocfl-py/ocfl-py/tests/testdata/bags/uaa_v3/manifest-sha512.txt INFO:root:Updated OCFL object info:bb123cd4567 by adding v3 -<<<<<<< HEAD -### -======= -### ->>>>>>> main +### Updated object info:bb123cd4567 to v3 ``` @@ -158,11 +150,7 @@ INFO:bagit:Verifying checksum for file /home/runner/work/ocfl-py/ocfl-py/tests/t INFO:bagit:Verifying checksum for file /home/runner/work/ocfl-py/ocfl-py/tests/testdata/bags/uaa_v4/bag-info.txt INFO:bagit:Verifying checksum for file /home/runner/work/ocfl-py/ocfl-py/tests/testdata/bags/uaa_v4/manifest-sha512.txt INFO:root:Updated OCFL object info:bb123cd4567 by adding v4 -<<<<<<< HEAD -### -======= -### ->>>>>>> main +### Updated object info:bb123cd4567 to v4 ``` @@ -176,13 +164,8 @@ Taking the newly created OCFL object `/tmp/obj` we can `--extract` the `v4` cont INFO:root:Extracted v4 into tmp/extracted_v4 INFO:bagit:Creating bag for directory tmp/extracted_v4 INFO:bagit:Creating data directory -<<<<<<< HEAD -INFO:bagit:Moving my_content to tmp/extracted_v4/tmpeornl0iv/my_content -INFO:bagit:Moving tmp/extracted_v4/tmpeornl0iv to data -======= -INFO:bagit:Moving my_content to tmp/extracted_v4/tmpw3baynt4/my_content -INFO:bagit:Moving tmp/extracted_v4/tmpw3baynt4 to data ->>>>>>> main +INFO:bagit:Moving my_content to tmp/extracted_v4/tmppexdbf02/my_content +INFO:bagit:Moving tmp/extracted_v4/tmppexdbf02 to data INFO:bagit:Using 1 processes to generate manifests: sha512 INFO:bagit:Generating manifest lines for file data/my_content/dracula.txt INFO:bagit:Generating manifest lines for file data/my_content/dunwich.txt diff --git a/docs/validation_status.md b/docs/validation_status.md index 2258bd3..fe28362 100644 --- a/docs/validation_status.md +++ b/docs/validation_status.md @@ -65,17 +65,10 @@ The following tables show the implementation status of all errors and warnings i | | E037a | OCFL Object %s inventory `id` attribute is empty or badly formed \[[ocfl/inventory_validator.py#L120](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L120)\] | | | E037b | OCFL Object %s inventory id `%s` does not match the value in the root inventory `%s` \[[ocfl/validator.py#L335](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L335)\] | | [E038](https://ocfl.io/1.1/spec#E038) | 'In the object root inventory [the type value] must be the URI of the inventory section of the specification version matching the object conformance declaration.' | _See multiple cases identified with suffixes below_ | -<<<<<<< HEAD | | E038a | OCFL Object %s inventory `type` attribute has wrong value (expected %s, got %s) \[[ocfl/inventory_validator.py#L135](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L135)\] | | | E038b | OCFL Object %s inventory `type` attribute does not look like a valid specification URI (got %s), will proceed as if using version %s \[[ocfl/inventory_validator.py#L140](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L140)\] | | | E038c | OCFL Object %s inventory `type` attribute has an unsupported specification version number (%s), will proceed as if using version %s \[[ocfl/inventory_validator.py#L145](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L145)\] | | | E038d | OCFL Object %s inventory `type` attribute does not have a string value \[[ocfl/inventory_validator.py#L132](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L132)\] | -======= -| | E038a | OCFL Object %s inventory `type` attribute has wrong value (expected %s, got %s) \[[ocfl/inventory_validator.py#L134](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L134)\] | -| | E038b | OCFL Object %s inventory `type` attribute does not look like a valid specification URI (got %s), will proceed as if using version %s \[[ocfl/inventory_validator.py#L139](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L139)\] | -| | E038c | OCFL Object %s inventory `type` attribute has an unsupported specification version number (%s), will proceed as if using version %s \[[ocfl/inventory_validator.py#L144](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L144)\] | -| | E038d | OCFL Object %s inventory `type` attribute does not have a string value \[[ocfl/inventory_validator.py#L131](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L131)\] | ->>>>>>> main | [E039](https://ocfl.io/1.1/spec#E039) | '[digestAlgorithm] must be the algorithm used in the manifest and state blocks.' | _Not implemented_ | | [E040](https://ocfl.io/1.1/spec#E040) | [head] must be the version directory name with the highest version number.' | OCFL Object %s inventory head attribute doesn't match versions (got %s, expected %s) \[[ocfl/inventory_validator.py#L184](https://github.com/zimeon/ocfl-py/blob/main/ocfl/inventory_validator.py#L184)\] | | [E041](https://ocfl.io/1.1/spec#E041) | 'In addition to these keys, there must be two other blocks present, manifest and versions, which are discussed in the next two sections.' | _See multiple cases identified with suffixes below_ | @@ -213,8 +206,4 @@ The following tables show the implementation status of all errors and warnings i | [W016](https://ocfl.io/1.1/spec#W016) | 'In the Storage Root, extension sub-directories SHOULD be named according to a registered extension name.' | _Not implemented_ | | W901 | **Not in specification** | OCFL Storage Root includes unregistered extension directory '%s' \[[ocfl/storage_root.py#L275](https://github.com/zimeon/ocfl-py/blob/main/ocfl/storage_root.py#L275)\] | -<<<<<<< HEAD -_Generated by `extract_codes.py` at 2024-12-12 19:42:10.420730_ -======= -_Generated by `extract_codes.py` at 2024-12-12 18:50:33.443332_ ->>>>>>> main +_Generated by `extract_codes.py` at 2024-12-12 21:25:17.976772_ \ No newline at end of file From 3f7e8992a1380a9a974a8f4c2c822c9b4c20a096 Mon Sep 17 00:00:00 2001 From: Simeon Warner Date: Thu, 12 Dec 2024 17:01:11 -0500 Subject: [PATCH 09/10] Remove old debugging statement --- ocfl/object.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ocfl/object.py b/ocfl/object.py index 60ba818..1a62b55 100755 --- a/ocfl/object.py +++ b/ocfl/object.py @@ -383,7 +383,6 @@ def add_version_with_content(self, objdir="", srcdir=None, metadata=None): settings (such as using a new digest). There will be no content change between versions. """ - print("### " + str(metadata)) nv = self.start_new_version(objdir=objdir, srcdir=srcdir, digest_algorithm=self.digest_algorithm, From f88525985a0dea046fe8bb1f03d025b206bd15a8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 12 Dec 2024 22:03:08 +0000 Subject: [PATCH 10/10] Update error codes and demo docs [actions skip] --- docs/demo_using_bagit_bags.md | 7 ++----- docs/validation_status.md | 4 ++-- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/docs/demo_using_bagit_bags.md b/docs/demo_using_bagit_bags.md index 6f5121f..8e97e43 100644 --- a/docs/demo_using_bagit_bags.md +++ b/docs/demo_using_bagit_bags.md @@ -63,7 +63,6 @@ INFO:bagit:Verifying checksum for file /home/runner/work/ocfl-py/ocfl-py/tests/t INFO:bagit:Verifying checksum for file /home/runner/work/ocfl-py/ocfl-py/tests/testdata/bags/uaa_v2/bag-info.txt INFO:bagit:Verifying checksum for file /home/runner/work/ocfl-py/ocfl-py/tests/testdata/bags/uaa_v2/manifest-sha512.txt INFO:root:Updated OCFL object info:bb123cd4567 by adding v2 -### Updated object info:bb123cd4567 to v2 ``` @@ -104,7 +103,6 @@ INFO:bagit:Verifying checksum for file /home/runner/work/ocfl-py/ocfl-py/tests/t INFO:bagit:Verifying checksum for file /home/runner/work/ocfl-py/ocfl-py/tests/testdata/bags/uaa_v3/bag-info.txt INFO:bagit:Verifying checksum for file /home/runner/work/ocfl-py/ocfl-py/tests/testdata/bags/uaa_v3/manifest-sha512.txt INFO:root:Updated OCFL object info:bb123cd4567 by adding v3 -### Updated object info:bb123cd4567 to v3 ``` @@ -150,7 +148,6 @@ INFO:bagit:Verifying checksum for file /home/runner/work/ocfl-py/ocfl-py/tests/t INFO:bagit:Verifying checksum for file /home/runner/work/ocfl-py/ocfl-py/tests/testdata/bags/uaa_v4/bag-info.txt INFO:bagit:Verifying checksum for file /home/runner/work/ocfl-py/ocfl-py/tests/testdata/bags/uaa_v4/manifest-sha512.txt INFO:root:Updated OCFL object info:bb123cd4567 by adding v4 -### Updated object info:bb123cd4567 to v4 ``` @@ -164,8 +161,8 @@ Taking the newly created OCFL object `/tmp/obj` we can `--extract` the `v4` cont INFO:root:Extracted v4 into tmp/extracted_v4 INFO:bagit:Creating bag for directory tmp/extracted_v4 INFO:bagit:Creating data directory -INFO:bagit:Moving my_content to tmp/extracted_v4/tmppexdbf02/my_content -INFO:bagit:Moving tmp/extracted_v4/tmppexdbf02 to data +INFO:bagit:Moving my_content to tmp/extracted_v4/tmp0odx3wcl/my_content +INFO:bagit:Moving tmp/extracted_v4/tmp0odx3wcl to data INFO:bagit:Using 1 processes to generate manifests: sha512 INFO:bagit:Generating manifest lines for file data/my_content/dracula.txt INFO:bagit:Generating manifest lines for file data/my_content/dunwich.txt diff --git a/docs/validation_status.md b/docs/validation_status.md index fe28362..cdf8159 100644 --- a/docs/validation_status.md +++ b/docs/validation_status.md @@ -53,7 +53,7 @@ The following tables show the implementation status of all errors and warnings i | [E030](https://ocfl.io/1.1/spec#E030) | 'SHA-256 algorithm defined by [FIPS-180-4] and must be encoded using hex (base16) encoding [RFC4648].' | _Not implemented_ | | [E031](https://ocfl.io/1.1/spec#E031) | 'SHA-512 algorithm defined by [FIPS-180-4] and must be encoded using hex (base16) encoding [RFC4648].' | _Not implemented_ | | [E032](https://ocfl.io/1.1/spec#E032) | '[blake2b-512] must be encoded using hex (base16) encoding [RFC4648].' | _Not implemented_ | -| [E033](https://ocfl.io/1.1/spec#E033) | 'An OCFL Object Inventory MUST follow the [JSON] structure described in this section and must be named inventory.json.' | OCFL Object %s inventory is not valid JSON (%s) \[[ocfl/object.py#L645](https://github.com/zimeon/ocfl-py/blob/main/ocfl/object.py#L645) [ocfl/validator.py#L203](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L203)\] | +| [E033](https://ocfl.io/1.1/spec#E033) | 'An OCFL Object Inventory MUST follow the [JSON] structure described in this section and must be named inventory.json.' | OCFL Object %s inventory is not valid JSON (%s) \[[ocfl/object.py#L644](https://github.com/zimeon/ocfl-py/blob/main/ocfl/object.py#L644) [ocfl/validator.py#L203](https://github.com/zimeon/ocfl-py/blob/main/ocfl/validator.py#L203)\] | | [E034](https://ocfl.io/1.1/spec#E034) | 'An OCFL Object Inventory must follow the [JSON] structure described in this section and MUST be named inventory.json.' | _Not implemented_ | | [E035](https://ocfl.io/1.1/spec#E035) | 'The forward slash (/) path separator must be used in content paths in the manifest and fixity blocks within the inventory.' | _Not implemented_ | | [E036](https://ocfl.io/1.1/spec#E036) | 'An OCFL Object Inventory must include the following keys: [id, type, digestAlgorithm, head]' | _See multiple cases identified with suffixes below_ | @@ -206,4 +206,4 @@ The following tables show the implementation status of all errors and warnings i | [W016](https://ocfl.io/1.1/spec#W016) | 'In the Storage Root, extension sub-directories SHOULD be named according to a registered extension name.' | _Not implemented_ | | W901 | **Not in specification** | OCFL Storage Root includes unregistered extension directory '%s' \[[ocfl/storage_root.py#L275](https://github.com/zimeon/ocfl-py/blob/main/ocfl/storage_root.py#L275)\] | -_Generated by `extract_codes.py` at 2024-12-12 21:25:17.976772_ \ No newline at end of file +_Generated by `extract_codes.py` at 2024-12-12 22:02:54.146470_ \ No newline at end of file