Skip to content

Commit 97bcd8b

Browse files
committed
release: 🔖 version 0.6.5
2 parents 9fe1a71 + ac14080 commit 97bcd8b

File tree

5 files changed

+155
-18
lines changed

5 files changed

+155
-18
lines changed

‎pyproject.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "roc-validator"
3-
version = "0.6.4"
3+
version = "0.6.5"
44
description = "A Python package to validate RO-Crates"
55
authors = [
66
"Marco Enrico Piras <[email protected]>",

‎rocrate_validator/cli/commands/validate.py‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,11 @@ def update(self, event: Event):
558558
else:
559559
self._stats["failed_checks"].append(event.requirement_check)
560560
self.layout.update(self._stats)
561+
else:
562+
logger.debug("Requirement check validation result is None: %s",
563+
event.requirement_check.identifier)
564+
else:
565+
logger.debug("Skipping requirement check validation: %s", event.requirement_check.identifier)
561566
elif event.event_type == EventType.REQUIREMENT_VALIDATION_END:
562567
if not event.requirement.hidden:
563568
self.progress.update(task_id=self.requirement_validation, advance=1)

‎rocrate_validator/events.py‎

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@
1919

2020
import enum_tools
2121

22+
import rocrate_validator.log as logging
23+
24+
# Set up logging
25+
logger = logging.getLogger(__name__)
26+
2227

2328
@enum.unique
2429
@enum_tools.documentation.document_enum
@@ -67,6 +72,59 @@ def __init__(self, event_type: EventType, message: Optional[str] = None):
6772
self.event_type = event_type
6873
self.message = message
6974

75+
def __str__(self):
76+
"""
77+
String representation of the event.
78+
79+
:return: the string representation
80+
:rtype: str
81+
"""
82+
return f"{self.event_type.name}: {self.message}" if self.message else self.event_type.name
83+
84+
def __repr__(self):
85+
"""
86+
String representation of the event.
87+
88+
:return: the string representation
89+
:rtype: str
90+
"""
91+
return f"{self.event_type.name}: {self.message}" if self.message else self.event_type.name
92+
93+
def __eq__(self, other):
94+
"""
95+
Compare two events.
96+
97+
:param other: the other event
98+
:type other: Event
99+
100+
:return: True if the events are equal, False otherwise
101+
:rtype: bool
102+
"""
103+
if self.__class__ is other.__class__:
104+
return self.event_type == other.event_type and self.message == other.message
105+
return NotImplemented
106+
107+
def __hash__(self):
108+
"""
109+
Hash the event.
110+
111+
:return: the hash
112+
:rtype: int
113+
"""
114+
return hash((self.event_type, self.message))
115+
116+
def __ne__(self, other):
117+
"""
118+
Compare two events.
119+
:param other: the other event
120+
:type other: Event
121+
:return: True if the events are not equal, False otherwise
122+
:rtype: bool
123+
"""
124+
if self.__class__ is other.__class__:
125+
return not self.__eq__(other)
126+
return NotImplemented
127+
70128

71129
class Subscriber(ABC):
72130

@@ -91,8 +149,10 @@ def update(self, event: Event):
91149

92150

93151
class Publisher:
94-
def __init__(self):
152+
def __init__(self, avoid_duplicate_notifications: bool = False):
95153
self.__subscribers = set()
154+
self.__notified_events = set()
155+
self.__avoid_duplicate_notifications = avoid_duplicate_notifications
96156

97157
@property
98158
def subscribers(self):
@@ -107,5 +167,15 @@ def remove_subscriber(self, subscriber):
107167
def notify(self, event: Union[Event, EventType]):
108168
if isinstance(event, EventType):
109169
event = Event(event)
170+
# Check if the event has already been notified
171+
# This is to avoid notifying the same event multiple times
172+
if self.__avoid_duplicate_notifications:
173+
if event in self.__notified_events:
174+
logger.warning(f"Event {event} already notified")
175+
return
176+
# Add the event to the notified events
177+
self.__notified_events.add(event)
178+
logger.debug(f"Notifying event {event}")
179+
# Notify all subscribers
110180
for subscriber in self.subscribers:
111181
subscriber.update(event)

‎rocrate_validator/models.py‎

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -897,9 +897,7 @@ def _do_validate_(self, context: ValidationContext) -> bool:
897897
or _.identifier not in context.settings.skip_checks]:
898898

