From ece3a0b68d97f367d4df8cdabdf328a88211815b Mon Sep 17 00:00:00 2001 From: Simeon Warner Date: Thu, 24 Oct 2024 14:23:30 -0400 Subject: [PATCH] Set logger spec_version so links will be to correct spec --- ocfl/inventory_validator.py | 2 ++ ocfl/storage_root.py | 1 + ocfl/validation_logger.py | 39 ++++++++++++++++++++----- ocfl/validator.py | 5 +++- tests/test_demo_ocfl_validate_script.py | 24 +++++++++++---- 5 files changed, 57 insertions(+), 14 deletions(-) diff --git a/ocfl/inventory_validator.py b/ocfl/inventory_validator.py index e5122eba..459beafd 100644 --- a/ocfl/inventory_validator.py +++ b/ocfl/inventory_validator.py @@ -77,6 +77,7 @@ def validate(self, inventory, force_spec_version=None): # Basic structure self.inventory = inventory self.spec_version = self.default_spec_version if force_spec_version is None else force_spec_version + self.log.spec_version = self.spec_version if 'id' in inventory: iid = inventory['id'] if not isinstance(iid, str) or iid == '': @@ -103,6 +104,7 @@ def validate(self, inventory, force_spec_version=None): self.error('E038b', got=inventory['type'], assumed_spec_version=self.spec_version) elif m.group(1) in self.spec_versions_supported: self.spec_version = m.group(1) + self.log.spec_version = self.spec_version else: self.error("E038c", got=m.group(1), assumed_spec_version=self.spec_version) if 'digestAlgorithm' not in inventory: diff --git a/ocfl/storage_root.py b/ocfl/storage_root.py index b0d988da..d2f68924 100644 --- a/ocfl/storage_root.py +++ b/ocfl/storage_root.py @@ -362,6 +362,7 @@ def validate(self, *, validate_objects=True, check_digests=True, valid = False self.structure_error = str(e) logging.debug("Storage root structure is INVALID (%s)", str(e)) + self.log.spec_version = self.spec_version self.num_objects, self.good_objects, self.errors = self.validate_hierarchy(validate_objects=validate_objects, check_digests=check_digests, log_warnings=log_warnings, max_errors=max_errors) if self.num_traversal_errors > 0: valid = False diff --git a/ocfl/validation_logger.py b/ocfl/validation_logger.py index d9aacb44..deec7ad3 100644 --- a/ocfl/validation_logger.py +++ b/ocfl/validation_logger.py @@ -40,21 +40,44 @@ class ValidationLogger(): validation_codes = None def __init__(self, log_warnings=False, log_errors=True, - lang='en', validation_codes=None): - """Initialize OCFL validation logger.""" + spec_version="1.1", lang="en", validation_codes=None): + """Initialize OCFL validation logger. + + Arguments: + log_warnings (bool): True to log warnings via the + warning() method. Default False. + log_errors (bool): True to logs errors via the error() + method. Default True. + spec_version (str): Specification version being validated + against, default "1.1". + lang (str): Language code to look up description strings + 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. + """ self.log_warnings = log_warnings self.log_errors = log_errors + self.spec_version = spec_version self.lang = lang - self.codes = {} - self.messages = [] - self.num_errors = 0 - self.num_warnings = 0 - self.spec = 'https://ocfl.io/1.0/spec/' if validation_codes is not None: self.validation_codes = validation_codes elif self.validation_codes is None: with open(os.path.join(os.path.dirname(__file__), 'data/validation-errors.json'), 'r', encoding="utf-8") as fh: self.validation_codes = json.load(fh) + # Instance variables to accumulate log data + self.codes = {} + self.messages = [] + self.num_errors = 0 + self.num_warnings = 0 + + @property + def spec_url(self): + """Link to the relevant specification version.""" + return "https://ocfl.io/" + self.spec_version + "/spec/" def log(self, code, is_error, **args): """Log either an error or a warning. @@ -100,7 +123,7 @@ def log(self, code, is_error, **args): # Add link to spec m = re.match(r'''([EW](\d\d\d))''', code) if m and int(m.group(2)) < 200: - message += ' (see ' + self.spec + '#' + m.group(1) + ')' + message += ' (see ' + self.spec_url + '#' + m.group(1) + ')' # Store set of codes with last message for that code, and _full_ list of messages self.codes[code] = message if (is_error and self.log_errors) or (not is_error and self.log_warnings): diff --git a/ocfl/validator.py b/ocfl/validator.py index 1b6e4353..8ad6a92e 100644 --- a/ocfl/validator.py +++ b/ocfl/validator.py @@ -50,7 +50,8 @@ def __init__(self, *, log_warnings=False, log_errors=True, default_spec_version: string of default specification version to assume where not specified (default '1.1') log: None (default) to create new ValidationLogger instance, or - else an instance to use. + 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. """ @@ -86,6 +87,7 @@ def initialize(self): """ self.id = None self.spec_version = self.default_spec_version + self.log.spec_version = self.spec_version self.digest_algorithm = 'sha512' self.content_directory = 'content' self.inventory_digest_files = {} # index by version_dir, algorithms may differ @@ -142,6 +144,7 @@ def validate_object(self, path): self.log.error('E003c', assumed_version=self.spec_version) else: self.spec_version = spec_version + self.log.spec_version = self.spec_version if len(namastes) > 1: self.log.error('E003b', files=len(namastes), using_version=self.spec_version) # Object root inventory file diff --git a/tests/test_demo_ocfl_validate_script.py b/tests/test_demo_ocfl_validate_script.py index bac578dc..81ad28a7 100644 --- a/tests/test_demo_ocfl_validate_script.py +++ b/tests/test_demo_ocfl_validate_script.py @@ -22,16 +22,30 @@ def test01_good(self): def test02_warnings(self): """Test warning cases.""" - out = self.run_script("Warning test with -q", + out = self.run_script("Warning test for a v1.0 object", ["python", "ocfl-validate.py", - "-q", "fixtures/1.0/warn-objects/W004_uses_sha256"]) + "fixtures/1.0/warn-objects/W004_uses_sha256"], + text="The test shows warning W004 with a link to the v1.0 specification") self.assertIn("fixtures/1.0/warn-objects/W004_uses_sha256 is VALID", out) - self.assertNotIn("[W004]", out) - out = self.run_script("Warning test without -q", + self.assertIn("[W004]", out) + self.assertIn("https://ocfl.io/1.0/spec/#W004", out) + # + out = self.run_script("Warning test with -q (--quiet) flag", ["python", "ocfl-validate.py", - "fixtures/1.0/warn-objects/W004_uses_sha256"]) + "-q", "fixtures/1.0/warn-objects/W004_uses_sha256"], + text="The -q or --quiet flag will silence any warning messages") self.assertIn("fixtures/1.0/warn-objects/W004_uses_sha256 is VALID", out) + self.assertNotIn("[W004]", out) + # + out = self.run_script("Warning test for a v1.1 object with several warnings", + ["python", "ocfl-validate.py", + "fixtures/1.1/warn-objects/W001_W004_W005_zero_padded_versions"], + text="The test shows warning W004 with a link to the v1.0 specification") + self.assertIn("fixtures/1.1/warn-objects/W001_W004_W005_zero_padded_versions is VALID", out) + self.assertIn("[W001]", out) self.assertIn("[W004]", out) + self.assertIn("[W005]", out) + self.assertIn("https://ocfl.io/1.1/spec/#W001", out) if __name__ == "__main__":