diff --git a/README.md b/README.md index 9d9b35d..ed7b36a 100644 --- a/README.md +++ b/README.md @@ -318,8 +318,9 @@ The RustFS console is at `http://localhost:9001` (default credentials are To validate against profiles other than the bundled ones, mount a profiles directory into **both** the flask and worker containers (metadata validation -runs in flask; crate validation runs in the worker) and set `PROFILES_PATH` to -the mounted path. See `docker-compose-develop.yml` for a working example. +runs in flask; crate validation runs in the worker) and set `EXTRA_PROFILES_PATH` +to the mounted path. This keeps the bundled base profiles available to the +custom profiles. See `docker-compose-develop.yml` for a working example. ## Development diff --git a/docs/api.md b/docs/api.md index 8b41fb7..1e85ca8 100644 --- a/docs/api.md +++ b/docs/api.md @@ -14,10 +14,10 @@ This validates the contents of an `ro-crate-metadata.json` document and returns | Field | Required | Description | |-------|----------|-------------| | `crate_json` | yes | The metadata document, as a JSON string | -| `profile_name` | no | Profile to validate against, e.g. `ro-crate-1.2`. The validator will default to `ro-crate-1.1` when this is omitted | +| `profile_name` | no | Profile to validate against, e.g. `ro-crate-1.2`. The validator will default to `ro-crate-1.3` when this is omitted | !!! warning - Currently, the validation profile is not detected from the RO-Crate. In other words, a `conformsTo` declaration in the metadata does not influence which validation profile is used by the validator, and the validation always runs against `profile_name`, or `ro-crate-1.1` when it is omitted. + Currently, the validation profile is not detected from the RO-Crate. In other words, a `conformsTo` declaration in the metadata does not influence which validation profile is used by the validator, and the validation always runs against `profile_name`, or `ro-crate-1.3` when it is omitted. To validate an ro-crate metadata file: @@ -47,7 +47,7 @@ This queues validation of an RO-Crate held in the object store. The RO-Crate is | Field | Required | Description | |-------|----------|-------------| -| `profile_name` | no | Profile to validate against; defaults to `ro-crate-1.1` when omitted | +| `profile_name` | no | Profile to validate against; defaults to `ro-crate-1.3` when omitted | | `webhook_url` | no | URL that receives the result when validation finishes | ```bash @@ -101,7 +101,7 @@ An RO-Crate's `status` can be: | `error` | The validation could not run; the reason is in an `error` field. No `detail` field is provided | !!! note - `detail` contains the complete validation report. `created_at` is the UTC time of a stored-crate validation, and `null` for metadata-only validation, which does not set it. `profile` is the requested profile name, or `null` when the default (`ro-crate-1.1`) was used. + `detail` contains the complete validation report. `created_at` is the UTC time of a stored-crate validation, and `null` for metadata-only validation, which does not set it. `profile` is the requested profile name, or `null` when the default (`ro-crate-1.3`) was used. For stored RO-Crates the same object is saved to `{S3_RESULTS_PREFIX}/.json` and returned by the GET endpoint. diff --git a/docs/five-safes.md b/docs/five-safes.md index e998c96..a57e2bf 100644 --- a/docs/five-safes.md +++ b/docs/five-safes.md @@ -18,14 +18,14 @@ The `five-safes-crate` profile itself is defined in the [eScienceLab rocrate-val A [complete example crate](https://github.com/eScienceLab/rocrate-validator/blob/five-safes-0.7.4-beta/tests/data/crates/valid/five-safes-crate-result/ro-crate-metadata.json) is available in the fork's test data. !!! note - The current `-fivesafes-profile` image pairs the profile with a base profile for RO-Crate 1.1, whilst the profile itself expects RO-Crate 1.2. The walkthrough below mounts the matched profile set instead. + The repository's profile directory contains custom profiles that depend on the validator's bundled RO-Crate profiles. Use `EXTRA_PROFILES_PATH` to keep those base profiles available. -For this walkthrough, run the service from a checkout of this repository, with the repository's profile set mounted in place of the bundled profiles: +For this walkthrough, run the service from a checkout of this repository, with the repository's custom profiles mounted alongside the bundled profiles: ```bash docker run --rm -p 5001:5000 \ -e FLASK_APP=wsgi.py \ - -e PROFILES_PATH=/app/profiles \ + -e EXTRA_PROFILES_PATH=/app/profiles \ -v "$PWD/tests/data/rocrate_validator_profiles:/app/profiles:ro" \ ghcr.io/esciencelab/ro-crate-validation-service-fivesafes-profile:latest ``` diff --git a/docs/installation.md b/docs/installation.md index bcbb0c3..44cfee6 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -102,10 +102,10 @@ The following can all be set as environment variables for the service using an ` The validator comes with several RO-Crate profiles, and for the Five Safes RO-Crate, the prebuilt `ghcr.io/esciencelab/ro-crate-validation-service-fivesafes-profile` image has the `five-safes-crate` profile already included; see [Five Safes validation](five-safes.md). -Other profiles can be provided by mounting the directory containing these profiles as a volume for the `flask` container. Mount the same directory as a volume for the `celery_worker` container as well if you have enabled stored-crate validation. Then set either the `EXTRA_PROFILES_PATH` or `PROFILES_PATH` environment variable to match the volume path. There is a working example in `docker-compose-develop.yml`. +Other profiles can be provided by mounting the directory containing these profiles as a volume for the `flask` container. Mount the same directory as a volume for the `celery_worker` container as well if you have enabled stored-crate validation. Then set `EXTRA_PROFILES_PATH` to match the volume path so the bundled base profiles remain available. There is a working example in `docker-compose-develop.yml`. !!! note - `EXTRA_PROFILES_PATH` adds the directory to the bundled profiles, whereas `PROFILES_PATH` replaces them entirely. The two can be set together, in which case the validator takes profiles from both locations. + `EXTRA_PROFILES_PATH` adds the directory to the bundled profiles. Use `PROFILES_PATH` only when supplying a complete set of replacement profiles, including any base profiles your custom profiles depend on. The two can be set together, in which case the validator takes profiles from both locations. ## Offline validation diff --git a/pyproject.toml b/pyproject.toml index fed3923..feb5224 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,15 +13,15 @@ authors = [{ name = "eScience Lab, The University of Manchester" }] # Direct runtime dependencies: dependencies = [ - "celery==5.6.3", - "boto3==1.43.97", + "celery==5.7.0", + "boto3==1.43.124", "requests==2.34.2", "Flask==3.1.3", - "Werkzeug==3.1.8", + "Werkzeug==3.1.9", "redis==8.1.0", - "python-dotenv==1.2.3", - "apiflask==3.1.2", - "roc-validator==0.11.4", + "python-dotenv==1.2.4", + "apiflask==3.1.3", + "roc-validator==0.13.1", ] [project.optional-dependencies] diff --git a/requirements-dev.txt b/requirements-dev.txt index d984ee4..615d7d7 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -4,27 +4,29 @@ # # pip-compile --extra=dev --no-index --output-file=requirements-dev.txt # -amqp==5.3.1 +amqp==5.4.2 # via kombu annotated-types==0.8.0 # via pydantic -apiflask==3.1.2 +apiflask==3.1.3 # via ro-crate-validation-service (pyproject.toml) apispec==6.10.0 # via apiflask +async-timeout==5.0.1 + # via redis attrs==26.1.0 # via # cattrs # requests-cache -billiard==4.3.0 +billiard==4.3.2 # via celery blinker==1.9.0 # via flask -boto3==1.43.97 +boto3==1.43.124 # via # moto # ro-crate-validation-service (pyproject.toml) -botocore==1.43.97 +botocore==1.43.124 # via # boto3 # moto @@ -33,7 +35,7 @@ build==1.6.1 # via pip-tools cattrs==26.2.0 # via requests-cache -celery==5.6.3 +celery==5.7.0 # via ro-crate-validation-service (pyproject.toml) certifi==2026.7.22 # via requests @@ -55,7 +57,7 @@ click-didyoumean==0.3.1 # via celery click-plugins==1.1.1.2 # via celery -click-repl==0.4.0 +click-repl==0.4.1 # via celery colorlog==6.12.0 # via roc-validator @@ -98,7 +100,7 @@ jmespath==1.1.0 # via # boto3 # botocore -kombu==5.6.2 +kombu==5.7.0 # via celery markdown-it-py==4.2.0 # via rich @@ -122,7 +124,6 @@ packaging==26.3 # via # apispec # build - # kombu # pyshacl # pytest # webargs @@ -172,7 +173,7 @@ python-dateutil==2.9.0.post0 # via # botocore # celery -python-dotenv==1.2.3 +python-dotenv==1.2.4 # via ro-crate-validation-service (pyproject.toml) pyyaml==6.0.3 # via @@ -202,7 +203,7 @@ rich==13.9.4 # roc-validator rich-click==1.9.9 # via roc-validator -roc-validator==0.11.4 +roc-validator==0.13.1 # via ro-crate-validation-service (pyproject.toml) ruff==0.16.8 # via ro-crate-validation-service (pyproject.toml) @@ -222,8 +223,6 @@ typing-extensions==4.16.0 # typing-inspection typing-inspection==0.4.4 # via pydantic -tzdata==2026.4 - # via kombu tzlocal==5.4.4 # via celery url-normalize==3.0.0 @@ -245,7 +244,7 @@ wcwidth==0.8.4 # prompt-toolkit webargs==8.7.1 # via apiflask -werkzeug==3.1.8 +werkzeug==3.1.9 # via # flask # moto diff --git a/requirements.txt b/requirements.txt index 397703e..224388d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,31 +4,33 @@ # # pip-compile --no-index --output-file=requirements.txt # -amqp==5.3.1 +amqp==5.4.2 # via kombu annotated-types==0.8.0 # via pydantic -apiflask==3.1.2 +apiflask==3.1.3 # via ro-crate-validation-service (pyproject.toml) apispec==6.10.0 # via apiflask +async-timeout==5.0.1 + # via redis attrs==26.1.0 # via # cattrs # requests-cache -billiard==4.3.0 +billiard==4.3.2 # via celery blinker==1.9.0 # via flask -boto3==1.43.97 +boto3==1.43.124 # via ro-crate-validation-service (pyproject.toml) -botocore==1.43.97 +botocore==1.43.124 # via # boto3 # s3transfer cattrs==26.2.0 # via requests-cache -celery==5.6.3 +celery==5.7.0 # via ro-crate-validation-service (pyproject.toml) certifi==2026.7.22 # via requests @@ -47,7 +49,7 @@ click-didyoumean==0.3.1 # via celery click-plugins==1.1.1.2 # via celery -click-repl==0.4.0 +click-repl==0.4.1 # via celery colorlog==6.12.0 # via roc-validator @@ -86,7 +88,7 @@ jmespath==1.1.0 # via # boto3 # botocore -kombu==5.6.2 +kombu==5.7.0 # via celery markdown-it-py==4.2.0 # via rich @@ -107,7 +109,6 @@ owlrl==7.6.2 packaging==26.3 # via # apispec - # kombu # pyshacl # webargs pfzy==0.3.4 @@ -136,7 +137,7 @@ python-dateutil==2.9.0.post0 # via # botocore # celery -python-dotenv==1.2.3 +python-dotenv==1.2.4 # via ro-crate-validation-service (pyproject.toml) rdflib[html]==7.6.0 # via @@ -158,7 +159,7 @@ rich==13.9.4 # roc-validator rich-click==1.9.9 # via roc-validator -roc-validator==0.11.4 +roc-validator==0.13.1 # via ro-crate-validation-service (pyproject.toml) s3transfer==0.19.2 # via boto3 @@ -176,8 +177,6 @@ typing-extensions==4.16.0 # typing-inspection typing-inspection==0.4.4 # via pydantic -tzdata==2026.4 - # via kombu tzlocal==5.4.4 # via celery url-normalize==3.0.0 @@ -198,7 +197,7 @@ wcwidth==0.8.4 # prompt-toolkit webargs==8.7.1 # via apiflask -werkzeug==3.1.8 +werkzeug==3.1.9 # via # flask # ro-crate-validation-service (pyproject.toml) diff --git a/tests/data/ro-crate-metadata.json b/tests/data/ro-crate-metadata.json index 957ddea..cef48fb 100644 --- a/tests/data/ro-crate-metadata.json +++ b/tests/data/ro-crate-metadata.json @@ -1,5 +1,5 @@ { - "@context": "https://w3id.org/ro/crate/1.1/context", + "@context": "https://w3id.org/ro/crate/1.3/context", "@graph": [ { "@id": "./", @@ -32,7 +32,7 @@ "@id": "./" }, "conformsTo": { - "@id": "https://w3id.org/ro/crate/1.1" + "@id": "https://w3id.org/ro/crate/1.3" } }, { diff --git a/tests/data/rocrate_validator_profiles/alpha-crate/1_root_data_entity.ttl b/tests/data/rocrate_validator_profiles/alpha-crate/1_root_data_entity.ttl index af82b5d..6923d34 100644 --- a/tests/data/rocrate_validator_profiles/alpha-crate/1_root_data_entity.ttl +++ b/tests/data/rocrate_validator_profiles/alpha-crate/1_root_data_entity.ttl @@ -29,18 +29,9 @@ alpha-crate:RootDataEntityRequiredProperties sh:property [ a sh:PropertyShape ; - sh:name "sourceOrganization" ; - sh:path schema:sourceOrganization; + sh:name "datePublished" ; + sh:path schema:datePublished; sh:minCount 1 ; sh:severity sh:Violation ; - sh:message """The Root Data Entity MUST have a `sourceOrganization` property.""" ; - ] ; - - sh:property [ - a sh:PropertyShape ; - sh:name "sourceOrganization" ; - sh:path schema:sourceOrganization ; - sh:class schema:Project ; - sh:severity sh:Violation ; - sh:message """The `sourceOrganization` property of the RootDataEntity MUST point to a Project entity.""" ; + sh:message """The Root Data Entity MUST have a `datePublished` property.""" ; ] . diff --git a/tests/data/rocrate_validator_profiles/ro-crate/may/4_data_entity_metadata.ttl b/tests/data/rocrate_validator_profiles/ro-crate/may/4_data_entity_metadata.ttl deleted file mode 100644 index 4dfd242..0000000 --- a/tests/data/rocrate_validator_profiles/ro-crate/may/4_data_entity_metadata.ttl +++ /dev/null @@ -1,89 +0,0 @@ -# Copyright (c) 2024-2026 CRS4 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -@prefix ro: <./> . -@prefix ro-crate: . -@prefix rdf: . -@prefix sh: . -@prefix xsd: . -@prefix owl: . -@prefix schema: . -@prefix validator: . - -ro-crate:FileDataEntityWebOptionalProperties a sh:NodeShape ; - sh:name "File Data Entity with web presence: OPTIONAL properties" ; - sh:description """A File Data Entity which have a corresponding web presence, - for instance a landing page that describes the file, including persistence identifiers (e.g. DOI), - resolving to an intermediate HTML page instead of the downloadable file directly. - These can included for File Data Entities as additional metadata by using the properties: - `identifier`, `url`, `subjectOf`and `mainEntityOfPage`""" ; - sh:targetClass ro-crate:File ; - # Check if the Web-based Data Entity has a contentSize property - sh:property [ - a sh:PropertyShape ; - sh:minCount 1 ; - sh:name "File Data Entity: optional formal `identifier` (e.g. DOI)" ; - sh:description """Check if the File Data Entity has a formal identifier string such as a DOI""" ; - sh:path schema:identifier ; - sh:datatype xsd:anyURI ; - sh:severity sh:Info ; - sh:message """The File Data Entity MAY have a formal identifier specified through an `identifier` property""" ; - ] ; - sh:property [ - a sh:PropertyShape ; - sh:minCount 1 ; - sh:name "File Data Entity: optional `url` property" ; - sh:description """Check if the File Data Entity has an optional `download` link""" ; - sh:path schema:url ; - sh:datatype xsd:anyURI ; - sh:severity sh:Info ; - sh:message """The File Data Entity MAY use a `url` property to denote a `download` link""" ; - ] ; - sh:property [ - a sh:PropertyShape ; - sh:minCount 1 ; - sh:name "File Data Entity: optional `subjectOf` property" ; - sh:description """Check if the File Data Entity includes a `subjectOf` property to link `CreativeWork` instances that mention it.""" ; - sh:path schema:subjectOf ; - sh:class schema:WebPage, schema:CreativeWork ; - sh:severity sh:Info ; - sh:message """The File Data Entity MAY include a `subjectOf` property to link `CreativeWork` instances that mention it.""" ; - ] ; - sh:property [ - a sh:PropertyShape ; - sh:minCount 1 ; - sh:name "File Data Entity: optional `mainEntityOfPage` property" ; - sh:description """Check if the File Data Entity has a `mainEntityOfPage` property""" ; - sh:path schema:mainEntityOfPage ; - sh:class schema:WebPage, schema:CreativeWork ; - sh:severity sh:Info ; - sh:message """The File Data Entity MAY have a `mainEntityOfPage` property""" ; - ] . - - -ro-crate:DirectoryDataEntityWebOptionalDistribution a sh:NodeShape ; - sh:name "Directory Data Entity: OPTIONAL `distribution` property" ; - sh:description """A Directory Data Entity MAY have a `distribution` property to denote the distribution of the files within the directory""" ; - sh:targetClass ro-crate:File ; - # Check if the Web-based Data Entity has a contentSize property - sh:property [ - a sh:PropertyShape ; - sh:minCount 1 ; - sh:name "Directory Data Entity: optional `distribution` property" ; - sh:description """Check if the Directory Data Entity has a `distribution` property""" ; - sh:path schema:distribution ; - sh:datatype xsd:anyURI ; - sh:severity sh:Info ; - sh:message """The Directory Data Entity MAY have a `distribution` property to denote the distribution of the files within the directory""" ; - ] . diff --git a/tests/data/rocrate_validator_profiles/ro-crate/may/61_license_entity.ttl b/tests/data/rocrate_validator_profiles/ro-crate/may/61_license_entity.ttl deleted file mode 100644 index 202af89..0000000 --- a/tests/data/rocrate_validator_profiles/ro-crate/may/61_license_entity.ttl +++ /dev/null @@ -1,66 +0,0 @@ -# Copyright (c) 2024-2026 CRS4 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -@prefix ro: <./> . -@prefix ro-crate: . -@prefix rdf: . -@prefix schema_org: . -@prefix sh: . -@prefix xsd: . - - -ro-crate:LicenseOptionalAllowedValues a sh:NodeShape ; - sh:name "Root Data Entity: optional properties" ; - sh:description """Define the optional properties for the Root Data Entity (e.g., license)""" ; - sh:targetClass ro-crate:RootDataEntity ; - sh:property [ - a sh:PropertyShape ; - sh:name "License" ; - sh:description """Check if the RO-Crate has a license property with a URI or a textual description""" ; - sh:message """MAY have a URI (eg for Creative Commons or Open Source licenses). - MAY, if necessary be a textual description of how the RO-Crate may be used.""" ; - sh:minCount 1 ; - sh:nodeKind sh:IRIOrLiteral ; - sh:path schema_org:license ; - sh:or ( - [ sh:dataType xsd:string ] - [ sh:dataType xsd:anyURI ] - ) ; - ]. - -ro-crate:LicenseDefinition a sh:NodeShape ; - sh:name "License definition" ; - sh:description """Contextual entity representing a license with a name and description."""; - sh:targetClass schema_org:license ; - sh:property [ - a sh:PropertyShape ; - sh:name "License name" ; - sh:description "The license MAY have a name" ; - sh:minCount 1 ; - sh:maxCount 1 ; - sh:nodeKind sh:Literal ; - sh:path schema_org:name ; - sh:message "Missing license name" ; - ] ; - sh:property [ - a sh:PropertyShape ; - sh:name "License description" ; - sh:description """The license MAY have a description""" ; - sh:maxCount 1; - sh:minCount 1 ; - sh:nodeKind sh:Literal ; - sh:path schema_org:description ; - sh:message "Missing license description" ; - ] . - diff --git a/tests/data/rocrate_validator_profiles/ro-crate/must/0_file_descriptor_format.py b/tests/data/rocrate_validator_profiles/ro-crate/must/0_file_descriptor_format.py deleted file mode 100644 index fa02f64..0000000 --- a/tests/data/rocrate_validator_profiles/ro-crate/must/0_file_descriptor_format.py +++ /dev/null @@ -1,401 +0,0 @@ -# Copyright (c) 2024-2026 CRS4 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from typing import Any - -from rocrate_validator.utils import log as logging -from rocrate_validator.models import ValidationContext -from rocrate_validator.requirements.python import (PyFunctionCheck, check, - requirement) -from rocrate_validator.utils.http import HttpRequester - -# set up logging -logger = logging.getLogger(__name__) - - -@requirement(name="File Descriptor existence") -class FileDescriptorExistence(PyFunctionCheck): - """The file descriptor MUST be present in the RO-Crate and MUST not be empty.""" - - @check(name="File Descriptor Existence") - def test_existence(self, context: ValidationContext) -> bool: - """ - Check if the file descriptor is present in the RO-Crate - """ - if context.settings.metadata_only: - logger.debug("Skipping file descriptor existence check in metadata-only mode") - return True - if not context.ro_crate.has_descriptor(): - message = f'file descriptor "{context.rel_fd_path}" is not present' - context.result.add_issue(message, self) - return False - return True - - @check(name="File Descriptor size check") - def test_size(self, context: ValidationContext) -> bool: - """ - Check if the file descriptor is not empty - """ - if context.settings.metadata_only: - logger.debug("Skipping file descriptor existence check in metadata-only mode") - return True - if not context.ro_crate.has_descriptor(): - message = f'file descriptor {context.rel_fd_path} is empty' - context.result.add_issue(message, self) - return False - if context.ro_crate.metadata.size == 0: - context.result.add_issue(f'RO-Crate "{context.rel_fd_path}" file descriptor is empty', self) - return False - return True - - -@requirement(name="File Descriptor JSON format") -class FileDescriptorJsonFormat(PyFunctionCheck): - """ - The file descriptor MUST be a valid JSON file - """ - @check(name="File Descriptor JSON format") - def check(self, context: ValidationContext) -> bool: - """ Check if the file descriptor is in the correct format""" - try: - logger.debug("Checking validity of JSON file at %s", context.ro_crate.metadata) - context.ro_crate.metadata.as_dict() - return True - except Exception as e: - context.result.add_issue( - f'RO-Crate file descriptor "{context.rel_fd_path}" is not in the correct format', self) - if logger.isEnabledFor(logging.DEBUG): - logger.exception(e) - return False - - -@requirement(name="File Descriptor JSON-LD format") -class FileDescriptorJsonLdFormat(PyFunctionCheck): - """ - The file descriptor MUST be a valid JSON-LD file - """ - - def __check_remote_context__(self, context_uri: str) -> bool: - # Try to retrieve the context - try: - raw_data = HttpRequester().get(context_uri, headers={"Accept": "application/ld+json"}) - if raw_data.status_code != 200: - raise RuntimeError(f"Unable to retrieve the JSON-LD context '{context_uri}'", self) - logger.debug(f"Retrieved context from {context_uri}") - - # Try to parse the JSON-LD and access the context - jsonLD = raw_data.json()["@context"] - assert isinstance(jsonLD, dict) - return True - except Exception as e: - if logger.isEnabledFor(logging.DEBUG): - logger.exception(e) - return False - - def __check_contexts__(self, context: ValidationContext, jsonld_context: object) -> bool: - """ Get the keys of the context URI """ - is_valid = True - # if the context is a string, check if it is a valid URI - if isinstance(jsonld_context, str): - if not self.__check_remote_context__(jsonld_context): - context.result.add_issue( - f'Unable to retrieve the JSON-LD context "{jsonld_context}"', self) - is_valid = False - - # if the context is a dictionary, get the keys of the dictionary - if isinstance(jsonld_context, dict): - logger.debug(f"Detected dictionary context: {jsonld_context}") - - # if the context is a list of contexts, get the keys of each context - if isinstance(jsonld_context, list): - for ctx in jsonld_context: - if not self.__check_contexts__(context, ctx): - is_valid = False - # return if the context is valid - return is_valid - - @check(name="File Descriptor @context property validation") - def check_context(self, context: ValidationContext) -> bool: - """ Check if the file descriptor contains - the @context property and it is a valid JSON-LD context - """ - try: - json_dict = context.ro_crate.metadata.as_dict() - if "@context" not in json_dict: - context.result.add_issue( - f'RO-Crate file descriptor "{context.rel_fd_path}" ' - "does not contain a context", self) - return False - - # Check if the context is valid - return self.__check_contexts__(context, json_dict["@context"]) - except Exception as e: - if logger.isEnabledFor(logging.DEBUG): - logger.exception(e) - return False - - @check(name="File Descriptor JSON-LD must be flattened") - def check_flattened(self, context: ValidationContext) -> bool: - """ Check if the file descriptor is flattened """ - - def is_entity_flat_recursive(entity: Any, is_first: bool = True, fail_fast: bool = False) -> bool: - """ Recursively check if the given data corresponds to a flattened JSON-LD object - and returns False if it does not and is not a root element - """ - result = True - if isinstance(entity, dict): - if is_first: - for _, elem in entity.items(): - if not is_entity_flat_recursive(elem, is_first=False, fail_fast=fail_fast): - result = False - if fail_fast: - return False - # if this is not the root element, it must not contain more properties than @id - else: - if "@id" in entity and "@value" in entity: - # add issue if both @id and @value are present - context.result.add_issue( - ( - f'entity "{entity.get("@id", entity)}" contains both @id and @value: ' - 'an object with an @value represents a value object, which is a literal value such as ' - 'a string, number, date, or language-tagged string. This object is not an identifiable ' - 'resource, but a simple literal value.' - ), - self - ) - result = False - if fail_fast: - return False - - # Handle value objects - if "@value" in entity: - # Inline the checks from is_value_object and add issues for each violation - if not isinstance(entity, dict): - context.result.add_issue( - f'entity "{entity.get("@id", entity)}" is not a valid value object: ' - 'it MUST be a dictionary.', - self - ) - result = False - if fail_fast: - return False - - has_language = "@language" in entity - has_type = "@type" in entity - - if has_language and has_type: - context.result.add_issue( - f'entity "{entity.get("@id", entity)}" is not a valid value object: ' - '@language and @type cannot coexist.', - self - ) - result = False - if fail_fast: - return False - - if has_language and not isinstance(entity["@value"], str): - context.result.add_issue( - f'entity "{entity.get("@id", entity)}" is not a valid value object: ' - 'if @language is present, @value must be a string.', - self - ) - result = False - if fail_fast: - return False - # Handle node objects: - # every remaining entity with len(entity) > 1 must be a node object - elif "@id" not in entity or len(entity) > 1: - context.result.add_issue( - f'entity "{entity.get("@id", entity)}" is not a valid node object reference: ' - 'it MUST have only @id, but no other properties.', - self - ) - result = False - if fail_fast: - return False - if isinstance(entity, list): - for element in entity: - if not is_entity_flat_recursive(element, is_first=False, fail_fast=fail_fast): - result = False - if fail_fast: - return False - return result - - try: - fail_fast = context.settings.abort_on_first - json_dict = context.ro_crate.metadata.as_dict() - result = True - for entity in json_dict["@graph"]: - if not is_entity_flat_recursive(entity, fail_fast=fail_fast): - context.result.add_issue( - f'RO-Crate file descriptor "{context.rel_fd_path}" ' - f'is not fully flattened at entity "{entity.get("@id", entity)}"', self) - result = False - if fail_fast: - return False - return result - except Exception as e: - if logger.isEnabledFor(logging.DEBUG): - logger.exception(e) - return False - - @check(name="Validation of the @id property of the file descriptor entities") - def check_identifiers(self, context: ValidationContext) -> bool: - """ Check if the file descriptor entities have the @id property """ - try: - json_dict = context.ro_crate.metadata.as_dict() - for entity in json_dict["@graph"]: - if "@id" not in entity: - context.result.add_issue( - f"Entity \"{entity.get('name', None) or entity}\" " - f"of RO-Crate \"{context.rel_fd_path}\" " - "file descriptor does not contain the @id attribute", self) - return False - return True - except Exception as e: - if logger.isEnabledFor(logging.DEBUG): - logger.exception(e) - return False - - @check(name="Validation of the @type property of the file descriptor entities") - def check_types(self, context: ValidationContext) -> bool: - """ Check if the file descriptor entities have the @type property """ - try: - json_dict = context.ro_crate.metadata.as_dict() - for entity in json_dict["@graph"]: - if "@type" not in entity: - context.result.add_issue( - f"Entity \"{entity.get('name', None) or entity}\" " - f"of RO-Crate \"{context.rel_fd_path}\" " - "file descriptor does not contain the @type attribute", self) - return False - return True - except Exception as e: - if logger.isEnabledFor(logging.DEBUG): - logger.exception(e) - return False - - def __get_context_keys__(self, context: object) -> set: - """ Get the keys of the context URI """ - if isinstance(context, str): - return self.__get_remote_context_keys__(context) - - # if the context is a dictionary, get the keys of the dictionary - if isinstance(context, dict): - return set(context.keys()) - - # if the context is a list of contexts, get the keys of each context - if isinstance(context, list): - keys = set() - for ctx in context: - keys.update(self.__get_context_keys__(ctx)) - return keys - - def __get_remote_context_keys__(self, context_uri: str) -> set: - """ Get the keys of the context URI """ - - logger.debug(f"Retrieving context from {context_uri}...") - # Try to retrieve the context - raw_data = HttpRequester().get(context_uri, headers={"Accept": "application/ld+json"}) - if raw_data.status_code != 200: - raise RuntimeError(f"Unable to retrieve the JSON-LD context '{context_uri}'") - - logger.debug(f"Retrieved context from {context_uri}") - - # Get the keys of the context - jsonLD = raw_data.json() - jsonLD_ctx = jsonLD["@context"] - if not isinstance(jsonLD_ctx, dict): - raise RuntimeError("The context is not a dictionary", self) - return set(jsonLD_ctx.keys()) - - def __check_entity_keys__(self, entity: dict, - context_keys: set, - unexpected_keys: dict[str, int] = None) -> dict[str, int]: - """ Check if the entity is in the correct format """ - - def add_unexpected_key(k: str, u_keys: dict) -> None: - """ Add a key to the unexpected keys dictionary """ - u_keys[k] = u_keys.get(k, 0) + 1 - - # Keys that should be skipped - SKIP_KEYS = {"@id", "@type", "@context", "@value", "@language"} - - # Ensure unexpected_keys is initialized - if unexpected_keys is None: - unexpected_keys = {} - - # If the entity is a dictionary, check each key - if isinstance(entity, dict): - for k, v in entity.items(): - if k not in context_keys and k not in SKIP_KEYS: - logger.debug(f"Key {k} not in context keys") - add_unexpected_key(k, unexpected_keys) - if isinstance(v, (dict, list)): - self.__check_entity_keys__(v, context_keys, unexpected_keys) - - # If the entity is a list, check each element - elif isinstance(entity, list): - for elem in entity: - self.__check_entity_keys__(elem, context_keys, unexpected_keys) - - return unexpected_keys - - @check(name="Validation of the compaction format of the file descriptor") - def check_compaction(self, context: ValidationContext) -> bool: - """ Check if the file descriptor is in the **compacted** JSON-LD format """ - try: - logger.debug("Checking compaction format of JSON-LD file at %s", context.ro_crate.metadata) - json_dict = context.ro_crate.metadata.as_dict() - logger.debug(f"JSONLD keys:{json_dict.keys()}") - - jsonld_context = json_dict.get("@context", None) - logger.debug(f"Context: {jsonld_context}") - - try: - context_keys = self.__get_context_keys__(jsonld_context) - logger.debug(f"{context_keys}") - except Exception as e: - if logger.isEnabledFor(logging.DEBUG): - logger.exception(e) - context.result.add_issue(str(e), self) - return False - - unexpected_keys = self.__check_entity_keys__(json_dict.get("@graph"), context_keys) - logger.debug(f"Unexpected keys: {unexpected_keys}") - if len(unexpected_keys) > 0: - for k, v in unexpected_keys.items(): - logger.debug(f"Key {k} appears {v} times") - # Add the correct suffix to the message - suffix = "s" if v > 1 else "" - # Check if k is a term or a URI - if k.startswith("http"): - context.result.add_issue( - f'The The {v} occurrence{suffix} of the "{k}" URI cannot be used as a key{suffix} "' - 'because the compacted format requires simple terms as keys ' - '(see https://www.w3.org/TR/json-ld-api/#compaction for more details).', self) - else: - context.result.add_issue( - f'The {v} occurrence{suffix} of the JSON-LD key "{k}" ' - f'{"is" if v == 1 else "are"} not allowed in the compacted format ' - 'because it is not present in the @context of the document', self) - return False - - return True - except Exception as e: - if logger.isEnabledFor(logging.DEBUG): - logger.exception(e) - context.result.add_issue( - f'Unexpected error: {e}', self) - return False diff --git a/tests/data/rocrate_validator_profiles/ro-crate/must/1_file-descriptor_metadata.ttl b/tests/data/rocrate_validator_profiles/ro-crate/must/1_file-descriptor_metadata.ttl deleted file mode 100644 index 32d2a63..0000000 --- a/tests/data/rocrate_validator_profiles/ro-crate/must/1_file-descriptor_metadata.ttl +++ /dev/null @@ -1,100 +0,0 @@ -# Copyright (c) 2024-2026 CRS4 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -@prefix ro: <./> . -@prefix ro-crate: . -@prefix dct: . -@prefix rdf: . -@prefix schema_org: . -@prefix sh: . -@prefix validator: . - - -ro-crate:FindROCrateMetadataFileDescriptorEntity a sh:NodeShape, validator:HiddenShape; - sh:name "Identify the RO-Crate Metadata File Descriptor" ; - sh:description """The RO-Crate Metadata File Descriptor entity describes the RO-Crate itself, and it is named as `ro-crate-metadata.json`. - It can be identified by name according to the RO-Crate specification - available at [Finding RO-Crate Root in RDF triple stores](https://www.researchobject.org/ro-crate/1.1/appendix/relative-uris.html#finding-ro-crate-root-in-rdf-triple-stores).""" ; - sh:target [ - a sh:SPARQLTarget ; - sh:prefixes ro-crate:sparqlPrefixes ; - sh:select """ - SELECT ?this - WHERE { - ?this a schema:CreativeWork ; - FILTER(contains(str(?this), "ro-crate-metadata.json")) - } - """ - ] ; - - # Expand data graph with triples from the file data entity - sh:rule [ - a sh:TripleRule ; - sh:subject sh:this ; - sh:predicate rdf:type ; - sh:object ro-crate:ROCrateMetadataFileDescriptor ; - ] . - -ro-crate:ROCrateMetadataFileDescriptorExistence - a sh:NodeShape ; - sh:name "RO-Crate Metadata File Descriptor entity existence" ; - sh:description "The RO-Crate JSON-LD MUST contain a Metadata File Descriptor entity named `ro-crate-metadata.json` and typed as `schema:CreativeWork`" ; - sh:targetNode ro:ro-crate-metadata.json ; - sh:property [ - a sh:PropertyShape ; - sh:name "RO-Crate Metadata File Descriptor entity existence" ; - sh:description """Check if the RO-Crate Metadata File Descriptor entity exists, - i.e., if there exists an entity with @id `ro-crate-metadata.json` and type `schema:CreativeWork`""" ; - sh:path rdf:type ; - sh:hasValue ro-crate:ROCrateMetadataFileDescriptor ; - sh:minCount 1 ; - sh:message "The root of the document MUST have an entity with @id `ro-crate-metadata.json`" ; - ] . - -ro-crate:ROCrateMetadataFileDescriptorRecommendedProperties a sh:NodeShape ; - sh:name "RO-Crate Metadata File Descriptor REQUIRED properties" ; - sh:description """RO-Crate Metadata Descriptor MUST be defined - according with the requirements details defined in - [RO-Crate Metadata File Descriptor](https://www.researchobject.org/ro-crate/1.1/root-data-entity.html#ro-crate-metadata-file-descriptor)"""; - sh:targetNode ro:ro-crate-metadata.json ; - sh:property [ - a sh:PropertyShape ; - sh:name "Metadata File Descriptor entity type" ; - sh:description "Check if the RO-Crate Metadata File Descriptor has `@type` CreativeWork, as per schema.org" ; - sh:minCount 1 ; - sh:nodeKind sh:IRI ; - sh:path rdf:type ; - sh:hasValue schema_org:CreativeWork ; - sh:message "The RO-Crate metadata file MUST be a CreativeWork, as per schema.org" ; - ] ; - sh:property [ - a sh:PropertyShape ; - sh:name "Metadata File Descriptor entity: `about` property" ; - sh:description """Check if the RO-Crate Metadata File Descriptor has an `about` property referencing the Root Data Entity""" ; - sh:maxCount 1; - sh:minCount 1 ; - sh:nodeKind sh:IRI ; - sh:path schema_org:about ; - sh:class schema_org:Dataset ; - sh:message "The RO-Crate metadata file descriptor MUST have an `about` property referencing the Root Data Entity" ; - ] ; - sh:property ro-crate:conformsToROCrateSpec . - -ro-crate:conformsToROCrateSpec sh:name "Metadata File Descriptor entity: `conformsTo` property" ; - sh:description """Check if the RO-Crate Metadata File Descriptor has a `conformsTo` property which points to the RO-Crate specification version""" ; - sh:minCount 1 ; - sh:nodeKind sh:IRI ; - sh:path dct:conformsTo ; - sh:hasValue ; - sh:message "The RO-Crate metadata file descriptor MUST have a `conformsTo` property with the RO-Crate specification version" . diff --git a/tests/data/rocrate_validator_profiles/ro-crate/must/2_root_data_entity_metadata.ttl b/tests/data/rocrate_validator_profiles/ro-crate/must/2_root_data_entity_metadata.ttl deleted file mode 100644 index 2c2efcf..0000000 --- a/tests/data/rocrate_validator_profiles/ro-crate/must/2_root_data_entity_metadata.ttl +++ /dev/null @@ -1,175 +0,0 @@ -# Copyright (c) 2024-2026 CRS4 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -@prefix ro: <./> . -@prefix ro-crate: . -@prefix rdf: . -@prefix schema_org: . -@prefix sh: . -@prefix validator: . -@prefix xsd: . - - -ro-crate:RootDataEntityType - a sh:NodeShape ; - sh:name "RO-Crate Root Data Entity type" ; - sh:description "The Root Data Entity MUST be a `Dataset` (as per `schema.org`)" ; - sh:target [ - a sh:SPARQLTarget ; - sh:prefixes ro-crate:sparqlPrefixes ; - sh:select """ - SELECT ?this - WHERE { - ?metadatafile schema:about ?this . - FILTER(contains(str(?metadatafile), "ro-crate-metadata.json")) - } - """ - ] ; - sh:property [ - a sh:PropertyShape ; - sh:name "Root Data Entity type" ; - sh:description "Check if the Root Data Entity is a `Dataset` (as per `schema.org`)" ; - sh:path rdf:type ; - sh:hasValue schema_org:Dataset ; - sh:minCount 1 ; - sh:message """The Root Data Entity MUST be a `Dataset` (as per `schema.org`)""" ; - ] ; - # Validate that if the publisher is specified, it is an Organization or a Person - sh:property [ - sh:path schema_org:publisher ; - sh:severity sh:Violation ; - sh:name "Root Data Entity: `publisher` property" ; - sh:description """Check if the Root Data Entity has a `publisher` property of type `Organization` or `Person`.""" ; - sh:or ( - [ sh:class schema_org:Organization ] - [ sh:class schema_org:Person ] - ) ; - sh:message """The Root Data Entity MUST have a `publisher` property of type `Organization` or `Person`.""" ; - ] . - - -ro-crate:FindRootDataEntity a sh:NodeShape, validator:HiddenShape; - sh:name "Identify the Root Data Entity of the RO-Crate" ; - sh:description """The Root Data Entity is the top-level Data Entity in the RO-Crate and serves as the starting point for the description of the RO-Crate. - It is a schema:Dataset and is indirectly identified by the about property of the resource ro-crate-metadata.json in the RO-Crate - (see the definition at [Finding RO-Crate Root in RDF triple stores](https://www.researchobject.org/ro-crate/1.1/appendix/relative-uris.html#finding-ro-crate-root-in-rdf-triple-stores)). - """ ; - sh:target [ - a sh:SPARQLTarget ; - sh:prefixes ro-crate:sparqlPrefixes ; - sh:select """ - SELECT ?this - WHERE { - ?this a schema:Dataset . - ?metadatafile schema:about ?this . - FILTER(contains(str(?metadatafile), "ro-crate-metadata.json")) - } - """ - ] ; - - # Expand data graph with triples from the file data entity - sh:rule [ - a sh:TripleRule ; - sh:subject sh:this ; - sh:predicate rdf:type ; - sh:object ro-crate:RootDataEntity ; - ] . - - -ro-crate:RootDataEntityValueRestriction - a sh:NodeShape ; - sh:name "RO-Crate Root Data Entity value restriction" ; - sh:description "The Root Data Entity MUST end with `/`" ; - sh:targetNode ro-crate:RootDataEntity ; - sh:property [ - a sh:PropertyShape ; - sh:name "Root Data Entity URI value" ; - sh:description "Check if the Root Data Entity URI ends with `/`" ; - sh:path [ sh:inversePath rdf:type ] ; - sh:minCount 1 ; - sh:message """The Root Data Entity URI MUST end with `/`""" ; - sh:pattern "/$" ; - ] . - -ro-crate:RootDataEntityRequiredProperties - a sh:NodeShape ; - sh:name "RO-Crate Root Data Entity REQUIRED properties" ; - sh:description "The Root Data Entity MUST have a `name`, `description`, `license` and `datePublished`" ; - sh:targetClass ro-crate:RootDataEntity ; - sh:property [ - a sh:PropertyShape ; - sh:name "Root Data Entity: `name` property" ; - sh:description """Check if the Root Data Entity includes a `name` (as specified by schema.org) - to clearly identify the dataset and distinguish it from other datasets.""" ; - sh:minCount 1 ; - sh:nodeKind sh:Literal ; - sh:path schema_org:name; - sh:message "The Root Data Entity MUST have a `name` property (as specified by schema.org)" ; - ] ; - sh:property [ - a sh:PropertyShape ; - sh:name "Root Data Entity: `description` property" ; - sh:description """Check if the Root Data Entity includes a `description` (as specified by schema.org) - to provide a human-readable description of the dataset.""" ; - sh:minCount 1 ; - sh:nodeKind sh:Literal ; - sh:path schema_org:description; - sh:message "The Root Data Entity MUST have a `description` property (as specified by schema.org)" ; - ] ; - sh:property [ - a sh:PropertyShape ; - sh:name "Root Data Entity: `license` property" ; - sh:description """Check if the Root Data Entity includes a `license` property (as specified by schema.org) - to provide information about the license of the dataset.""" ; - sh:path schema_org:license; - sh:minCount 1 ; - sh:or ( - [ sh:class ro-crate:ContextualEntity ] - [ sh:dataType xsd:anyURI ] - [ sh:datatype xsd:string ] - ) ; - sh:message """The Root Data Entity MUST have a `license` property (as specified by schema.org). - SHOULD link to a Contextual Entity in the RO-Crate Metadata File with a name and description. - MAY have a URI (eg for Creative Commons or Open Source licenses). - MAY, if necessary be a textual description of how the RO-Crate may be used.""" ; - ] ; - sh:property [ - a sh:PropertyShape ; - sh:name "Root Data Entity: `datePublished` property" ; - sh:description """Check if the Root Data Entity includes a `datePublished` (as specified by schema.org) - to provide the date when the dataset was published. The datePublished MUST be a valid ISO 8601 date.""" ; - sh:minCount 1 ; - sh:nodeKind sh:Literal ; - sh:path schema_org:datePublished ; - sh:pattern "^([\\+-]?\\d{4})((-?)((0[1-9]|1[0-2])(\\3([12]\\d|0[1-9]|3[01]))?|W([0-4]\\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\\d|[12]\\d{2}|3([0-5]\\d|6[1-6])))([T\\s]((([01]\\d|2[0-3])((:?)?[0-5]\\d)?|24:?00)([\\.,]\\d+(?!:))?)?(\\17[0-5]\\d([\\.,]\\d+)?)?([zZ]|([\\+-])([01]\\d|2[0-3]):?([0-5]\\d)?)?)?)?$" ; - sh:message "The Root Data Entity MUST have a `datePublished` property (as specified by schema.org) with a valid ISO 8601 date" ; - ] . - -ro-crate:RootDataEntityHasPartValueRestriction - a sh:NodeShape ; - sh:name "RO-Crate Root Data Entity: `hasPart` value restriction" ; - sh:description "The Root Data Entity MUST be linked to the declared `File`, `Directory` and other types of instances through the `hasPart` property" ; - sh:targetClass ro-crate:RootDataEntity ; - sh:property [ - a sh:PropertyShape ; - sh:name "RO-Crate Root Data Entity: `hasPart` value restriction" ; - sh:description "Check if the Root Data Entity is linked to the declared `File`, `Directory` and other types of instances through the `hasPart` property" ; - sh:path schema_org:hasPart ; - sh:or ( - [ sh:class ro-crate:File ] - [ sh:class ro-crate:Directory ] - [ sh:class ro-crate:GenericDataEntity ] - ) ; - sh:message """The Root Data Entity MUST be linked to either File or Directory instances, nothing else""" ; - ] . diff --git a/tests/data/rocrate_validator_profiles/ro-crate/must/4_data_entity_metadata.py b/tests/data/rocrate_validator_profiles/ro-crate/must/4_data_entity_metadata.py deleted file mode 100644 index 1abcbcf..0000000 --- a/tests/data/rocrate_validator_profiles/ro-crate/must/4_data_entity_metadata.py +++ /dev/null @@ -1,75 +0,0 @@ -# Copyright (c) 2024-2026 CRS4 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from rocrate_validator.utils import log as logging -from rocrate_validator.models import ValidationContext -from rocrate_validator.requirements.python import (PyFunctionCheck, check, - requirement) - -# set up logging -logger = logging.getLogger(__name__) - - -@requirement(name="Data Entity: REQUIRED resource availability") -class DataEntityRequiredChecker(PyFunctionCheck): - """ - Resources corresponding to local Data Entities MUST be present in the RO-Crate payload - """ - - @check(name="Data Entity: REQUIRED resource availability") - def check_availability(self, context: ValidationContext) -> bool: - """ - Check the presence of the Data Entity in the RO-Crate - """ - # Skip the check in metadata-only mode - if context.settings.metadata_only: - logger.debug("Skipping file descriptor existence check in metadata-only mode") - return True - # Perform the check - result = True - for entity in context.ro_crate.metadata.get_data_entities(exclude_web_data_entities=True): - assert entity.id is not None, "Entity has no @id" - logger.debug("Ensure the presence of the Data Entity '%s' within the RO-Crate", entity.id) - try: - logger.debug("Ensure the presence of the Data Entity '%s' within the RO-Crate", entity.id) - if entity.has_local_identifier(): - logger.debug( - "Ignoring the Data Entity '%s' as it is a local entity with a local identifier. " - "According to the RO-Crate specification, local entities with local identifiers " - "are not required to be included in the RO-Crate payload" - "(see https://github.com/ResearchObject/ro-crate/issues/400#issuecomment-2779152885 and " - "https://github.com/ResearchObject/ro-crate/pull/426 for more details)", - entity.id) - continue - if not entity.has_relative_path(): - logger.debug( - "Ignoring the Data Entity '%s' as it is a local entity with an absolute path. " - "According to the RO-Crate specification, local entities with absolute paths " - "are not required to be included in the RO-Crate payload. " - "It is only recommended that they exist at the time of RO-Crate creation.", - entity.id) - continue - if not entity.is_available(): - context.result.add_issue( - f"The RO-Crate does not include the Data Entity '{entity.id}' as part of its payload", self) - result = False - except Exception as e: - context.result.add_issue( - f"Unable to check the the presence of the Data Entity '{entity.id}' within the RO-Crate", self) - if logger.isEnabledFor(logging.DEBUG): - logger.debug(e, exc_info=True) - result = False - if not result and context.fail_fast: - return result - return result diff --git a/tests/data/rocrate_validator_profiles/ro-crate/must/4_data_entity_metadata.ttl b/tests/data/rocrate_validator_profiles/ro-crate/must/4_data_entity_metadata.ttl deleted file mode 100644 index a172e1f..0000000 --- a/tests/data/rocrate_validator_profiles/ro-crate/must/4_data_entity_metadata.ttl +++ /dev/null @@ -1,216 +0,0 @@ -# Copyright (c) 2024-2026 CRS4 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -@prefix ro: <./> . -@prefix ro-crate: . -@prefix rdf: . -@prefix schema_org: . -@prefix sh: . -@prefix owl: . -@prefix validator: . - -ro-crate:DataEntityRequiredProperties a sh:NodeShape ; - sh:name "Data Entity: REQUIRED properties" ; - sh:description """A Data Entity MUST be a `URI Path` relative to the ROCrate root, - or an absolute URI""" ; - sh:targetClass ro-crate:DataEntity ; - - sh:property [ - sh:name "Data Entity: @id value restriction" ; - sh:description """Check if the Data Entity has an absolute or relative URI as `@id`""" ; - sh:path [sh:inversePath rdf:type ] ; - sh:nodeKind sh:IRI ; - sh:severity sh:Violation ; - sh:message """Data Entities MUST have an absolute or relative URI as @id.""" ; - ] . - -ro-crate:FileDataEntity a sh:NodeShape ; - sh:name "File Data Entity: REQUIRED properties" ; - sh:description """A File Data Entity MUST be a `File`. - `File` is an RO-Crate alias for the schema.org `MediaObject`. - The term `File` here is liberal, and includes "downloadable" resources where `@id` is an absolute URI. - """ ; - sh:target [ - a sh:SPARQLTarget ; - sh:prefixes ro-crate:sparqlPrefixes ; - sh:select """ - SELECT ?this - WHERE { - ?this a schema:MediaObject . - ?metadatafile schema:about ?root . - FILTER(contains(str(?metadatafile), "ro-crate-metadata.json")) - FILTER(!STRSTARTS(STR(?this), CONCAT(STR(?root), "#"))) - } - """ - ] ; - - sh:property [ - sh:name "File Data Entity: REQUIRED type" ; - sh:description """Check if the File Data Entity has `File` as `@type`. - `File` is an RO-Crate alias for the schema.org `MediaObject`. - """ ; - sh:path rdf:type ; - sh:hasValue ro-crate:File ; - sh:severity sh:Violation ; - sh:message """File Data Entities MUST have "File" as a value for @type.""" ; - ] ; - - # Expand data graph with triples from the file data entity - sh:rule [ - a sh:TripleRule ; - sh:subject sh:this ; - sh:predicate rdf:type ; - sh:object ro-crate:DataEntity ; - ] . - - -ro-crate:DirectoryDataEntity a sh:NodeShape ; - sh:name "Directory Data Entity: REQUIRED properties" ; - sh:description """A Directory Data Entity MUST be of @type `Dataset`. - The term `directory` here includes HTTP file listings where `@id` is an absolute URI. - """ ; - sh:target [ - a sh:SPARQLTarget ; - sh:prefixes ro-crate:sparqlPrefixes ; - sh:select """ - SELECT ?this - WHERE { - ?this a schema:Dataset . - ?metadatafile schema:about ?root . - # Exclude all dataset entities that ends with `./#` - FILTER(contains(str(?metadatafile), "ro-crate-metadata.json")) - FILTER(?this != ?root) - FILTER(!STRSTARTS(STR(?this), CONCAT(STR(?root), "#"))) - } - """ - ] ; - - # Decomment for debugging - # sh:property [ - # sh:name "Test Directory" ; - # sh:description """Data Entities representing directories MUST have "Directory" as a value for @type.""" ; - # sh:path rdf:type ; - # sh:hasValue ro-crate:File ; - # sh:severity sh:Violation ; - # ] ; - - # Expand data graph with triples from the file data entity - sh:rule [ - a sh:TripleRule ; - sh:subject sh:this ; - sh:predicate rdf:type ; - sh:object ro-crate:Directory ; - ] ; - - # Expand data graph with triples from the directory data entity - sh:rule [ - a sh:TripleRule ; - sh:subject sh:this ; - sh:predicate rdf:type ; - sh:object ro-crate:DataEntity ; - ] ; - - # Ensure that the directory data entity is a dataset - sh:property [ - sh:name "Directory Data Entity: REQUIRED type" ; - sh:description """Check if the Directory Data Entity has `Dataset` as `@type`.""" ; - sh:path rdf:type ; - sh:hasValue schema_org:Dataset ; - sh:severity sh:Violation ; - ] . - -ro-crate:DataEntityRequiredPropertiesShape a sh:NodeShape ; - sh:name "Data Entity: REQUIRED properties" ; - sh:description """A `DataEntity` MUST be linked, either directly or indirectly, from the Root Data Entity""" ; - sh:targetClass ro-crate:DataEntity ; - sh:property - [ - a sh:PropertyShape ; - sh:path [ sh:inversePath schema_org:hasPart ] ; - sh:node schema_org:Dataset ; - sh:minCount 1 ; - sh:name "Data Entity MUST be directly referenced" ; - sh:description """Check if the Data Entity is linked, either directly or indirectly, to the `Root Data Entity` using the `hasPart` (as defined in `schema.org`) property" """ ; - # sh:message "A Data Entity MUST be directly or indirectly linked to the `Root Data Entity` through the `hasPart` property" ; - ] . - -ro-crate:GenericDataEntityRequiredProperties a sh:NodeShape ; - sh:name "Generic Data Entity: REQUIRED properties" ; - sh:description """A Data Entity other than a File or a Directory MUST be a `DataEntity`""" ; - sh:target [ - a sh:SPARQLTarget ; - sh:prefixes ro-crate:sparqlPrefixes ; - sh:select """ - SELECT ?this - WHERE { - ?root schema:hasPart ?this . - ?metadatafile schema:about ?root . - FILTER(contains(str(?metadatafile), "ro-crate-metadata.json")) - FILTER(?this != ?root) - FILTER(?this != ?metadatafile) - FILTER NOT EXISTS { - ?this a schema:MediaObject . - ?this a schema:Dataset . - } - } - """ - ] ; - - # Expand data graph with triples to mark the matching entities as GenericDataEntity instances - sh:rule [ - a sh:TripleRule ; - sh:subject sh:this ; - sh:predicate rdf:type ; - sh:object ro-crate:GenericDataEntity ; - ] ; - - # Expand data graph with triples to mark the matching entities as DataEntity instances - sh:rule [ - a sh:TripleRule ; - sh:subject sh:this ; - sh:predicate rdf:type ; - sh:object ro-crate:DataEntity ; - ] . - - -# Uncomment for debugging -# ro-crate:TestGenericDataEntity a sh:NodeShape ; -# sh:disabled true ; -# sh:targetClass ro-crate:GenericDataEntity ; -# sh:name "Generic Data Entity: test invalid property"; -# sh:description """Check if the GenericDataEntity has the invalidProperty property""" ; -# sh:property [ -# sh:minCount 1 ; -# sh:maxCount 1 ; -# sh:path ro-crate:invalidProperty ; -# sh:severity sh:Violation ; -# sh:message "Testing the generic data entity"; -# sh:datatype xsd:string ; -# sh:message "Testing for the invalidProperty of the generic data entity"; -# ] . - - -# Uncomment for debugging -# ro:testDirectory a sh:NodeShape ; -# sh:name "Definition of Test Directory" ; -# sh:description """A Test Directory is a digital object that is stored in a file format""" ; -# sh:targetClass ro-crate:Directory ; - -# sh:property [ -# sh:name "Test Directory instance" ; -# sh:description """Check if the Directory DataEntity instance has the fake property ro-crate:foo""" ; -# sh:path rdf:type ; -# sh:hasValue ro-crate:foo ; -# sh:severity sh:Violation ; -# ] . diff --git a/tests/data/rocrate_validator_profiles/ro-crate/must/5_web_data_entity_metadata.ttl b/tests/data/rocrate_validator_profiles/ro-crate/must/5_web_data_entity_metadata.ttl deleted file mode 100644 index c547d60..0000000 --- a/tests/data/rocrate_validator_profiles/ro-crate/must/5_web_data_entity_metadata.ttl +++ /dev/null @@ -1,50 +0,0 @@ -# Copyright (c) 2024-2026 CRS4 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -@prefix ro: <./> . -@prefix ro-crate: . -@prefix rdf: . -@prefix dct: . -@prefix schema_org: . -@prefix sh: . -@prefix owl: . -@prefix xsd: . -@prefix validator: . - - -ro-crate:WebBasedDataEntity a sh:NodeShape, validator:HiddenShape ; - sh:name "Web-based Data Entity: REQUIRED properties" ; - sh:description """A Web-based Data Entity is a `File` identified by an absolute URL""" ; - - sh:target [ - a sh:SPARQLTarget ; - sh:prefixes ro-crate:sparqlPrefixes ; - sh:select """ - SELECT ?this - WHERE { - ?this a schema:MediaObject . - FILTER(?this != ro:ro-crate-metadata.json) - FILTER regex(str(?this), "^(https?|ftps?)://", "i") - } - """ - ] ; - - # Expand data graph with triples which identify the web-based data entity - sh:rule [ - a sh:TripleRule ; - sh:subject sh:this ; - sh:predicate rdf:type ; - sh:object ro-crate:WebDataEntity ; - ] . - diff --git a/tests/data/rocrate_validator_profiles/ro-crate/must/6_contextual_entity.ttl b/tests/data/rocrate_validator_profiles/ro-crate/must/6_contextual_entity.ttl deleted file mode 100644 index 9ecf87a..0000000 --- a/tests/data/rocrate_validator_profiles/ro-crate/must/6_contextual_entity.ttl +++ /dev/null @@ -1,81 +0,0 @@ -# Copyright (c) 2024-2026 CRS4 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -@prefix ro: <./> . -@prefix ro-crate: . -@prefix rdf: . -@prefix schema: . -@prefix sh: . -@prefix xsd: . -@prefix owl: . -@prefix validator: . - - -ro-crate:FindLicenseEntity a sh:NodeShape, validator:HiddenShape ; - sh:name "Identify License Entity" ; - sh:description """Mark a license entity any Data Entity referenced by the `schema:license` property.""" ; - sh:target [ - a sh:SPARQLTarget ; - sh:prefixes ro-crate:sparqlPrefixes ; - sh:select """ - SELECT ?this - WHERE { - ?subject schema:license ?this . - } - """ - ] ; - - # Expand data graph with triples from the file data entity - sh:rule [ - a sh:TripleRule ; - sh:subject sh:this ; - sh:predicate rdf:type ; - sh:object ro-crate:ContextualEntity ; - ] . - - -ro-crate:WebSiteRecommendedProperties a sh:NodeShape ; - sh:name "WebSite RECOMMENDED Properties" ; - sh:description """A `WebSite` MUST be identified by a valid IRI and MUST have a `name` property.""" ; - sh:targetClass schema:WebSite ; - sh:property [ - sh:path [sh:inversePath rdf:type] ; - sh:datType sh:IRI ; - sh:name "WebSite: value restriction of its identifier" ; - sh:description "Check if the WebSite has a valid IRI" ; - sh:message "A WebSite MUST have a valid IRI" ; - ] ; - sh:property [ - sh:path schema:name ; - sh:minCount 1 ; - sh:dataType xsd:string ; - sh:name "WebSite: REQUIRED `name` property" ; - sh:description "Check if the WebSite has a `name` property" ; - sh:message "A WebSite MUST have a `name` property" ; - ] . - - -ro-crate:CreativeWorkAuthorDefinition a sh:NodeShape, validator:HiddenShape ; - sh:name "CreativeWork Author Definition" ; - sh:description """Define the `CreativeWorkAuthor` as the `Person` object of the `schema:author` predicate.""" ; - sh:targetObjectsOf schema:author ; - sh:rule [ - a sh:TripleRule ; - sh:subject sh:this ; - sh:predicate rdf:type ; - sh:object ro-crate:CreativeWorkAuthor ; - sh:condition [ - sh:property [ sh:path rdf:type ; sh:hasValue schema:Person ; sh:minCount 1 ] ; - ] ; - ] . diff --git a/tests/data/rocrate_validator_profiles/ro-crate/ontology.ttl b/tests/data/rocrate_validator_profiles/ro-crate/ontology.ttl deleted file mode 100644 index 9af3a09..0000000 --- a/tests/data/rocrate_validator_profiles/ro-crate/ontology.ttl +++ /dev/null @@ -1,67 +0,0 @@ -# Copyright (c) 2024-2026 CRS4 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -@prefix ro: <./> . -@prefix owl: . -@prefix rdf: . -@prefix xml: . -@prefix xsd: . -@prefix rdfs: . -@prefix schema: . -@prefix rocrate: . -@prefix bioschemas: . -@prefix ro-crate: . -# @base <./.> . - - rdf:type owl:Ontology ; - owl:versionIRI . - -# # ################################################################# -# # # Classes -# # ################################################################# - -# Declare the RootDataEntity class -ro-crate:RootDataEntity rdf:type owl:Class ; - rdfs:subClassOf schema:Dataset ; - rdfs:label "RootDataEntity"@en . - -### http://schema.org/CreativeWork -schema:CreativeWork rdf:type owl:Class ; - rdfs:label "CreativeWork"@en . - -### http://schema.org/MediaObject -schema:MediaObject rdf:type owl:Class ; - owl:equivalentClass ro-crate:File ; - rdfs:label "MediaObject"@en . - - -### http://schema.org/SoftwareSourceCode -schema:SoftwareSourceCode rdf:type owl:Class ; - rdfs:subClassOf schema:CreativeWork . - - -### https://bioschemas.org/ComputationalWorkflow -bioschemas:ComputationalWorkflow rdf:type owl:Class . - - -### https://w3id.org/ro/crate/1.1/DataEntity -ro-crate:DataEntity rdf:type owl:Class ; - rdfs:subClassOf schema:CreativeWork ; - rdfs:label "DataEntity"@en . - - -# # ### https://w3id.org/ro/crate/1.1/Directory -ro-crate:Directory rdf:type owl:Class ; - rdfs:subClassOf schema:Dataset ; - rdfs:label "Directory"@en . diff --git a/tests/data/rocrate_validator_profiles/ro-crate/prefixes.ttl b/tests/data/rocrate_validator_profiles/ro-crate/prefixes.ttl deleted file mode 100644 index 79006d5..0000000 --- a/tests/data/rocrate_validator_profiles/ro-crate/prefixes.ttl +++ /dev/null @@ -1,49 +0,0 @@ -# Copyright (c) 2024-2026 CRS4 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -@prefix ro: <./> . -@prefix sh: . -@prefix xsd: . -@prefix ro-crate: . - -# Define the prefixes used in the SPARQL queries -ro-crate:sparqlPrefixes - sh:declare [ - sh:prefix "schema" ; - sh:namespace "http://schema.org/"^^xsd:anyURI ; - ] ; - sh:declare [ - sh:prefix "bioschemas" ; - sh:namespace "https://bioschemas.org/"^^xsd:anyURI ; - ] ; - sh:declare [ - sh:prefix "bioschemas-cw" ; - sh:namespace "https://bioschemas.org/ComputationalWorkflow#"^^xsd:anyURI ; - ] ; - sh:declare [ - sh:prefix "rocrate" ; - sh:namespace "https://w3id.org/ro/crate/1.1/"^^xsd:anyURI ; - ] ; - sh:declare [ - sh:prefix "wfrun" ; - sh:namespace "https://w3id.org/ro/terms/workflow-run#"^^xsd:anyURI ; - ] ; - sh:declare [ - sh:prefix "codemeta" ; - sh:namespace "https://codemeta.github.io/terms/"^^xsd:anyURI ; - ] ; - sh:declare [ - sh:prefix "ro" ; - sh:namespace "./"^^xsd:anyURI ; - ] . diff --git a/tests/data/rocrate_validator_profiles/ro-crate/profile.ttl b/tests/data/rocrate_validator_profiles/ro-crate/profile.ttl deleted file mode 100644 index 47872a9..0000000 --- a/tests/data/rocrate_validator_profiles/ro-crate/profile.ttl +++ /dev/null @@ -1,74 +0,0 @@ -# Copyright (c) 2024-2026 CRS4 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -@prefix dct: . -@prefix prof: . -@prefix role: . -@prefix rdfs: . - - - - # a Profile; it's identifying URI - a prof:Profile ; - - # common metadata for the Profile - - # the Profile's label - rdfs:label "RO-Crate Metadata Specification 1.1" ; - - # regular metadata, a basic description of the Profile - rdfs:comment """RO-Crate Metadata Specification."""@en ; - - # regular metadata, URI of publisher - dct:publisher ; - - # this profile has a JSON-LD context resource - prof:hasResource [ - a prof:ResourceDescriptor ; - - # it's in JSON-LD format - dct:format ; - - # it conforms to JSON-LD, here referred to by its namespace URI as a Profile - dct:conformsTo ; - - # this profile resource plays the role of "Vocabulary" - # described in this ontology's accompanying Roles vocabulary - prof:hasRole role:Vocabulary ; - - # this profile resource's actual file - prof:hasArtifact ; - ] ; - - # this profile has a human-readable documentation resource - prof:hasResource [ - a prof:ResourceDescriptor ; - - # it's in HTML format - dct:format ; - - # it conforms to HTML, here referred to by its namespace URI as a Profile - dct:conformsTo ; - - # this profile resource plays the role of "Specification" - # described in this ontology's accompanying Roles vocabulary - prof:hasRole role:Specification ; - - # this profile resource's actual file - prof:hasArtifact ; - ] ; - - # a short code to refer to the Profile with when a URI can't be used - prof:hasToken "ro-crate" ; -. diff --git a/tests/data/rocrate_validator_profiles/ro-crate/should/2_root_data_entity_metadata.ttl b/tests/data/rocrate_validator_profiles/ro-crate/should/2_root_data_entity_metadata.ttl deleted file mode 100644 index 7cb53f0..0000000 --- a/tests/data/rocrate_validator_profiles/ro-crate/should/2_root_data_entity_metadata.ttl +++ /dev/null @@ -1,74 +0,0 @@ -# Copyright (c) 2024-2026 CRS4 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -@prefix ro: <./> . -@prefix ro-crate: . -@prefix rdf: . -@prefix schema_org: . -@prefix sh: . -@prefix validator: . - -ro-crate:RootDataEntityDirectRecommendedProperties a sh:NodeShape ; - sh:name "RO-Crate Root Data Entity RECOMMENDED properties" ; - sh:description """The Root Data Entity SHOULD have - the properties `name`, `description` and `license` defined as described - in the RO-Crate specification """; - sh:targetClass ro-crate:RootDataEntity ; - sh:property [ - a sh:PropertyShape ; - sh:name "Root Data Entity: `license` SHOULD link to a Contextual Entity" ; - sh:description """Check if the Root Data Entity includes a `license` property - that links to a Contextual Entity with type `schema_org:CreativeWork` to describe the license.""" ; - sh:nodeKind sh:BlankNodeOrIRI ; - sh:class schema_org:CreativeWork ; - sh:path schema_org:license; - sh:minCount 1 ; - sh:message """The Root Data Entity SHOULD have a link to a Contextual Entity representing the schema_org:license type""" ; - ] ; - sh:property [ - a sh:PropertyShape ; - sh:name "Root Data Entity: `author` property" ; - sh:description """Check if the Root Data Entity includes a `author` property (as specified by schema.org) - to provide information about its author.""" ; - sh:or ( - [ sh:class schema_org:Person ;] - [ sh:class schema_org:Organization ;] - ) ; - sh:path schema_org:author; - sh:minCount 1 ; - sh:message """The Root Data Entity SHOULD have a link to a Contextual Entity representing the `author` of the RO-Crate""" ; - ] ; - sh:property [ - sh:minCount 1 ; - sh:maxCount 1 ; - sh:path schema_org:publisher ; - sh:severity sh:Warning ; - sh:name "Root Data Entity: `publisher` property" ; - sh:description """Check if the Root Data Entity has a `publisher` property of type `Organization`.""" ; - sh:message "The `publisher` property of a `Root Data Entity` SHOULD be an `Organization`"; - sh:nodeKind sh:IRI ; - sh:class schema_org:Organization ; - ] ; - sh:property [ - a sh:PropertyShape ; - sh:name "Root Data Entity: RECOMMENDED `datePublished` property" ; - sh:description """Check if the Root Data Entity includes a `datePublished` (as specified by schema.org) - to provide the date when the dataset was published. The datePublished MUST be a valid ISO 8601 date. - It SHOULD be specified to at least the day level, but MAY include a time component.""" ; - sh:minCount 1 ; - sh:nodeKind sh:Literal ; - sh:path schema_org:datePublished ; - sh:pattern "^([\\+-]?\\d{4})((-?)((0[1-9]|1[0-2])(\\3([12]\\d|0[1-9]|3[01]))|W([0-4]\\d|5[0-2])(-?[1-7])|(00[1-9]|0[1-9]\\d|[12]\\d{2}|3([0-5]\\d|6[1-6])))([T\\s]((([01]\\d|2[0-3])((:?)?[0-5]\\d)?|24:?00)([\\.,]\\d+(?!:))?)?(\\17[0-5]\\d([\\.,]\\d+)?)?([zZ]|([\\+-])([01]\\d|2[0-3]):?([0-5]\\d)?)?)?)$" ; - sh:message "The Root Data Entity MUST have a `datePublished` property (as specified by schema.org) with a valid ISO 8601 date and the precision of at least the day level" ; - ] . diff --git a/tests/data/rocrate_validator_profiles/ro-crate/should/2_root_data_entity_relative_uri.py b/tests/data/rocrate_validator_profiles/ro-crate/should/2_root_data_entity_relative_uri.py deleted file mode 100644 index dd7d8fc..0000000 --- a/tests/data/rocrate_validator_profiles/ro-crate/should/2_root_data_entity_relative_uri.py +++ /dev/null @@ -1,42 +0,0 @@ -# Copyright (c) 2024-2026 CRS4 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from rocrate_validator.utils import log as logging -from rocrate_validator.models import ValidationContext -from rocrate_validator.requirements.python import (PyFunctionCheck, check, - requirement) - -# set up logging -logger = logging.getLogger(__name__) - - -@requirement(name="RO-Crate Root Data Entity RECOMMENDED value") -class RootDataEntityRelativeURI(PyFunctionCheck): - """ - The Root Data Entity SHOULD be denoted by the string / - """ - - @check(name="Root Data Entity: RECOMMENDED value") - def check_relative_uris(self, context: ValidationContext) -> bool: - """Check if the Root Data Entity is denoted by the string `./` in the file descriptor JSON-LD""" - try: - if not context.ro_crate.metadata.get_root_data_entity().id == './': - context.result.add_issue( - 'Root Data Entity URI is not denoted by the string `./`', self) - return False - return True - except Exception as e: - context.result.add_issue( - f'Error checking Root Data Entity URI: {str(e)}', self) - return False diff --git a/tests/data/rocrate_validator_profiles/ro-crate/should/4_data_entity_existence.py b/tests/data/rocrate_validator_profiles/ro-crate/should/4_data_entity_existence.py deleted file mode 100644 index f5742ac..0000000 --- a/tests/data/rocrate_validator_profiles/ro-crate/should/4_data_entity_existence.py +++ /dev/null @@ -1,58 +0,0 @@ -# Copyright (c) 2024-2026 CRS4 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from rocrate_validator.utils import log as logging -from rocrate_validator.models import ValidationContext -from rocrate_validator.requirements.python import (PyFunctionCheck, check, - requirement) - -# set up logging -logger = logging.getLogger(__name__) - - -@requirement(name="Data Entity: RECOMMENDED resource availability") -class DataEntityRecommendedChecker(PyFunctionCheck): - """ - Data Entities with absolute URI paths SHOULD be available - at the time of RO-Crate creation - """ - - @check(name="Data Entity: RECOMMENDED resource availability") - def check_availability(self, context: ValidationContext) -> bool: - """ - Check the availability of the Data Entity with absolute URI paths - are available at the time of RO-Crate creation - """ - # Skip the check in metadata-only mode - if context.settings.metadata_only: - logger.debug("Skipping file descriptor existence check in metadata-only mode") - return True - # Perform the check - result = True - for entity in [ - _ for _ in context.ro_crate.metadata.get_data_entities(exclude_web_data_entities=True) - if _.has_absolute_path()]: - assert entity.id is not None, "Entity has no @id" - try: - if not entity.is_available(): - context.result.add_issue( - f'Data Entity {entity.id} is not available', self) - result = False - except Exception as e: - context.result.add_issue( - f'Web-based Data Entity {entity.id} is not available: {e}', self) - result = False - if not result and context.fail_fast: - return result - return result diff --git a/tests/data/rocrate_validator_profiles/ro-crate/should/4_data_entity_metadata.ttl b/tests/data/rocrate_validator_profiles/ro-crate/should/4_data_entity_metadata.ttl deleted file mode 100644 index 188e3a8..0000000 --- a/tests/data/rocrate_validator_profiles/ro-crate/should/4_data_entity_metadata.ttl +++ /dev/null @@ -1,69 +0,0 @@ -# Copyright (c) 2024-2026 CRS4 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -@prefix ro: <./> . -@prefix ro-crate: . -@prefix rdf: . -@prefix schema_org: . -@prefix sh: . -@prefix xsd: . - -ro-crate:FileRecommendedProperties a sh:NodeShape ; - sh:targetClass ro-crate:File ; - sh:name "File Data Entity: RECOMMENDED properties"; - sh:description """A `File` Data Entity SHOULD have detailed descriptions encodings through the `encodingFormat` property""" ; - sh:property [ - sh:minCount 1 ; - sh:maxCount 2 ; - sh:path schema_org:encodingFormat ; - sh:severity sh:Warning ; - sh:name "File Data Entity: RECOMMENDED `encodingFormat` property" ; - sh:description """Check if the File Data Entity has a detailed description of encodings through the `encodingFormat` property. - The `encodingFormat` property SHOULD be a PRONOM identifier (e.g., application/pdf) or, - to add more detail, SHOULD be linked using a `PRONOM` to a `Contextual Entity` of type `WebSite` - (see [Adding detailed descriptions of encodings](https://www.researchobject.org/ro-crate/1.1/data-entities.html#adding-detailed-descriptions-of-encodings)). - """ ; - sh:message "Missing or invalid `encodingFormat` linked to the `File Data Entity`"; - sh:or ( - [ - sh:datatype xsd:string ; - sh:pattern "^(\\w*)\\/(\\w[\\w\\.-]*)(?:\\+(\\w[\\w\\.-]*))?(?:;(\\w+=[^;]+))*$" ; - sh:name "File Data Entity: RECOMMENDED `PRONOM` for the `encodingFormat` property" ; - sh:description """Check if the File Data Entity is linked to its `encodingFormat` through a PRONOM identifier - (e.g., application/pdf, application/text, image/svg+xml, image/svg;q=0.9,/;q=0.8,image/svg+xml;q=0.9,/;q=0.8, application/vnd.uplanet.listcmd-wbxml;charset=utf-8). - """ ; - sh:message "The `encodingFormat` SHOULD be linked using a PRONOM identifier (e.g., application/pdf)."; - ] - [ - sh:nodeKind sh:IRI ; - sh:class schema_org:WebSite ; - sh:name "File Data Entity: RECOMMENDED `Contextual Entity` linked to the `encodingFormat` property"; - sh:description "Check if the File Data Entity `encodingFormat` is linked to a `Contextual Entity of type `WebSite`." ; - sh:message "The `encodingFormat` SHOULD be linked to a `Contextual Entity` of type `Web Site`." ; - ] - ) - ] . - -ro-crate:DirectoryDataEntityRequiredValueRestriction a sh:NodeShape ; - sh:name "Directory Data Entity: RECOMMENDED value restriction" ; - sh:description """A Directory Data Entity SHOULD end with `/`""" ; - sh:targetNode ro-crate:Directory ; - sh:property [ - a sh:PropertyShape ; - sh:name "Directory Data Entity: RECOMMENDED value restriction" ; - sh:description """Check if the Directory Data Entity ends with `/`""" ; - sh:path [ sh:inversePath rdf:type ] ; - sh:message """Every Data Entity Directory URI SHOULD end with `/`""" ; - sh:pattern "/$" ; - ] . diff --git a/tests/data/rocrate_validator_profiles/ro-crate/should/5_web_data_entity_metadata.py b/tests/data/rocrate_validator_profiles/ro-crate/should/5_web_data_entity_metadata.py deleted file mode 100644 index 13ef914..0000000 --- a/tests/data/rocrate_validator_profiles/ro-crate/should/5_web_data_entity_metadata.py +++ /dev/null @@ -1,73 +0,0 @@ -# Copyright (c) 2024-2026 CRS4 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from rocrate_validator.utils import log as logging -from rocrate_validator.models import ValidationContext -from rocrate_validator.requirements.python import (PyFunctionCheck, check, - requirement) - -# set up logging -logger = logging.getLogger(__name__) - - -@requirement(name="Web-based Data Entity: RECOMMENDED resource availability") -class WebDataEntityRecommendedChecker(PyFunctionCheck): - """ - Web-based Data Entity instances SHOULD be available - at the URIs specified in the `@id` property of the Web-based Data Entity. - """ - - @check(name="Web-based Data Entity: resource availability") - def check_availability(self, context: ValidationContext) -> bool: - """ - Check if the Web-based Data Entity is directly downloadable - by a simple retrieval (e.g. HTTP GET) permitting redirection and HTTP/HTTPS URIs - """ - result = True - for entity in context.ro_crate.metadata.get_web_data_entities(): - assert entity.id is not None, "Entity has no @id" - try: - if not entity.is_available(): - context.result.add_issue( - f'Web-based Data Entity {entity.id} is not available', self) - result = False - except Exception as e: - context.result.add_issue( - f'Web-based Data Entity {entity.id} is not available: {e}', self) - result = False - if not result and context.fail_fast: - return result - return result - - @check(name="Web-based Data Entity: `contentSize` property") - def check_content_size(self, context: ValidationContext) -> bool: - """ - Check if the Web-based Data Entity has a `contentSize` property - and if it is set to actual size of the downloadable content - """ - result = True - for entity in context.ro_crate.metadata.get_web_data_entities(): - assert entity.id is not None, "Entity has no @id" - if entity.is_available(): - content_size = entity.get_property("contentSize") - if content_size and int(content_size) != context.ro_crate.get_external_file_size(entity.id): - context.result.add_issue( - f'The property contentSize={content_size} of the Web-based Data Entity ' - f'{entity.id} does not match the actual size of ' - f'the downloadable content, i.e., {entity.content_size} (bytes)', self, - violatingEntity=entity.id, violatingProperty='contentSize', violatingPropertyValue=content_size) - result = False - if not result and context.fail_fast: - return result - return result diff --git a/tests/data/rocrate_validator_profiles/ro-crate/should/5_web_data_entity_metadata.ttl b/tests/data/rocrate_validator_profiles/ro-crate/should/5_web_data_entity_metadata.ttl deleted file mode 100644 index e4f3cc5..0000000 --- a/tests/data/rocrate_validator_profiles/ro-crate/should/5_web_data_entity_metadata.ttl +++ /dev/null @@ -1,63 +0,0 @@ -# Copyright (c) 2024-2026 CRS4 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -@prefix ro: <./> . -@prefix ro-crate: . -@prefix rdf: . -@prefix dct: . -@prefix schema_org: . -@prefix sh: . -@prefix owl: . -@prefix xsd: . -@prefix validator: . - - -ro-crate:WebBasedDataEntityRequiredValueRestriction a sh:NodeShape ; - sh:name "Web-based Data Entity: RECOMMENDED properties" ; - sh:description """A Web-based Data Entity MUST be identified by an absolute URL and - SHOULD have a `contentSize` and `sdDatePublished` property""" ; - sh:targetClass ro-crate:WebDataEntity ; - # Check if the Web-based Data Entity has a contentSize property - sh:property [ - a sh:PropertyShape ; - sh:minCount 1 ; - sh:name "Web-based Data Entity: `contentSize` property" ; - sh:description """Check if the Web-based Data Entity has a `contentSize` property""" ; - sh:path schema_org:contentSize ; - sh:datatype xsd:string ; - sh:severity sh:Warning ; - sh:message """Web-based Data Entities SHOULD have a `contentSize` property""" ; - sh:sparql [ - sh:message "If the value is a string it must be a string representing an integer." ; - sh:select """ - SELECT ?this ?value - WHERE { - ?this schema:contentSize ?value . - FILTER NOT EXISTS { - FILTER (xsd:integer(?value) = ?value) - } - } - """ ; - ] ; - ] ; - # Check if the Web-based Data Entity has a sdDatePublished property - sh:property [ - a sh:PropertyShape ; - sh:minCount 1 ; - sh:name "Web-based Data Entity: `sdDatePublished` property" ; - sh:description """Check if the Web-based Data Entity has a `sdDatePublished` property""" ; - sh:path schema_org:sdDatePublished ; - sh:pattern "^([\\+-]?\\d{4})((-?)((0[1-9]|1[0-2])(\\3([12]\\d|0[1-9]|3[01]))|W([0-4]\\d|5[0-2])(-?[1-7])|(00[1-9]|0[1-9]\\d|[12]\\d{2}|3([0-5]\\d|6[1-6])))([T\\s]((([01]\\d|2[0-3])((:?)?[0-5]\\d)?|24:?00)([\\.,]\\d+(?!:))?)?(\\17[0-5]\\d([\\.,]\\d+)?)?([zZ]|([\\+-])([01]\\d|2[0-3]):?([0-5]\\d)?)?)?)$" ; - sh:message """Web-based Data Entities SHOULD have a `sdDatePublished` property to indicate when the absolute URL was accessed""" ; - ] . diff --git a/tests/data/rocrate_validator_profiles/ro-crate/should/6_contextual_entity_metadata.ttl b/tests/data/rocrate_validator_profiles/ro-crate/should/6_contextual_entity_metadata.ttl deleted file mode 100644 index a90dbc2..0000000 --- a/tests/data/rocrate_validator_profiles/ro-crate/should/6_contextual_entity_metadata.ttl +++ /dev/null @@ -1,75 +0,0 @@ -# Copyright (c) 2024-2026 CRS4 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -@prefix ro: <./> . -@prefix ro-crate: . -@prefix schema: . -@prefix sh: . -@prefix xsd: . - -ro-crate:CreativeWorkAuthorMinimumRecommendedProperties a sh:NodeShape ; - sh:name "CreativeWork Author: minimum RECOMMENDED properties" ; - sh:description """The minimum recommended properties for a `CreativeWork Author` are `name` and `affiliation`.""" ; - sh:targetClass ro-crate:CreativeWorkAuthor ; - sh:property [ - sh:path schema:name ; - sh:minCount 1 ; - sh:dataType xsd:string ; - sh:name "CreativeWork Author: RECOMMENDED name property" ; - sh:description "Check if the author has a name." ; - sh:message "The author SHOULD have a name." ; - ] ; - sh:property [ - sh:path schema:affiliation ; - sh:minCount 1 ; - sh:or ( - [ sh:dataType xsd:string ; ] - [ sh:class schema:Organization ;] - ) ; - sh:severity sh:Warning ; - sh:name "CreativeWork Author: RECOMMENDED affiliation property" ; - sh:description "Check if the author has an organizational affiliation." ; - sh:message "The author SHOULD have an organizational affiliation." ; - ] ; - sh:property [ - sh:path schema:affiliation ; - sh:minCount 1 ; - sh:class schema:Organization ; - sh:severity sh:Warning ; - sh:name "CreativeWork Author: RECOMMENDED Contextual Entity linked for the organizational `affiliation` property" ; - sh:description "Check if the author has a Contextual Entity for the organizational `affiliation` property." ; - sh:message "The author SHOULD have a Contextual Entity which specifies the organizational `affiliation`." ; - ] . - - -ro-crate:OrganizationRecommendedProperties a sh:NodeShape ; - sh:name "Organization: RECOMMENDED properties" ; - sh:description """The recommended properties for an `Organization` are `name` and `url`.""" ; - sh:targetClass schema:Organization ; - sh:property [ - sh:path schema:name ; - sh:minCount 1 ; - sh:dataType xsd:string ; - sh:name "Organization: RECOMMENDED name property" ; - sh:description "Check if the `organization` has a name." ; - sh:message "The organization SHOULD have a name." ; - ] ; - sh:property [ - sh:path schema:url ; - sh:minCount 1 ; - sh:dataType xsd:anyURI ; - sh:name "Organization: RECOMMENDED url property" ; - sh:description "Check if the `organization` has a URL." ; - sh:message "The organization SHOULD have a URL." ; - ] .