899899
try:
900-
logger.debug("Running check '%s' - Desc: %s - overridden: %s",
901-
check.name, check.description, [_.identifier for _ in check.overridden_by])
902-
if check.overridden:
900+
if check.overridden and not check.requirement.profile.identifier == context.profile_identifier:
903901
logger.debug("Skipping check '%s' because overridden by '%r'",
904902
check.identifier, [_.identifier for _ in check.overridden_by])
905903
continue
@@ -910,7 +908,7 @@ def _do_validate_(self, context: ValidationContext) -> bool:
910908
context.result._add_executed_check(check, check_result)
911909
context.validator.notify(RequirementCheckValidationEvent(
912910
EventType.REQUIREMENT_CHECK_VALIDATION_END, check, validation_result=check_result))
913-
logger.debug("Ran check '%s'. Got result %s", check.name, check_result)
911+
logger.debug("Ran check '%s'. Got result %s", check.identifier, check_result)
914912
if not isinstance(check_result, bool):
915913
logger.warning("Ignoring the check %s as it returned the value %r instead of a boolean", check.name)
916914
raise RuntimeError(f"Ignoring invalid result from check {check.name}")
@@ -1659,6 +1657,23 @@ def __init__(self, event_type: EventType, profile: Profile, message: Optional[st
16591657
def profile(self) -> Profile:
16601658
return self._profile
16611659

1660+
def __str__(self) -> str:
1661+
return f"ProfileValidationEvent({self.event_type}, {self.profile})"
1662+
1663+
def __repr__(self) -> str:
1664+
return f"ProfileValidationEvent(event_type={self.event_type}, profile={self.profile})"
1665+
1666+
def __eq__(self, other: object) -> bool:
1667+
if not isinstance(other, ProfileValidationEvent):
1668+
raise TypeError(f"Cannot compare {type(self)} with {type(other)}")
1669+
return self.event_type == other.event_type and self.profile == other.profile
1670+
1671+
def __ne__(self, other: object) -> bool:
1672+
return not self.__eq__(other)
1673+
1674+
def __hash__(self) -> int:
1675+
return hash((self.event_type, self.profile))
1676+
16621677

16631678
class RequirementValidationEvent(Event):
16641679
def __init__(self,
@@ -1679,6 +1694,23 @@ def requirement(self) -> Requirement:
16791694
def validation_result(self) -> Optional[bool]:
16801695
return self._validation_result
16811696

1697+
def __str__(self) -> str:
1698+
return f"RequirementValidationEvent({self.event_type}, {self.requirement})"
1699+
1700+
def __repr__(self) -> str:
1701+
return f"RequirementValidationEvent(event_type={self.event_type}, requirement={self.requirement})"
1702+
1703+
def __eq__(self, other: object) -> bool:
1704+
if not isinstance(other, RequirementValidationEvent):
1705+
raise TypeError(f"Cannot compare {type(self)} with {type(other)}")
1706+
return self.event_type == other.event_type and self.requirement == other.requirement
1707+
1708+
def __ne__(self, other: object) -> bool:
1709+
return not self.__eq__(other)
1710+
1711+
def __hash__(self) -> int:
1712+
return hash((self.event_type, self.requirement))
1713+
16821714

16831715
class RequirementCheckValidationEvent(Event):
16841716
def __init__(self, event_type: EventType,
@@ -1697,6 +1729,24 @@ def requirement_check(self) -> RequirementCheck:
16971729
def validation_result(self) -> Optional[bool]:
16981730
return self._validation_result
16991731

1732+
def __str__(self) -> str:
1733+
return f"RequirementCheckValidationEvent({self.event_type}, {self.requirement_check})"
1734+
1735+
def __repr__(self) -> str:
1736+
return f"RequirementCheckValidationEvent(event_type={self.event_type}, " \
1737+
f"requirement_check={self.requirement_check})"
1738+
1739+
def __eq__(self, other: object) -> bool:
1740+
if not isinstance(other, RequirementCheckValidationEvent):
1741+
raise TypeError(f"Cannot compare {type(self)} with {type(other)}")
1742+
return self.event_type == other.event_type and self.requirement_check == other.requirement_check
1743+
1744+
def __ne__(self, other: object) -> bool:
1745+
return not self.__eq__(other)
1746+
1747+
def __hash__(self) -> int:
1748+
return hash((self.event_type, self.requirement_check))
1749+
17001750

17011751
class Validator(Publisher):
17021752
"""
@@ -1809,7 +1859,7 @@ def __do_validate__(self,
18091859
self.notify(RequirementValidationEvent(
18101860
EventType.REQUIREMENT_VALIDATION_START, requirement=requirement))
18111861
passed = requirement._do_validate_(context)
1812-
logger.debug("Requirement %s passed: %s", requirement, passed)
1862+
logger.debug("Requirement %s passed: %s", requirement.identifier, passed)
18131863
if not requirement.overridden:
18141864
self.notify(RequirementValidationEvent(
18151865
EventType.REQUIREMENT_VALIDATION_END, requirement=requirement, validation_result=passed))

‎rocrate_validator/requirements/shacl/checks.py‎

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -111,16 +111,19 @@ def execute_check(self, context: ValidationContext):
111111
self.requirement.profile.identifier, self.identifier)
112112
with SHACLValidationContextManager(self, context) as ctx:
113113
# The check is executed only if the profile is the most specific one
114-
logger.debug("SHACL Validation of profile %s requirement %s started",
114+
logger.debug("SHACL Validation of requirement check %s (profile: %s) started",
115115
self.requirement.profile.identifier, self.identifier)
116116
result = self.__do_execute_check__(ctx)
117-
ctx.current_validation_result = self not in result
117+
ctx.current_validation_result = self.identifier not in result
118+
logger.debug("SHACL Validation of requirement check %s (profile: %s) finished with result %s",
119+
self.requirement.profile.identifier, self.identifier, ctx.current_validation_result)
118120
return ctx.current_validation_result
119121
except SHACLValidationAlreadyProcessed:
120-
logger.debug("SHACL Validation of profile %s already processed", self.requirement.profile.identifier)
122+
logger.debug("SHACL Validation of requirement check %s (profile: %s) already processed",
123+
self.requirement.identifier, self.requirement.profile.identifier)
121124
# The check belongs to a profile which has already been processed
122125
# so we can skip the validation and return the specific result for the check
123-
return self not in [i.check for i in context.result.get_issues()]
126+
return self.identifier not in [i.check.identifier for i in context.result.get_issues()]
124127
except SHACLValidationSkip as e:
125128
logger.debug("SHACL Validation of profile %s requirement %s skipped",
126129
self.requirement.profile.identifier, self.identifier)
@@ -187,7 +190,9 @@ def __do_execute_check__(self, shacl_context: SHACLValidationContext):
187190
# if the validation fails, process the failed checks
188191
failed_requirements_checks = set()
189192
failed_requirements_checks_violations: dict[str, SHACLViolation] = {}
190-
failed_requirement_checks_notified = []
193+
failed_requirement_checks_notified = [_.check.identifier for _ in shacl_context.result.get_issues(
194+
min_severity=shacl_context.settings.requirement_severity)]
195+
191196
logger.debug("Parsing Validation with result: %s", shacl_result)
192197
# process the failed checks to extract the requirement checks involved
193198
for violation in shacl_result.violations:
@@ -215,13 +220,18 @@ def __do_execute_check__(self, shacl_context: SHACLValidationContext):
215220
violation_message = violation.get_result_message(shacl_context.rocrate_uri)
216221
registered_check_issues = shacl_context.result.get_issues_by_check(requirementCheck)
217222
skip_requirement_check = False
223+
# check if the violation is already registered
224+
# and skip the requirement check if it is
218225
for check_issue in registered_check_issues:
219226
if check_issue.message == violation_message and \
220227
check_issue.violatingProperty == violating_property and \
221228
check_issue.violatingEntity == violating_entity and \
222229
check_issue.violatingPropertyValue == violation.value:
223230
skip_requirement_check = True
231+
logger.debug("Skipping requirement check %s: %s", requirementCheck.identifier,
232+
violation_message)
224233
break
234+
# if the check is not to be skipped, add the issue to the context
225235
if not skip_requirement_check:
226236
c = shacl_context.result.add_issue(
227237
message=violation.get_result_message(shacl_context.rocrate_uri),
@@ -240,11 +250,13 @@ def __do_execute_check__(self, shacl_context: SHACLValidationContext):
240250
# all together and not profile by profile
241251
if requirementCheck.identifier not in failed_requirement_checks_notified:
242252
shacl_context.result._add_executed_check(requirementCheck, False)
243-
shacl_context.validator.notify(RequirementCheckValidationEvent(
244-
EventType.REQUIREMENT_CHECK_VALIDATION_END,
245-
requirementCheck, validation_result=False))
246-
# failed_requirement_checks_notified.append(requirementCheck.identifier)
247-
logger.debug("Added validation issue to the context: %s", c)
253+
if requirementCheck.identifier not in failed_requirement_checks_notified and \
254+
requirementCheck.requirement.profile != shacl_context.current_validation_profile:
255+
failed_requirement_checks_notified.append(requirementCheck.identifier)
256+
shacl_context.validator.notify(RequirementCheckValidationEvent(
257+
EventType.REQUIREMENT_CHECK_VALIDATION_END,
258+
requirementCheck, validation_result=False))
259+
logger.debug("Added validation issue to the context: %s", c)
248260

249261
# if the fail fast mode is enabled, stop the validation after the first failed check
250262
if shacl_context.fail_fast:
@@ -257,7 +269,7 @@ def __do_execute_check__(self, shacl_context: SHACLValidationContext):
257269
if not isinstance(requirementCheck, SHACLCheck):
258270
logger.debug("Skipped check is not a SHACLCheck: %s", requirementCheck.identifier)
259271
continue
260-
# if requirementCheck.requirement.profile != shacl_context.current_validation_profile and \
272+
# if requirementCheck.requirement.profile != shacl_context.current_validation_profile and
261273
if requirementCheck.identifier not in failed_requirement_checks_notified:
262274
failed_requirement_checks_notified.append(requirementCheck.identifier)
263275
shacl_context.result._add_executed_check(requirementCheck, True)

0 commit comments

Comments
 (0)