From 76b4b33324a0c553c3b0d6845958bdb7e8adf672 Mon Sep 17 00:00:00 2001 From: Marco Enrico Piras Date: Tue, 29 Oct 2024 10:24:04 +0100 Subject: [PATCH 01/15] refactor(core): :recycle: determine the issue level and severity based on the check associated with the issue --- rocrate_validator/models.py | 22 +++++-------------- .../requirements/shacl/checks.py | 1 - 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/rocrate_validator/models.py b/rocrate_validator/models.py index 4e6fa410..55d97897 100644 --- a/rocrate_validator/models.py +++ b/rocrate_validator/models.py @@ -942,18 +942,12 @@ class CheckIssue: check (RequirementCheck): The check that generated the issue """ - # TODO: - # 2. CheckIssue has the check, so it is able to determine the level and the Severity - # without having it provided through an additional argument. - def __init__(self, severity: Severity, + def __init__(self, check: RequirementCheck, message: Optional[str] = None, resultPath: Optional[str] = None, focusNode: Optional[str] = None, value: Optional[str] = None): - if not isinstance(severity, Severity): - raise TypeError(f"CheckIssue constructed with a severity '{severity}' of type {type(severity)}") - self._severity = severity self._message = message self._check: RequirementCheck = check self._resultPath = resultPath @@ -973,7 +967,7 @@ def level(self) -> RequirementLevel: @property def severity(self) -> Severity: """Severity of the RequirementLevel associated with this check.""" - return self._severity + return self._check.severity @property def level_name(self) -> str: @@ -999,16 +993,15 @@ def value(self) -> Optional[str]: def __eq__(self, other: object) -> bool: return isinstance(other, CheckIssue) and \ self._check == other._check and \ - self._severity == other._severity and \ self._message == other._message def __lt__(self, other: object) -> bool: if not isinstance(other, CheckIssue): raise TypeError(f"Cannot compare {type(self)} with {type(other)}") - return (self._check, self._severity, self._message) < (other._check, other._severity, other._message) + return (self._check, self._message) < (other._check, other._message) def __hash__(self) -> int: - return hash((self._check, self._severity, self._message)) + return hash((self._check, self._message)) def __repr__(self) -> str: return f'CheckIssue(severity={self.severity}, check={self.check}, message={self.message})' @@ -1139,18 +1132,15 @@ def add_issue(self, issue: CheckIssue): def add_check_issue(self, message: str, check: RequirementCheck, - severity: Optional[Severity] = None, resultPath: Optional[str] = None, focusNode: Optional[str] = None, value: Optional[str] = None) -> CheckIssue: - sev_value = severity if severity is not None else check.severity - c = CheckIssue(sev_value, check, message, resultPath=resultPath, focusNode=focusNode, value=value) - # self._issues.append(c) + c = CheckIssue(check, message, resultPath=resultPath, focusNode=focusNode, value=value) bisect.insort(self._issues, c) return c def add_error(self, message: str, check: RequirementCheck) -> CheckIssue: - return self.add_check_issue(message, check, Severity.REQUIRED) + return self.add_check_issue(message, check) # --- Requirements --- @property diff --git a/rocrate_validator/requirements/shacl/checks.py b/rocrate_validator/requirements/shacl/checks.py index b23e3033..e910a878 100644 --- a/rocrate_validator/requirements/shacl/checks.py +++ b/rocrate_validator/requirements/shacl/checks.py @@ -201,7 +201,6 @@ def __do_execute_check__(self, shacl_context: SHACLValidationContext): c = shacl_context.result.add_check_issue( message=violation.get_result_message(shacl_context.rocrate_path), check=requirementCheck, - severity=violation.get_result_severity(), resultPath=violation.resultPath.toPython() if violation.resultPath else None, focusNode=make_uris_relative( violation.focusNode.toPython(), shacl_context.publicID), From 4350038fb6a7d9d3152eee365f000d564b2240cf Mon Sep 17 00:00:00 2001 From: Marco Enrico Piras Date: Tue, 29 Oct 2024 10:29:18 +0100 Subject: [PATCH 02/15] fix(core): :bug: fix severity detection of Python checks --- rocrate_validator/requirements/python/__init__.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/rocrate_validator/requirements/python/__init__.py b/rocrate_validator/requirements/python/__init__.py index fa927524..975fb4c6 100644 --- a/rocrate_validator/requirements/python/__init__.py +++ b/rocrate_validator/requirements/python/__init__.py @@ -84,8 +84,14 @@ def __init_checks__(self): severity = None try: severity = member.severity + logger.debug("Severity set for check '%r' from decorator: %r", check_name, severity) except Exception: + pass + if not severity: + logger.debug(f"No explicit severity set for check '{check_name}' from decorator." + f"Getting severity from path: {self.severity_from_path}") severity = self.severity_from_path or Severity.REQUIRED + logger.error("Severity log: %r", severity) check = self.requirement_check_class(self, check_name, member, From 3e19a5d02871d1b0ed67c261ef227b3435730baf Mon Sep 17 00:00:00 2001 From: Marco Enrico Piras Date: Tue, 29 Oct 2024 10:34:10 +0100 Subject: [PATCH 03/15] chore(tests): :pencil2: fix typo --- tests/unit/requirements/test_profiles.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/requirements/test_profiles.py b/tests/unit/requirements/test_profiles.py index 1fc4bb8b..46c9f122 100644 --- a/tests/unit/requirements/test_profiles.py +++ b/tests/unit/requirements/test_profiles.py @@ -405,7 +405,7 @@ def check_profile(profile, check, inherited_profiles, overridden_by, override): # Get the check check = requirement.get_checks()[0] - logger.debug("The check: %r of requirement %r of the profiles %f", check, requirement, profile.token) + logger.debug("The check: %r of requirement %r of the profiles %s", check, requirement, profile.token) # Check the profile 'a' if profile.token == "a": From bb54c11e26d2df6a4bb2c78a35208ecf84e9f3d0 Mon Sep 17 00:00:00 2001 From: Marco Enrico Piras Date: Tue, 29 Oct 2024 10:37:17 +0100 Subject: [PATCH 04/15] test(utils): :bug: use a parametric severity level in the shared test function --- tests/shared.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/shared.py b/tests/shared.py index 9d6cd3a0..5e353dc9 100644 --- a/tests/shared.py +++ b/tests/shared.py @@ -93,7 +93,7 @@ def do_entity_test( f"\"{expected_triggered_requirement}\" was not found in the failed requirements" # check requirement issues - detected_issues = [issue.message for issue in result.get_issues(models.Severity.RECOMMENDED) + detected_issues = [issue.message for issue in result.get_issues(requirement_severity) if issue.message is not None] logger.debug("Detected issues: %s", detected_issues) logger.debug("Expected issues: %s", expected_triggered_issues) From ce00b36a98093b5755ea2e2b8ebcf00f940d860e Mon Sep 17 00:00:00 2001 From: Marco Enrico Piras Date: Tue, 29 Oct 2024 10:43:32 +0100 Subject: [PATCH 05/15] chore(logging): :loud_sound: fix level of a log message --- rocrate_validator/requirements/python/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rocrate_validator/requirements/python/__init__.py b/rocrate_validator/requirements/python/__init__.py index 975fb4c6..821e44d3 100644 --- a/rocrate_validator/requirements/python/__init__.py +++ b/rocrate_validator/requirements/python/__init__.py @@ -91,7 +91,7 @@ def __init_checks__(self): logger.debug(f"No explicit severity set for check '{check_name}' from decorator." f"Getting severity from path: {self.severity_from_path}") severity = self.severity_from_path or Severity.REQUIRED - logger.error("Severity log: %r", severity) + logger.debug("Severity log: %r", severity) check = self.requirement_check_class(self, check_name, member, From f16645d9922571b0c3885ca24c90a5761c242215 Mon Sep 17 00:00:00 2001 From: Marco Enrico Piras Date: Tue, 29 Oct 2024 10:47:00 +0100 Subject: [PATCH 06/15] fix(profiles/workflow-run-crate): :bug: fix missing f-string formatting --- .../profiles/workflow-ro-crate/must/0_main_workflow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rocrate_validator/profiles/workflow-ro-crate/must/0_main_workflow.py b/rocrate_validator/profiles/workflow-ro-crate/must/0_main_workflow.py index 18fbc0d6..c47220c8 100644 --- a/rocrate_validator/profiles/workflow-ro-crate/must/0_main_workflow.py +++ b/rocrate_validator/profiles/workflow-ro-crate/must/0_main_workflow.py @@ -34,7 +34,7 @@ def check_workflow(self, context: ValidationContext) -> bool: context.result.add_check_issue("main workflow does not exist in metadata file", self) return False if not main_workflow.is_available(): - context.result.add_check_issue("Main Workflow {main_workflow.id} not found in crate", self) + context.result.add_check_issue(f"Main Workflow {main_workflow.id} not found in crate", self) return False return True except ValueError as e: From 552a362aa8ca94a4ad596db51fc0087cbb0847d2 Mon Sep 17 00:00:00 2001 From: Marco Enrico Piras Date: Tue, 29 Oct 2024 11:20:05 +0100 Subject: [PATCH 07/15] feat(core): :sparkles: add minimal dict serializers for the Profile and RequirementCheck models --- rocrate_validator/models.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/rocrate_validator/models.py b/rocrate_validator/models.py index 55d97897..a60c887b 100644 --- a/rocrate_validator/models.py +++ b/rocrate_validator/models.py @@ -388,6 +388,14 @@ def __repr__(self) -> str: def __str__(self) -> str: return f"{self.name} ({self.identifier})" + def to_dict(self) -> dict: + return { + "identifier": self.identifier, + "uri": self.uri, + "name": self.name, + "description": self.description + } + @staticmethod def __extract_version_from_token__(token: str) -> Optional[str]: if not token: @@ -893,6 +901,16 @@ def overridden(self) -> bool: def execute_check(self, context: ValidationContext) -> bool: raise NotImplementedError() + def to_dict(self) -> dict: + return { + "identifier": self.relative_identifier, + "name": self.name, + "description": self.description, + "level": self.level.name, + "severity": self.severity.name, + "profile": self.requirement.profile.to_dict() + } + def __eq__(self, other: object) -> bool: if not isinstance(other, RequirementCheck): raise ValueError(f"Cannot compare RequirementCheck with {type(other)}") From d4757054375c2aedd5c79963b75de9c9556718cf Mon Sep 17 00:00:00 2001 From: Marco Enrico Piras Date: Tue, 29 Oct 2024 11:21:27 +0100 Subject: [PATCH 08/15] feat(core): :sparkles: extend check info on JSON output --- rocrate_validator/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rocrate_validator/models.py b/rocrate_validator/models.py index a60c887b..96b36afd 100644 --- a/rocrate_validator/models.py +++ b/rocrate_validator/models.py @@ -1031,7 +1031,7 @@ def to_dict(self) -> dict: return { "severity": self.severity.name, "message": self.message, - "check": self.check.name, + "check": self.check.to_dict(), "resultPath": self.resultPath, "focusNode": self.focusNode, "value": self.value From 8d76f9e6d86998f1344174b587fa641f7f54999b Mon Sep 17 00:00:00 2001 From: Marco Enrico Piras Date: Wed, 30 Oct 2024 11:27:05 +0100 Subject: [PATCH 09/15] refactor(shacl): :recycle: expose `node_name` getter --- rocrate_validator/requirements/shacl/models.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/rocrate_validator/requirements/shacl/models.py b/rocrate_validator/requirements/shacl/models.py index f0412426..6b4b58a4 100644 --- a/rocrate_validator/requirements/shacl/models.py +++ b/rocrate_validator/requirements/shacl/models.py @@ -58,7 +58,7 @@ def __init__(self, node: Node, graph: Graph, parent: Optional[SHACLNode] = None) def name(self) -> str: """Return the name of the shape""" if not self._name: - self._name = self._node.split("#")[-1] if "#" in self.node else self._node.split("/")[-1] + self._name = self.node_name return self._name or self._node.split("/")[-1] @name.setter @@ -86,6 +86,11 @@ def node(self): """Return the node of the shape""" return self._node + @property + def node_name(self): + """Return the name of the node""" + return self._node.split("#")[-1] if "#" in self.node else self._node.split("/")[-1] + @property def graph(self): """Return the subgraph of the shape""" From cc2a8add26686039af31aba95fca9ab60aeae4dd Mon Sep 17 00:00:00 2001 From: Marco Enrico Piras Date: Wed, 30 Oct 2024 11:32:04 +0100 Subject: [PATCH 10/15] feat(core): :zap: add `to_dict` serializer methods --- rocrate_validator/models.py | 49 ++++++++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 11 deletions(-) diff --git a/rocrate_validator/models.py b/rocrate_validator/models.py index 96b36afd..15a98643 100644 --- a/rocrate_validator/models.py +++ b/rocrate_validator/models.py @@ -736,6 +736,19 @@ def __repr__(self): def __str__(self) -> str: return self.name + def to_dict(self, with_profile: bool = True, with_checks: bool = True) -> dict: + result = { + "identifier": self.identifier, + "name": self.name, + "description": self.description, + "order": self.order_number + } + if with_profile: + result["profile"] = self.profile.to_dict() + if with_checks: + result["checks"] = [_.to_dict(with_requirement=False, with_profile=False) for _ in self._checks] + return result + class RequirementLoader: @@ -901,15 +914,18 @@ def overridden(self) -> bool: def execute_check(self, context: ValidationContext) -> bool: raise NotImplementedError() - def to_dict(self) -> dict: - return { - "identifier": self.relative_identifier, + def to_dict(self, with_requirement: bool = True, with_profile: bool = True) -> dict: + result = { + "identifier": self.identifier, + "label": self.relative_identifier, + "order": self.order_number, "name": self.name, "description": self.description, - "level": self.level.name, - "severity": self.severity.name, - "profile": self.requirement.profile.to_dict() + "severity": self.severity.name } + if with_requirement: + result["requirement"] = self.requirement.to_dict(with_profile=with_profile, with_checks=False) + return result def __eq__(self, other: object) -> bool: if not isinstance(other, RequirementCheck): @@ -1027,18 +1043,29 @@ def __repr__(self) -> str: def __str__(self) -> str: return f"{self.severity}: {self.message} ({self.check})" - def to_dict(self) -> dict: - return { + def to_dict(self, with_check: bool = True, + with_requirement: bool = True, with_profile: bool = True) -> dict: + result = { "severity": self.severity.name, "message": self.message, - "check": self.check.to_dict(), "resultPath": self.resultPath, "focusNode": self.focusNode, "value": self.value } + if with_check: + result["check"] = self.check.to_dict(with_requirement=with_requirement, with_profile=with_profile) + return result - def to_json(self) -> str: - return json.dumps(self.to_dict(), indent=4, cls=CustomEncoder) + def to_json(self, + with_checks: bool = True, + with_requirements: bool = True, + with_profile: bool = True) -> str: + return json.dumps( + self.to_dict( + with_check=with_checks, + with_requirement=with_requirements, + with_profile=with_profile + ), indent=4, cls=CustomEncoder) # @property # def code(self) -> int: From e4aa11054a9b979d888ba78865a48bb3e32b1c38 Mon Sep 17 00:00:00 2001 From: Marco Enrico Piras Date: Wed, 30 Oct 2024 12:42:05 +0100 Subject: [PATCH 11/15] refactor(core): :recycle: remove `resultPath` from issue serialisation --- rocrate_validator/models.py | 1 - 1 file changed, 1 deletion(-) diff --git a/rocrate_validator/models.py b/rocrate_validator/models.py index 15a98643..4e4dfaec 100644 --- a/rocrate_validator/models.py +++ b/rocrate_validator/models.py @@ -1048,7 +1048,6 @@ def to_dict(self, with_check: bool = True, result = { "severity": self.severity.name, "message": self.message, - "resultPath": self.resultPath, "focusNode": self.focusNode, "value": self.value } From ff1f392755d9842c45deee0acc87da6b742086b7 Mon Sep 17 00:00:00 2001 From: Marco Enrico Piras Date: Wed, 30 Oct 2024 16:32:26 +0100 Subject: [PATCH 12/15] refactor(core): :fire: remove `data_path` e `profiles_path` from JSON output --- rocrate_validator/models.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/rocrate_validator/models.py b/rocrate_validator/models.py index 4e4dfaec..8a71ddb8 100644 --- a/rocrate_validator/models.py +++ b/rocrate_validator/models.py @@ -1217,8 +1217,7 @@ def __eq__(self, other: object) -> bool: return self._issues == other._issues def to_dict(self) -> dict: - allowed_properties = ["data_path", "profiles_path", - "profile_identifier", "inherit_profiles", "requirement_severity", "abort_on_first"] + allowed_properties = ["profile_identifier", "inherit_profiles", "requirement_severity", "abort_on_first"] return { "rocrate": str(self.rocrate_path), "validation_settings": {key: self.validation_settings[key] From 7ff6fb11ac7f362c53ea759e067320da7b383a6d Mon Sep 17 00:00:00 2001 From: Marco Enrico Piras Date: Wed, 30 Oct 2024 16:33:16 +0100 Subject: [PATCH 13/15] refactor(core): :fire: remove `rocrate` path property from JSON output --- rocrate_validator/models.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/rocrate_validator/models.py b/rocrate_validator/models.py index 8a71ddb8..65559c25 100644 --- a/rocrate_validator/models.py +++ b/rocrate_validator/models.py @@ -1218,8 +1218,7 @@ def __eq__(self, other: object) -> bool: def to_dict(self) -> dict: allowed_properties = ["profile_identifier", "inherit_profiles", "requirement_severity", "abort_on_first"] - return { - "rocrate": str(self.rocrate_path), + result = { "validation_settings": {key: self.validation_settings[key] for key in allowed_properties if key in self.validation_settings}, "passed": self.passed(self.context.settings["requirement_severity"]), From c6b8abaec767d5a0476acbcfdbbe8bb94d7b5c7d Mon Sep 17 00:00:00 2001 From: Marco Enrico Piras Date: Wed, 30 Oct 2024 16:42:31 +0100 Subject: [PATCH 14/15] refactor(core): :zap: add rocrate-validator version to the JSON ouput --- rocrate_validator/models.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rocrate_validator/models.py b/rocrate_validator/models.py index 65559c25..42387339 100644 --- a/rocrate_validator/models.py +++ b/rocrate_validator/models.py @@ -29,6 +29,7 @@ from rdflib import RDF, RDFS, Graph, Namespace, URIRef import rocrate_validator.log as logging +from rocrate_validator import __version__ from rocrate_validator.constants import (DEFAULT_ONTOLOGY_FILE, DEFAULT_PROFILE_IDENTIFIER, DEFAULT_PROFILE_README_FILE, @@ -1224,6 +1225,9 @@ def to_dict(self) -> dict: "passed": self.passed(self.context.settings["requirement_severity"]), "issues": [issue.to_dict() for issue in self.issues] } + # add validator version to the settings + result["validation_settings"]["rocrate-validator-version"] = __version__ + return result def to_json(self, path: Optional[Path] = None) -> str: From e6f2a69386d7bf75ed9d79d002badb42aa4d0b75 Mon Sep 17 00:00:00 2001 From: Marco Enrico Piras Date: Wed, 30 Oct 2024 16:53:02 +0100 Subject: [PATCH 15/15] =?UTF-8?q?=F0=9F=94=96=20bump=20version=20number=20?= =?UTF-8?q?to=200.4.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 725802f3..a09614e2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "roc-validator" -version = "0.4.0" +version = "0.4.1" description = "A Python package to validate RO-Crates" authors = [ "Marco Enrico Piras ",