diff --git a/README.md b/README.md index e7f57a7b..9906c927 100644 --- a/README.md +++ b/README.md @@ -8,24 +8,24 @@ A Python package to validate [RO-Crate](https://researchobject.github.io/ro-crate/)s. -* Supports CLI-based validation as well as programmatic validation (so it can - easily be used by Python code). -* Implements an extensible validation framework to which new RO-Crate profiles - can be added. Validation is based on SHACL shapes and Python code. -* Currently, validations for the following profiles are implemented: RO-Crate - (base profile), [Workflow - RO-Crate](https://www.researchobject.org/ro-crate/specification/1.1/workflows.html), - [Process Run - Crate](https://www.researchobject.org/workflow-run-crate/profiles/0.1/process_run_crate.html). - More are being implemented. +- Supports [CLI-based validation](#cli-based-validation) as well as [programmatic validation](#programmatic-validation) (so it can easily be used by Python code). +- Implements an extensible validation framework to which new RO-Crate profiles + can be added. Validation is based on SHACL shapes and Python code. +- Currently, validation for the following profiles is implemented: RO-Crate + (base profile), [Workflow + RO-Crate](https://w3id.org/workflowhub/workflow-ro-crate), + [Process Run + Crate](https://w3id.org/ro/wfrun/process). + [Workflow Run Crate](https://w3id.org/ro/wfrun/workflow), + [Provenance Run Crate](https://w3id.org/ro/wfrun/provenance), + [Workflow Testing RO-Crate](https://w3id.org/ro/wftest). **Note**: this software is still work in progress. Feel free to try it out, -report positive and negative feedback. Do send a note (e.g., by opening an -Issue) before starting to develop patches you would like to contribute. The +report positive and negative feedback. Do send a note (e.g., by opening an +Issue) before starting to develop patches you would like to contribute. The implementation of validation code for additional RO-Crate profiles would be particularly welcome. - ## Installation You can install the package using `pip` or `poetry`. The following instructions assume you have Python 3.8 or later installed. @@ -36,23 +36,23 @@ It’s recommended to create a virtual environment before installing the package ```bash python3 -m venv .venv -```` +``` Then, activate the virtual environment: -* On **Unix** or **macOS**: +- On **Unix** or **macOS**: ```bash source .venv/bin/activate ``` -* On **Windows** (Command Prompt): +- On **Windows** (Command Prompt): ```bash .venv\Scripts\activate ``` -* On **Windows** (PowerShell): +- On **Windows** (PowerShell): ```powershell .venv\Scripts\Activate.ps1 @@ -86,8 +86,7 @@ Ensure you have Poetry installed. If not, follow the instructions [here](https:/ poetry install ``` - -## Usage +## CLI-based Validation After installation, use the `rocrate-validator` command to validate RO-Crates. You can run this in a virtual activated environment (if created in the [optional step](#optional-step-create-a-virtual-environment) above) or without a virtual environment if none was created. @@ -98,12 +97,11 @@ Run the validator using the following command: ```bash rocrate-validator validate ``` + where `` is the path to the RO-Crate you want to validate. Type `rocrate-validator --help` for more information. - - ### 2. Using `poetry` Run the validator using the following command: @@ -111,11 +109,51 @@ Run the validator using the following command: ```bash poetry run rocrate-validator validate ``` + where `` is the path to the RO-Crate you want to validate. Type `rocrate-validator --help` for more information. +## Programmatic Validation + +You can also integrate the package programmatically in your Python code. Here's an example: + +```python + +# Import the `services` and `models` module from the rocrate_validator package +from rocrate_validator import services, models + +# Create an instance of `ValidationSettings` class to configure the validation +settings = services.ValidationSettings( + # Set the path to the RO-Crate root directory + rocrate_uri='/path/to/ro-crate', + # Set the identifier of the RO-Crate profile to use for validation. + # If not set, the system will attempt to automatically determine the appropriate validation profile. + profile_identifier='ro-crate-1.1', + # Set the requirement level for the validation + requirement_severity=models.Severity.REQUIRED, +) + +# Call the validation service with the settings +result = services.validate(settings) + +# Check if the validation was successful +if not result.has_issues(): + print("RO-Crate is valid!") +else: + print("RO-Crate is invalid!") + # Explore the issues + for issue in result.get_issues(): + # Every issue object has a reference to the check that failed, the severity of the issue, and a message describing the issue. + print(f"Detected issue of severity {issue.severity.name} with check \"{issue.check.identifier}\": {issue.message}") +``` + +... that leads to the following output: +```bash +RO-Crate is invalid! +Detected issue of severity REQUIRED with check "ro-crate-1.1:root_entity_exists: The RO-Crate must contain a root entity. +``` ## Running the tests @@ -138,8 +176,8 @@ This project is licensed under the terms of the Apache License 2.0. See the This work has been partially funded by the following sources: -* the [BY-COVID](https://by-covid.org/) project (HORIZON Europe grant agreement number 101046203); -* the [LIFEMap](https://www.thelifemap.it/) project, funded by the Italian Ministry of Health (Piano Operative Salute, Trajectory 3). +- the [BY-COVID](https://by-covid.org/) project (HORIZON Europe grant agreement number 101046203); +- the [LIFEMap](https://www.thelifemap.it/) project, funded by the Italian Ministry of Health (Piano Operative Salute, Trajectory 3). Co-funded by the EU[/cyan]" + path += f"\"[green]{issue.violatingPropertyValue}[/green]\" " # keep the ending space + if issue.violatingEntity: + path = f"{path} on [cyan]<{issue.violatingEntity}>[/cyan]" console.print( Padding(f"- [[red]Violation[/red]{path}]: " f"{Markdown(issue.message).markup}", (0, 9)), style="white") @@ -777,14 +773,14 @@ def __compute_profile_stats__(validation_settings: dict): # extract the validation settings severity_validation = Severity.get(validation_settings.get("requirement_severity")) profiles = services.get_profiles(validation_settings.get("profiles_path"), severity=severity_validation) - profile = services.get_profile(validation_settings.get("profiles_path"), - validation_settings.get("profile_identifier"), + profile = services.get_profile(validation_settings.get("profile_identifier"), + validation_settings.get("profiles_path"), severity=severity_validation) # initialize the profiles list profiles = [profile] # add inherited profiles if enabled - if validation_settings.get("inherit_profiles"): + if validation_settings.get("enable_profile_inheritance"): profiles.extend(profile.inherited_profiles) logger.debug("Inherited profiles: %r", profile.inherited_profiles) diff --git a/rocrate_validator/constants.py b/rocrate_validator/constants.py index 83b39d79..035245ac 100644 --- a/rocrate_validator/constants.py +++ b/rocrate_validator/constants.py @@ -85,3 +85,6 @@ # Current JSON output format JSON_OUTPUT_FORMAT_VERSION = "0.1" + +# Default value for the HTTP cache timeout +DEFAULT_HTTP_CACHE_TIMEOUT = 60 diff --git a/rocrate_validator/models.py b/rocrate_validator/models.py index 1e1cfb36..e5ce9776 100644 --- a/rocrate_validator/models.py +++ b/rocrate_validator/models.py @@ -37,9 +37,7 @@ JSON_OUTPUT_FORMAT_VERSION, PROF_NS, PROFILE_FILE_EXTENSIONS, PROFILE_SPECIFICATION_FILE, - RDF_SERIALIZATION_FORMATS_TYPES, - ROCRATE_METADATA_FILE, SCHEMA_ORG_NS, - VALID_INFERENCE_OPTIONS_TYPES) + ROCRATE_METADATA_FILE, SCHEMA_ORG_NS) from rocrate_validator.errors import (DuplicateRequirementCheck, InvalidProfilePath, ProfileNotFound, ProfileSpecificationError, @@ -591,7 +589,7 @@ def order_number(self) -> int: @property def identifier(self) -> str: - return f"{self.profile.identifier}.{self.relative_identifier}" + return f"{self.profile.identifier}_{self.relative_identifier}" @property def relative_identifier(self) -> str: @@ -683,7 +681,7 @@ def __do_validate__(self, context: ValidationContext) -> bool: EventType.REQUIREMENT_CHECK_VALIDATION_START, check)) check_result = check.execute_check(context) logger.debug("Result of check %s: %s", check.identifier, check_result) - context.result.add_executed_check(check, check_result) + context.result._add_executed_check(check, check_result) context.validator.notify(RequirementCheckValidationEvent( EventType.REQUIREMENT_CHECK_VALIDATION_END, check, validation_result=check_result)) logger.debug("Ran check '%s'. Got result %s", check.name, check_result) @@ -695,7 +693,7 @@ def __do_validate__(self, context: ValidationContext) -> bool: break except SkipRequirementCheck as e: logger.debug("Skipping check '%s' because: %s", check.name, e) - context.result.add_skipped_check(check) + context.result._add_skipped_check(check) continue except Exception as e: # Ignore the fact that the check failed as far as the validation result is concerned. @@ -953,26 +951,6 @@ def __ne__(self, other: object) -> bool: def __hash__(self) -> int: return hash((self.requirement, self.name or "")) - # TODO: delete these? - # - # @property - # def issues(self) -> list[CheckIssue]: - # """Return the issues found during the check""" - # assert self.result, "Issues not set before the check" - # return self.result.get_issues_by_check(self, Severity.OPTIONAL) - - # def get_issues_by_severity(self, severity: Severity = Severity.RECOMMENDED) -> list[CheckIssue]: - # return self.result.get_issues_by_check_and_severity(self, severity) - - -# TODO: delete this? - -# def issue_types(issues: list[Type[CheckIssue]]) -> Type[RequirementCheck]: -# def class_decorator(cls): -# cls.issue_types = issues -# return cls -# return class_decorator - @total_ordering class CheckIssue: @@ -989,14 +967,14 @@ class CheckIssue: def __init__(self, check: RequirementCheck, message: Optional[str] = None, - resultPath: Optional[str] = None, - focusNode: Optional[str] = None, + violatingProperty: Optional[str] = None, + violatingEntity: Optional[str] = None, value: Optional[str] = None): self._message = message self._check: RequirementCheck = check - self._resultPath = resultPath - self._focusNode = focusNode - self._value = value + self._violatingProperty = violatingProperty + self._violatingEntity = violatingEntity + self._propertyValue = value @property def message(self) -> Optional[str]: @@ -1023,16 +1001,33 @@ def check(self) -> RequirementCheck: return self._check @property - def resultPath(self) -> Optional[str]: - return self._resultPath + def violatingEntity(self) -> Optional[str]: + """ + It represents the specific element being evaluated that fails + to meet the defined rules or constraints within a validation process. + Also referred to as `focusNode` in SHACL terminology + in the context of an RDF graph, it is the subject of a triple + that violates a given constraint on the subject’s property/predicate, + represented by the violatingProperty. + """ + return self._violatingEntity @property - def focusNode(self) -> Optional[str]: - return self._focusNode + def violatingProperty(self) -> Optional[str]: + """ + It refers to the specific property or relationship within an item + that leads to a validation failure. + It identifies the part of the data structure that is causing the issue. + Also referred to as `resultPath` in SHACL terminology, + in the context of an RDF graph, it is the predicate of a triple + that violates a given constraint on the subject’s property/predicate, + represented by the violatingProperty. + """ + return self._violatingProperty @property - def value(self) -> Optional[str]: - return self._value + def violatingPropertyValue(self) -> Optional[str]: + return self._propertyValue def __eq__(self, other: object) -> bool: return isinstance(other, CheckIssue) and \ @@ -1051,15 +1046,16 @@ def __repr__(self) -> str: return f'CheckIssue(severity={self.severity}, check={self.check}, message={self.message})' def __str__(self) -> str: - return f"{self.severity}: {self.message} ({self.check})" + return f"Issue of severity {self.severity.name} with check \"{self.check.identifier}\": {self.message}" 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, - "focusNode": self.focusNode, - "value": self.value + "violatingEntity": self.violatingEntity, + "violatingProperty": self.violatingProperty, + "violatingPropertyValue": self.violatingPropertyValue } if with_check: result["check"] = self.check.to_dict(with_requirement=with_requirement, with_profile=with_profile) @@ -1076,38 +1072,25 @@ def to_json(self, with_profile=with_profile ), indent=4, cls=CustomEncoder) - # @property - # def code(self) -> int: - # breakpoint() - # # If the code has not been set, calculate it - # if not self._code: - # """ - # Calculate the code based on the severity, the class name and the message. - # - All issues with the same severity, class name and message will have the same code. - # - All issues with the same severity and class name but different message will have different codes. - # - All issues with the same severity but different class name and message will have different codes. - # - All issues with the same severity should start with the same number. - # - All codes should be positive numbers. - # """ - # # Concatenate the level, class name and message into a single string - # issue_string = self.level.name + self.__class__.__name__ + str(self.message) - # - # # Use the built-in hash function to generate a unique code for this string - # # The modulo operation ensures that the code is a positive number - # self._code = hash(issue_string) % ((1 << 31) - 1) - # # Return the code - # return self._code - class ValidationResult: + """ + Class to store the result of the validation + + Attributes: + context (ValidationContext): The validation context + rocrate_uri (str): The URI of the RO-Crate + validation_settings (ValidationSettings): The validation settings + issues (list[CheckIssue]): The issues found during the validation + """ def __init__(self, context: ValidationContext): # reference to the validation context self._context = context - # reference to the ro-crate path - self._rocrate_path = context.rocrate_path + # reference to the ro-crate URI + self._rocrate_uri = context.rocrate_uri # reference to the validation settings - self._validation_settings: dict[str, BaseTypes] = context.settings + self._validation_settings: ValidationSettings = context.settings # keep track of the issues found during the validation self._issues: list[CheckIssue] = [] # keep track of the checks that have been executed @@ -1118,22 +1101,37 @@ def __init__(self, context: ValidationContext): @property def context(self) -> ValidationContext: + """" + The validation context + """ return self._context @property - def rocrate_path(self): - return self._rocrate_path + def rocrate_uri(self): + """ + The URI of the RO-Crate + """ + return self._rocrate_uri @property def validation_settings(self): + """ + The validation settings + """ return self._validation_settings # --- Checks --- @property def executed_checks(self) -> set[RequirementCheck]: + """ + The checks that have been executed + """ return self._executed_checks - def add_executed_check(self, check: RequirementCheck, result: bool): + def _add_executed_check(self, check: RequirementCheck, result: bool): + """" + Internal method to add a check to the executed checks + """ self._executed_checks.add(check) self._executed_checks_results[check.identifier] = result # remove the check from the skipped checks if it was skipped @@ -1142,30 +1140,52 @@ def add_executed_check(self, check: RequirementCheck, result: bool): logger.debug("Removing check '%s' from skipped checks", check.name) def get_executed_check_result(self, check: RequirementCheck) -> Optional[bool]: + """ + Get the result of an executed check + """ return self._executed_checks_results.get(check.identifier) @property def skipped_checks(self) -> set[RequirementCheck]: + """ + The checks that have been skipped + """ return self._skipped_checks - def add_skipped_check(self, check: RequirementCheck): + def _add_skipped_check(self, check: RequirementCheck): + """ + Internal method to add a check to the skipped checks + """ self._skipped_checks.add(check) - def remove_skipped_check(self, check: RequirementCheck): + def _remove_skipped_check(self, check: RequirementCheck): + """ + Internal method to remove a check from the skipped checks + """ self._skipped_checks.remove(check) # --- Issues --- @property def issues(self) -> list[CheckIssue]: - return self._issues + """ + The issues found during the validation + """ + return self._issues.copy() def get_issues(self, min_severity: Optional[Severity] = None) -> list[CheckIssue]: + """ + Get the issues found during the validation with a severity greater than or equal to min_severity + """ min_severity = min_severity or self.context.requirement_severity return [issue for issue in self._issues if issue.severity >= min_severity] def get_issues_by_check(self, check: RequirementCheck, min_severity: Severity = None) -> list[CheckIssue]: + """ + Get the issues found during the validation for a specific check + with a severity greater than or equal to min_severity + """ min_severity = min_severity or self.context.requirement_severity return [issue for issue in self._issues if issue.check == check and issue.severity >= min_severity] @@ -1173,14 +1193,26 @@ def get_issues_by_check(self, # return [issue for issue in self.issues if issue.check == check and issue.severity == severity] def has_issues(self, severity: Optional[Severity] = None) -> bool: + """ + Check if there are issues with a severity greater than or equal to the given severity + """ severity = severity or self.context.requirement_severity return any(issue.severity >= severity for issue in self._issues) def passed(self, severity: Optional[Severity] = None) -> bool: + """" + Check if all checks passed with a severity greater than or equal to the given severity + """ severity = severity or self.context.requirement_severity return not any(issue.severity >= severity for issue in self._issues) def add_issue(self, issue: CheckIssue): + """ + Add an issue to the validation result + + Parameters: + issue (CheckIssue): The issue to add to the validation result + """ bisect.insort(self._issues, issue) def add_check_issue(self, @@ -1189,37 +1221,62 @@ def add_check_issue(self, resultPath: Optional[str] = None, focusNode: Optional[str] = None, value: Optional[str] = None) -> CheckIssue: - c = CheckIssue(check, message, resultPath=resultPath, focusNode=focusNode, value=value) + """" + Add an issue to the validation result + + Parameters: + message (str): The message of the issue + check (RequirementCheck): The check that generated the issue + resultPath (Optional[str]): The result path (i.e., the predicate) of the issue + focusNode (Optional[str]): The focus node (i.e., the subject) of the issue + value (Optional[str]): The value of the result path which caused the issue (if any) + """ + c = CheckIssue(check, message, violatingProperty=resultPath, violatingEntity=focusNode, value=value) bisect.insort(self._issues, c) return c def add_error(self, message: str, check: RequirementCheck) -> CheckIssue: + """" + Add an error to the validation result + """ return self.add_check_issue(message, check) # --- Requirements --- @property def failed_requirements(self) -> Collection[Requirement]: + """" + Get the requirements that failed + """ return set(issue.check.requirement for issue in self._issues) # --- Checks --- @property def failed_checks(self) -> Collection[RequirementCheck]: + """" + Get the checks that failed + """ return set(issue.check for issue in self._issues) def get_failed_checks_by_requirement(self, requirement: Requirement) -> Collection[RequirementCheck]: + """" + Get the checks that failed for a specific requirement + """ return [check for check in self.failed_checks if check.requirement == requirement] def get_failed_checks_by_requirement_and_severity( self, requirement: Requirement, severity: Severity) -> Collection[RequirementCheck]: + """" + Get the checks that failed for a specific requirement and severity + """ return [check for check in self.failed_checks if check.requirement == requirement and check.severity == severity] def __str__(self) -> str: - return f"Validation result: {len(self._issues)} issues" + return f"Validation result: passed={len(self.failed_checks)==0}, {len(self._issues)} issues" def __repr__(self): - return f"ValidationResult(issues={self._issues})" + return f"ValidationResult(passed={len(self.failed_checks)==0},issues={self._issues})" def __eq__(self, other: object) -> bool: if not isinstance(other, ValidationResult): @@ -1227,14 +1284,19 @@ def __eq__(self, other: object) -> bool: return self._issues == other._issues def to_dict(self) -> dict: - allowed_properties = ["profile_identifier", "inherit_profiles", "requirement_severity", "abort_on_first"] + """ + Convert the ValidationResult to a dictionary + """ + allowed_properties = ["profile_identifier", "enable_profile_inheritance", + "requirement_severity", "abort_on_first"] + validation_settings = {key: value for key, value in self.validation_settings.to_dict().items() + if key in allowed_properties} result = { "meta": { "version": JSON_OUTPUT_FORMAT_VERSION }, - "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"]), + "validation_settings": validation_settings, + "passed": self.passed(self.context.settings.requirement_severity), "issues": [issue.to_dict() for issue in self.issues] } # add validator version to the settings @@ -1242,7 +1304,9 @@ def to_dict(self) -> dict: return result def to_json(self, path: Optional[Path] = None) -> str: - + """" + Convert the ValidationResult to a JSON string + """ result = json.dumps(self.to_dict(), indent=4, cls=CustomEncoder) if path: with open(path, "w") as f: @@ -1270,36 +1334,45 @@ def default(self, obj): @dataclass class ValidationSettings: + """ + A class to represent the settings for RO-Crate validation + Attributes: + rocrate_uri (URI): The URI of the RO-Crate to validate + profiles_path (Path): The path to the profiles directory (default: {DEFAULT_PROFILES_PATH}) + profile_identifier (str): The identifier of the profile to use (default: {DEFAULT_PROFILE_IDENTIFIER}) + enable_profile_inheritance (bool): Whether to enable profile inheritance (default: True) + abort_on_first (Optional[bool]): Whether to abort on the first validation error (default: True) + disable_inherited_profiles_reporting (bool): Whether to disable reporting of inherited profiles (default: False) + disable_remote_crate_download (bool): Whether to disable downloading of remote crates (default: True) + requirement_severity (Union[str, Severity]): The severity level for requirements (default: Severity.REQUIRED) + requirement_severity_only (bool): Whether to only consider the severity of requirements (default: False) + allow_requirement_check_override (bool): Whether to allow overriding requirement checks (default: True) + disable_check_for_duplicates (bool): Whether to disable checking for duplicate entries (default: False) + + Methods: + __init__(**kwargs): Initializes the ValidationSettings with the given keyword arguments + to_dict(): Converts the ValidationSettings instance to a dictionary + rocrate_uri(): Gets the RO-Crate URI + rocrate_uri(value): Sets the RO-Crate URI + parse(settings): Parses the settings into a ValidationSettings object + """ # Data settings - data_path: Path + rocrate_uri: URI # Profile settings profiles_path: Path = DEFAULT_PROFILES_PATH profile_identifier: str = DEFAULT_PROFILE_IDENTIFIER - inherit_profiles: bool = True - allow_requirement_check_override: bool = True - disable_check_for_duplicates: bool = False - # Ontology and inference settings - ontology_path: Optional[Path] = None - inference: Optional[VALID_INFERENCE_OPTIONS_TYPES] = None + enable_profile_inheritance: bool = True # Validation strategy settings - advanced: bool = True # enable SHACL Advanced Validation - inplace: Optional[bool] = False abort_on_first: Optional[bool] = True - inplace: Optional[bool] = False - meta_shacl: bool = False - iterate_rules: bool = True - target_only_validation: bool = True - remote_validation: bool = True - http_cache_timeout: int = 60 + disable_inherited_profiles_reporting: bool = False + disable_remote_crate_download: bool = True # Requirement severity settings requirement_severity: Union[str, Severity] = Severity.REQUIRED requirement_severity_only: bool = False - allow_infos: Optional[bool] = True - allow_warnings: Optional[bool] = True - # Output serialization settings - serialization_output_path: Optional[Path] = None - serialization_output_format: RDF_SERIALIZATION_FORMATS_TYPES = "turtle" + # Requirement check settings + allow_requirement_check_override: bool = True + disable_check_for_duplicates: bool = False def __init__(self, **kwargs): for key, value in kwargs.items(): @@ -1310,8 +1383,26 @@ def __init__(self, **kwargs): if isinstance(severity, str): setattr(self, "requirement_severity", Severity[severity]) + # parse and set the rocrate URI + self._rocrate_uri = None + if "rocrate_uri" in kwargs: + self.rocrate_uri = kwargs["rocrate_uri"] + logger.debug("Validating RO-Crate: %s", self.rocrate_uri) + def to_dict(self): - return asdict(self) + result = asdict(self) + result['rocrate_uri'] = str(self.rocrate_uri) + return result + + @property + def rocrate_uri(self) -> Optional[URI]: + return self._rocrate_uri + + @rocrate_uri.setter + def rocrate_uri(self, value: URI): + if not value: + raise ValueError("Invalid RO-Crate URI") + self._rocrate_uri: URI = URI(value) @classmethod def parse(cls, settings: Union[dict, ValidationSettings]) -> ValidationSettings: @@ -1397,8 +1488,23 @@ def validation_result(self) -> Optional[bool]: class Validator(Publisher): """ - Can validate conformance to a single Profile (including any requirements - inherited by parent profiles). + Validator class for validating Research Object Crates (RO-Crate) + against specified profiles according to the validation settings. + + Attributes: + validation_settings (ValidationSettings): The settings used for validation. + + Methods: + __init__(settings: Union[str, ValidationSettings]): + Initializes the Validator with the given settings. + validation_settings() -> ValidationSettings: + Returns the validation settings. + detect_rocrate_profiles() -> list[Profile]: + Detects the profiles to validate against. + validate() -> ValidationResult: + Validate the RO-Crate against the detected profiles according to the validation settings + validate_requirements(requirements: list[Requirement]) -> ValidationResult: + Validates the RO-Crate against the specified subset of the profile requirements. """ def __init__(self, settings: Union[str, ValidationSettings]): @@ -1415,7 +1521,7 @@ def detect_rocrate_profiles(self) -> list[Profile]: """ try: # initialize the validation context - context = ValidationContext(self, self.validation_settings.to_dict()) + context = ValidationContext(self, self.validation_settings) candidate_profiles_uris = set() try: candidate_profiles_uris.add(context.ro_crate.metadata.get_conforms_to()) @@ -1452,10 +1558,15 @@ def detect_rocrate_profiles(self) -> list[Profile]: return None def validate(self) -> ValidationResult: + """ + Validate the RO-Crate against the detected profiles according to the validation settings + """ return self.__do_validate__() def validate_requirements(self, requirements: list[Requirement]) -> ValidationResult: - # check if requirement is an instance of Requirement + """ + Validates the RO-Crate against the specified subset of the profile requirements + """ assert all(isinstance(requirement, Requirement) for requirement in requirements), \ "Invalid requirement type" # perform the requirements validation @@ -1465,7 +1576,7 @@ def __do_validate__(self, requirements: Optional[list[Requirement]] = None) -> ValidationResult: # initialize the validation context - context = ValidationContext(self, self.validation_settings.to_dict()) + context = ValidationContext(self, self.validation_settings) # set the profiles to validate against profiles = context.profiles @@ -1509,7 +1620,7 @@ def __do_validate__(self, class ValidationContext: - def __init__(self, validator: Validator, settings: dict[str, object]): + def __init__(self, validator: Validator, settings: ValidationSettings): # reference to the validator self._validator = validator # reference to the settings @@ -1523,12 +1634,8 @@ def __init__(self, validator: Validator, settings: dict[str, object]): # additional properties for the context self._properties = {} - # parse the rocrate path - rocrate_path: URI = URI(settings.get("data_path")) - logger.debug("Validating RO-Crate: %s", rocrate_path) - # initialize the ROCrate object - self._rocrate = ROCrate.new_instance(rocrate_path) + self._rocrate = ROCrate.new_instance(settings.rocrate_uri) assert isinstance(self._rocrate, ROCrate), "Invalid RO-Crate instance" @property @@ -1546,7 +1653,7 @@ def result(self) -> ValidationResult: return self._result @property - def settings(self) -> dict[str, object]: + def settings(self) -> ValidationSettings: return self._settings @property @@ -1558,14 +1665,14 @@ def publicID(self) -> str: @property def profiles_path(self) -> Path: - profiles_path = self.settings.get("profiles_path") + profiles_path = self.settings.profiles_path if isinstance(profiles_path, str): profiles_path = Path(profiles_path) return profiles_path @property def requirement_severity(self) -> Severity: - severity = self.settings.get("requirement_severity", Severity.REQUIRED) + severity = self.settings.requirement_severity if isinstance(severity, str): severity = Severity[severity] elif not isinstance(severity, Severity): @@ -1574,15 +1681,15 @@ def requirement_severity(self) -> Severity: @property def requirement_severity_only(self) -> bool: - return self.settings.get("requirement_severity_only", False) + return self.settings.requirement_severity_only @property - def rocrate_path(self) -> Path: - return self.settings.get("data_path") + def rocrate_uri(self) -> Path: + return self.settings.rocrate_uri @property def fail_fast(self) -> bool: - return self.settings.get("abort_on_first", True) + return self.settings.abort_on_first @property def rel_fd_path(self) -> Path: @@ -1604,7 +1711,7 @@ def get_data_graph(self, refresh: bool = False): return self._data_graph except FileNotFoundError as e: logger.debug("Error loading data graph: %s", e) - raise ROCrateMetadataNotFoundError(self.rocrate_path) + raise ROCrateMetadataNotFoundError(self.rocrate_uri) @property def data_graph(self) -> Graph: @@ -1612,19 +1719,19 @@ def data_graph(self) -> Graph: @property def inheritance_enabled(self) -> bool: - return self.settings.get("inherit_profiles", False) + return self.settings.enable_profile_inheritance @property def profile_identifier(self) -> str: - return self.settings.get("profile_identifier") + return self.settings.profile_identifier @property def allow_requirement_check_override(self) -> bool: - return self.settings.get("allow_requirement_check_override", True) + return self.settings.allow_requirement_check_override @property def disable_check_for_duplicates(self) -> bool: - return self.settings.get("disable_check_for_duplicates", False) + return self.settings.disable_check_for_duplicates def __load_profiles__(self) -> list[Profile]: @@ -1653,10 +1760,10 @@ def __load_profiles__(self) -> list[Profile]: if candidate_profiles: # Find the profile with the highest version number profile = max(candidate_profiles, key=lambda p: p.version) - self.settings["profile_identifier"] = profile.identifier + self.settings.profile_identifier = profile.identifier logger.debug("Profile with the highest version number: %s", profile) # if the profile is found by token, set the profile name to the identifier - self.settings["profile_identifier"] = profile.identifier + self.settings.profile_identifier = profile.identifier except AttributeError as e: # raised when the profile is not found if logger.isEnabledFor(logging.DEBUG): diff --git a/rocrate_validator/requirements/shacl/checks.py b/rocrate_validator/requirements/shacl/checks.py index 2d0b7698..3c639a4e 100644 --- a/rocrate_validator/requirements/shacl/checks.py +++ b/rocrate_validator/requirements/shacl/checks.py @@ -174,7 +174,7 @@ def __do_execute_check__(self, shacl_context: SHACLValidationContext): start_time = timer() shacl_validator = SHACLValidator(shapes_graph=shapes_graph, ont_graph=ontology_graph) shacl_result = shacl_validator.validate( - data_graph=data_graph, ontology_graph=ontology_graph, **shacl_context.settings) + data_graph=data_graph, ontology_graph=ontology_graph, **shacl_context.settings.to_dict()) # shacl_result.results_graph.serialize("logs/validation_results.ttl", format="turtle") # parse the validation result end_time = timer() @@ -204,21 +204,30 @@ def __do_execute_check__(self, shacl_context: SHACLValidationContext): # to ensure a consistent order of the issues # and to make the fail fast mode deterministic for requirementCheck in sorted(failed_requirements_checks, key=lambda x: (x.identifier, x.severity)): - # add only the issues for the current profile when the `target_profile_only` mode is disabled - # (issues related to other profiles will be added by the corresponding profile validation) - if requirementCheck.requirement.profile == shacl_context.current_validation_profile or \ - shacl_context.settings.get("target_only_validation", False): - for violation in failed_requirements_checks_violations[requirementCheck.identifier]: + # if the check is not in the current profile + # and the disable_inherited_profiles_reporting is enabled, skip it + if requirementCheck.requirement.profile != shacl_context.current_validation_profile and \ + shacl_context.settings.disable_inherited_profiles_reporting: + continue + for violation in failed_requirements_checks_violations[requirementCheck.identifier]: + violation_message = violation.get_result_message(shacl_context.rocrate_uri) + registered_check_issues = shacl_context.result.get_issues_by_check(requirementCheck) + skip_requirement_check = False + for check_issue in registered_check_issues: + if check_issue.message == violation_message: + skip_requirement_check = True + break + if not skip_requirement_check: c = shacl_context.result.add_check_issue( - message=violation.get_result_message(shacl_context.rocrate_path), + message=violation.get_result_message(shacl_context.rocrate_uri), check=requirementCheck, resultPath=violation.resultPath.toPython() if violation.resultPath else None, focusNode=make_uris_relative( violation.focusNode.toPython(), shacl_context.publicID), value=violation.value) - # if the fail fast mode is enabled, stop the validation after the first issue - if shacl_context.fail_fast: - break + # if the fail fast mode is enabled, stop the validation after the first issue + if shacl_context.fail_fast: + break # If the fail fast mode is disabled, notify all the validation issues # related to profiles other than the current one. @@ -229,7 +238,7 @@ def __do_execute_check__(self, shacl_context: SHACLValidationContext): # if requirementCheck.requirement.profile != shacl_context.current_validation_profile: failed_requirement_checks_notified.append(requirementCheck.identifier) - shacl_context.result.add_executed_check(requirementCheck, False) + shacl_context.result._add_executed_check(requirementCheck, False) shacl_context.validator.notify(RequirementCheckValidationEvent( EventType.REQUIREMENT_CHECK_VALIDATION_END, requirementCheck, validation_result=False)) logger.debug("Added validation issue to the context: %s", c) @@ -249,7 +258,7 @@ def __do_execute_check__(self, shacl_context: SHACLValidationContext): requirementCheck not in failed_requirements_checks and \ requirementCheck.identifier not in failed_requirement_checks_notified: failed_requirement_checks_notified.append(requirementCheck.identifier) - shacl_context.result.add_executed_check(requirementCheck, True) + shacl_context.result._add_executed_check(requirementCheck, True) shacl_context.validator.notify(RequirementCheckValidationEvent( EventType.REQUIREMENT_CHECK_VALIDATION_END, requirementCheck, validation_result=True)) logger.debug("Added skipped check to the context: %s", requirementCheck.identifier) diff --git a/rocrate_validator/requirements/shacl/validator.py b/rocrate_validator/requirements/shacl/validator.py index a4d5a65b..ec2c9eaf 100644 --- a/rocrate_validator/requirements/shacl/validator.py +++ b/rocrate_validator/requirements/shacl/validator.py @@ -64,10 +64,9 @@ def __enter__(self) -> SHACLValidationContext: raise SHACLValidationAlreadyProcessed( self._profile.identifier, self._shacl_context.get_validation_result(self._profile)) logger.debug("Processing profile: %s (id: %s)", self._profile.name, self._profile.identifier) - if self._context.settings.get("target_only_validation", False) and \ - self._profile.identifier != self._context.settings.get("profile_identifier", None): + if self._profile.identifier != self._context.settings.profile_identifier: logger.debug("Skipping validation of profile %s", self._profile.identifier) - self.context.result.add_skipped_check(self._check) + self.context.result._add_skipped_check(self._check) raise SHACLValidationSkip(f"Skipping validation of profile {self._profile.identifier}") logger.debug("ValidationContext of profile %s initialized", self._profile.identifier) return self._shacl_context @@ -125,7 +124,7 @@ def __set_current_validation_profile__(self, profile: Profile) -> bool: logger.debug("Loaded shapes: %s", profile_shapes) # enable overriding of checks - if self.settings.get("allow_requirement_check_override", True): + if self.settings.allow_requirement_check_override: from rocrate_validator.requirements.shacl.requirements import \ SHACLRequirement for requirement in [_ for _ in profile.requirements if isinstance(_, SHACLRequirement)]: @@ -187,12 +186,7 @@ def shapes_graph(self) -> Graph: def __get_ontology_path__(self, profile_path: Path, ontology_filename: str = DEFAULT_ONTOLOGY_FILE) -> Path: if not self._ontology_path: - supported_path = f"{profile_path}/{ontology_filename}" - if self.settings.get("ontology_path", None): - logger.warning("Detected an ontology path. Custom ontology file is not yet supported." - f"Use {supported_path} to provide an ontology for your profile.") - # overwrite the ontology path if the custom ontology file is provided - self._ontology_path = Path(supported_path) + self._ontology_path = Path(f"{profile_path}/{ontology_filename}") return self._ontology_path def __load_ontology_graph__(self, profile_path: Path, @@ -388,8 +382,8 @@ def validate( meta_shacl: bool = False, iterate_rules: bool = True, # SHACL validation severity - allow_infos: Optional[bool] = False, - allow_warnings: Optional[bool] = False, + allow_infos: Optional[bool] = True, + allow_warnings: Optional[bool] = True, # serialization settings serialization_output_path: Optional[str] = None, serialization_output_format: diff --git a/rocrate_validator/rocrate.py b/rocrate_validator/rocrate.py index 82d338f4..b11cb8e3 100644 --- a/rocrate_validator/rocrate.py +++ b/rocrate_validator/rocrate.py @@ -275,10 +275,10 @@ def __eq__(self, other: ROCrateMetadata) -> bool: class ROCrate(ABC): - def __init__(self, path: Union[str, Path, URI]): + def __init__(self, uri: Union[str, Path, URI]): # store the path to the crate - self._uri = URI(path) + self._uri = URI(uri) # cache the list of files self._files = None diff --git a/rocrate_validator/services.py b/rocrate_validator/services.py index 346d8a62..f0230546 100644 --- a/rocrate_validator/services.py +++ b/rocrate_validator/services.py @@ -22,7 +22,7 @@ import requests_cache import rocrate_validator.log as logging -from rocrate_validator.constants import DEFAULT_PROFILE_IDENTIFIER +from rocrate_validator.constants import DEFAULT_HTTP_CACHE_TIMEOUT from rocrate_validator.errors import ProfileNotFound from rocrate_validator.events import Subscriber from rocrate_validator.models import (Profile, Severity, ValidationResult, @@ -67,7 +67,7 @@ def __initialise_validator__(settings: Union[dict, ValidationSettings], settings = ValidationSettings.parse(settings) # parse the rocrate path - rocrate_path: URI = URI(settings.data_path) + rocrate_path: URI = URI(settings.rocrate_uri) logger.debug("Validating RO-Crate: %s", rocrate_path) # check if the RO-Crate exists @@ -75,20 +75,20 @@ def __initialise_validator__(settings: Union[dict, ValidationSettings], raise FileNotFoundError(f"RO-Crate not found: {rocrate_path}") # check if the requests cache is enabled - if settings.http_cache_timeout > 0: + if DEFAULT_HTTP_CACHE_TIMEOUT > 0: # Set up requests cache requests_cache.install_cache( '/tmp/rocrate_validator_cache', - expire_after=settings.http_cache_timeout, # Cache expiration time in seconds + expire_after=DEFAULT_HTTP_CACHE_TIMEOUT, # Cache expiration time in seconds backend='sqlite', # Use SQLite backend allowable_methods=('GET',), # Cache GET allowable_codes=(200, 302, 404) # Cache responses with these status codes ) # check if remote validation is enabled - remote_validation = settings.remote_validation - logger.debug("Remote validation: %s", remote_validation) - if remote_validation: + disable_remote_crate_download = settings.disable_remote_crate_download + logger.debug("Remote validation: %s", disable_remote_crate_download) + if disable_remote_crate_download: # create a validator validator = Validator(settings) logger.debug("Validator created. Starting validation...") @@ -108,7 +108,7 @@ def __init_validator__(settings: ValidationSettings) -> Validator: def __extract_and_validate_rocrate__(rocrate_path: Path): # store the original data path - original_data_path = settings.data_path + original_data_path = settings.rocrate_uri with tempfile.TemporaryDirectory() as tmp_dir: try: @@ -117,12 +117,12 @@ def __extract_and_validate_rocrate__(rocrate_path: Path): zip_ref.extractall(tmp_dir) logger.debug("RO-Crate extracted to temporary directory: %s", tmp_dir) # update the data path to point to the temporary directory - settings.data_path = Path(tmp_dir) + settings.rocrate_uri = Path(tmp_dir) # continue with the validation process return __init_validator__(settings) finally: # restore the original data path - settings.data_path = original_data_path + settings.rocrate_uri = original_data_path logger.debug("Original data path restored: %s", original_data_path) # check if the RO-Crate is a remote RO-Crate, @@ -150,7 +150,7 @@ def __extract_and_validate_rocrate__(rocrate_path: Path): # if the RO-Crate is not a ZIP file, directly validate the RO-Crate elif rocrate_path.is_local_directory(): logger.debug("RO-Crate is a local directory") - settings.data_path = rocrate_path.as_path() + settings.rocrate_uri = rocrate_path.as_path() return __init_validator__(settings) else: raise ValueError( @@ -159,30 +159,28 @@ def __extract_and_validate_rocrate__(rocrate_path: Path): def get_profiles(profiles_path: Path = DEFAULT_PROFILES_PATH, - publicID: str = None, severity=Severity.OPTIONAL, allow_requirement_check_override: bool = ValidationSettings.allow_requirement_check_override) -> list[Profile]: """ Load the profiles from the given path """ - profiles = Profile.load_profiles(profiles_path, publicID=publicID, + profiles = Profile.load_profiles(profiles_path, severity=severity, allow_requirement_check_override=allow_requirement_check_override) logger.debug("Profiles loaded: %s", profiles) return profiles -def get_profile(profiles_path: Path = DEFAULT_PROFILES_PATH, - profile_identifier: str = DEFAULT_PROFILE_IDENTIFIER, - publicID: str = None, +def get_profile(profile_identifier: str, + profiles_path: Path = DEFAULT_PROFILES_PATH, severity=Severity.OPTIONAL, allow_requirement_check_override: bool = ValidationSettings.allow_requirement_check_override) -> Profile: """ Load the profiles from the given path """ - profiles = get_profiles(profiles_path, publicID=publicID, severity=severity, + profiles = get_profiles(profiles_path, severity=severity, allow_requirement_check_override=allow_requirement_check_override) profile = next((p for p in profiles if p.identifier == profile_identifier), None) or \ next((p for p in profiles if str(p.identifier).replace(f"-{p.version}", '') == profile_identifier), None) diff --git a/tests/shared.py b/tests/shared.py index e9a8f317..684f3d91 100644 --- a/tests/shared.py +++ b/tests/shared.py @@ -92,7 +92,7 @@ def do_entity_test( # validate RO-Crate result: models.ValidationResult = \ services.validate(models.ValidationSettings(**{ - "data_path": rocrate_path, + "rocrate_uri": rocrate_path, "requirement_severity": requirement_severity, "abort_on_first": abort_on_first, "profile_identifier": profile_identifier diff --git a/tests/test_models.py b/tests/test_models.py index ed44b254..0fa0425b 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -80,7 +80,7 @@ def validation_settings(): # @pytest.mark.skip(reason="Temporarily disabled: we need an RO-Crate with multiple failed requirements to test this.") def test_sortability_requirements(validation_settings: ValidationSettings): - validation_settings.data_path = InvalidRootDataEntity().invalid_root_type + validation_settings.rocrate_uri = InvalidRootDataEntity().invalid_root_type result: models.ValidationResult = services.validate(validation_settings) failed_requirements = sorted(result.failed_requirements, reverse=True) assert len(failed_requirements) > 1 @@ -89,7 +89,7 @@ def test_sortability_requirements(validation_settings: ValidationSettings): def test_sortability_checks(validation_settings: ValidationSettings): - validation_settings.data_path = WROCInvalidReadme().wroc_readme_wrong_encoding_format + validation_settings.rocrate_uri = WROCInvalidReadme().wroc_readme_wrong_encoding_format result: models.ValidationResult = services.validate(validation_settings) failed_checks = sorted(result.failed_checks, reverse=True) assert len(failed_checks) > 1 @@ -100,7 +100,7 @@ def test_sortability_checks(validation_settings: ValidationSettings): def test_sortability_issues(validation_settings: ValidationSettings): - validation_settings.data_path = WROCInvalidReadme().wroc_readme_wrong_encoding_format + validation_settings.rocrate_uri = WROCInvalidReadme().wroc_readme_wrong_encoding_format result: models.ValidationResult = services.validate(validation_settings) issues = sorted(result.get_issues(min_severity=Severity.OPTIONAL), reverse=True) assert len(issues) > 1 @@ -111,7 +111,7 @@ def test_sortability_issues(validation_settings: ValidationSettings): def test_hidden_shape(): - rocrate_profile = services.get_profile(profile_identifier="ro-crate-1.1") + rocrate_profile = services.get_profile("ro-crate-1.1") assert rocrate_profile is not None, "Profile should not be None" # get the hidden requirement hidden_requirement = rocrate_profile.get_requirement("Identify the Root Data Entity of the RO-Crate") diff --git a/tests/unit/requirements/test_profiles.py b/tests/unit/requirements/test_profiles.py index 2d71052a..b1e9c28f 100644 --- a/tests/unit/requirements/test_profiles.py +++ b/tests/unit/requirements/test_profiles.py @@ -56,16 +56,16 @@ def test_load_invalid_profile_from_validation_context(fake_profiles_path: str): settings = { "profiles_path": "/tmp/random_path_xxx", "profile_identifier": DEFAULT_PROFILE_IDENTIFIER, - "data_path": ValidROC().wrroc_paper, - "inherit_profiles": False + "rocrate_uri": ValidROC().wrroc_paper, + "enable_profile_inheritance": False } settings = ValidationSettings(**settings) - assert not settings.inherit_profiles, "The inheritance mode should be set to False" + assert not settings.enable_profile_inheritance, "The inheritance mode should be set to False" validator = Validator(settings) # initialize the validation context - context = ValidationContext(validator, validator.validation_settings.to_dict()) + context = ValidationContext(validator, validator.validation_settings) # Check if the InvalidProfilePath exception is raised with pytest.raises(InvalidProfilePath): @@ -78,16 +78,16 @@ def test_load_valid_profile_without_inheritance_from_validation_context(fake_pro settings = { "profiles_path": fake_profiles_path, "profile_identifier": "c", - "data_path": ValidROC().wrroc_paper, - "inherit_profiles": False + "rocrate_uri": ValidROC().wrroc_paper, + "enable_profile_inheritance": False } settings = ValidationSettings(**settings) - assert not settings.inherit_profiles, "The inheritance mode should be set to False" + assert not settings.enable_profile_inheritance, "The inheritance mode should be set to False" validator = Validator(settings) # initialize the validation context - context = ValidationContext(validator, validator.validation_settings.to_dict()) + context = ValidationContext(validator, validator.validation_settings) # Load the profiles profiles = context.profiles @@ -102,17 +102,17 @@ def test_profile_spec_properties(fake_profiles_path: str): settings = { "profiles_path": fake_profiles_path, "profile_identifier": "c", - "data_path": ValidROC().wrroc_paper, - "inherit_profiles": True, + "rocrate_uri": ValidROC().wrroc_paper, + "enable_profile_inheritance": True, "disable_check_for_duplicates": True, } settings = ValidationSettings(**settings) - assert settings.inherit_profiles, "The inheritance mode should be set to True" + assert settings.enable_profile_inheritance, "The inheritance mode should be set to True" validator = Validator(settings) # initialize the validation context - context = ValidationContext(validator, validator.validation_settings.to_dict()) + context = ValidationContext(validator, validator.validation_settings) # Load the profiles profiles = context.profiles @@ -186,13 +186,13 @@ def __perform_test__(profile_identifier: str, expected_inherited_profiles: list[ settings = { "profiles_path": fake_profiles_path, "profile_identifier": profile_identifier, - "data_path": ValidROC().wrroc_paper, + "rocrate_uri": ValidROC().wrroc_paper, "disable_check_for_duplicates": True, } validator = Validator(settings) # initialize the validation context - context = ValidationContext(validator, validator.validation_settings.to_dict()) + context = ValidationContext(validator, validator.validation_settings) # Check if the inheritance mode is set to True assert context.inheritance_enabled @@ -226,18 +226,18 @@ def test_load_invalid_profile_no_override_enabled(fake_profiles_path: str): settings = { "profiles_path": fake_profiles_path, "profile_identifier": "invalid-duplicated-shapes", - "data_path": ValidROC().wrroc_paper, - "inherit_profiles": True, + "rocrate_uri": ValidROC().wrroc_paper, + "enable_profile_inheritance": True, "allow_requirement_check_override": False, } settings = ValidationSettings(**settings) - assert settings.inherit_profiles, "The inheritance mode should be set to True" + assert settings.enable_profile_inheritance, "The inheritance mode should be set to True" assert not settings.allow_requirement_check_override, "The override mode should be set to False" validator = Validator(settings) # initialize the validation context - context = ValidationContext(validator, validator.validation_settings.to_dict()) + context = ValidationContext(validator, validator.validation_settings) with pytest.raises(DuplicateRequirementCheck): # Load the profiles @@ -250,17 +250,17 @@ def test_load_invalid_profile_with_override_on_same_profile(fake_profiles_path: settings = { "profiles_path": fake_profiles_path, "profile_identifier": "invalid-duplicated-shapes", - "data_path": ValidROC().wrroc_paper, - "inherit_profiles": True, + "rocrate_uri": ValidROC().wrroc_paper, + "enable_profile_inheritance": True, "allow_requirement_check_override": False } settings = ValidationSettings(**settings) - assert settings.inherit_profiles, "The inheritance mode should be set to True" + assert settings.enable_profile_inheritance, "The inheritance mode should be set to True" assert not settings.allow_requirement_check_override, "The override mode should be set to `True`" validator = Validator(settings) # initialize the validation context - context = ValidationContext(validator, validator.validation_settings.to_dict()) + context = ValidationContext(validator, validator.validation_settings) with pytest.raises(DuplicateRequirementCheck): # Load the profiles @@ -273,17 +273,17 @@ def test_load_valid_profile_with_override_on_inherited_profile(fake_profiles_pat settings = { "profiles_path": fake_profiles_path, "profile_identifier": "c-overridden", - "data_path": ValidROC().wrroc_paper, - "inherit_profiles": True, + "rocrate_uri": ValidROC().wrroc_paper, + "enable_profile_inheritance": True, "allow_requirement_check_override": True } settings = ValidationSettings(**settings) - assert settings.inherit_profiles, "The inheritance mode should be set to True" + assert settings.enable_profile_inheritance, "The inheritance mode should be set to True" assert settings.allow_requirement_check_override, "The override mode should be set to `True`" validator = Validator(settings) # initialize the validation context - context = ValidationContext(validator, validator.validation_settings.to_dict()) + context = ValidationContext(validator, validator.validation_settings) # Load the profiles profiles = context.profiles diff --git a/tests/unit/test_cli_internals.py b/tests/unit/test_cli_internals.py index f65f5ba9..5fb439d8 100644 --- a/tests/unit/test_cli_internals.py +++ b/tests/unit/test_cli_internals.py @@ -29,7 +29,7 @@ def test_compute_stats(fake_profiles_path): "fail_fast": False, "profiles_path": fake_profiles_path, "profile_identifier": "a", - "inherit_profiles": True, + "enable_profile_inheritance": True, "allow_requirement_check_override": True, "requirement_severity": "REQUIRED", } diff --git a/tests/unit/test_validation_settings.py b/tests/unit/test_validation_settings.py index 1160a4e0..abd6519a 100644 --- a/tests/unit/test_validation_settings.py +++ b/tests/unit/test_validation_settings.py @@ -19,34 +19,33 @@ def test_validation_settings_parse_dict(): settings_dict = { - "data_path": "/path/to/data", + "rocrate_uri": "/path/to/data", "profiles_path": "/path/to/profiles", "requirement_severity": "RECOMMENDED", "allow_infos": True, - "inherit_profiles": False, + "enable_profile_inheritance": False, } settings = ValidationSettings.parse(settings_dict) - assert settings.data_path == "/path/to/data" + assert str(settings.rocrate_uri) == "/path/to/data" assert settings.profiles_path == "/path/to/profiles" assert settings.requirement_severity == Severity.RECOMMENDED assert settings.allow_infos is True - assert settings.inherit_profiles is False + assert settings.enable_profile_inheritance is False def test_validation_settings_parse_object(): existing_settings = ValidationSettings( - data_path="/path/to/data", + rocrate_uri="/path/to/data", profiles_path="/path/to/profiles", requirement_severity=Severity.RECOMMENDED, allow_infos=True, - inherit_profiles=False + enable_profile_inheritance=False ) settings = ValidationSettings.parse(existing_settings) - assert settings.data_path == "/path/to/data" + assert str(settings.rocrate_uri) == "/path/to/data" assert settings.profiles_path == "/path/to/profiles" assert settings.requirement_severity == Severity.RECOMMENDED - assert settings.allow_infos is True - assert settings.inherit_profiles is False + assert settings.enable_profile_inheritance is False def test_validation_settings_parse_invalid_type(): @@ -56,31 +55,30 @@ def test_validation_settings_parse_invalid_type(): def test_validation_settings_to_dict(): settings = ValidationSettings( - data_path="/path/to/data", + rocrate_uri="/path/to/data", profiles_path="/path/to/profiles", requirement_severity=Severity.RECOMMENDED, allow_infos=True, - inherit_profiles=False + enable_profile_inheritance=False ) settings_dict = settings.to_dict() - assert settings_dict["data_path"] == "/path/to/data" + assert settings_dict["rocrate_uri"] == "/path/to/data" assert settings_dict["profiles_path"] == "/path/to/profiles" assert settings_dict["requirement_severity"] == Severity.RECOMMENDED - assert settings_dict["allow_infos"] is True - assert settings_dict["inherit_profiles"] is False + assert settings_dict["enable_profile_inheritance"] is False -def test_validation_settings_inherit_profiles(): - settings = ValidationSettings(inherit_profiles=True) - assert settings.inherit_profiles is True +def test_validation_settings_enable_profile_inheritance(): + settings = ValidationSettings(enable_profile_inheritance=True) + assert settings.enable_profile_inheritance is True - settings = ValidationSettings(inherit_profiles=False) - assert settings.inherit_profiles is False + settings = ValidationSettings(enable_profile_inheritance=False) + assert settings.enable_profile_inheritance is False def test_validation_settings_data_path(): - settings = ValidationSettings(data_path="/path/to/data") - assert settings.data_path == "/path/to/data" + settings = ValidationSettings(rocrate_uri="/path/to/data") + assert str(settings.rocrate_uri) == "/path/to/data" def test_validation_settings_profiles_path():