Skip to content

Commit 46ae808

Browse files
authored
Merge branch 'develop' into fix/issue-120
2 parents 4f0ed43 + 635c86b commit 46ae808

41 files changed

Lines changed: 4896 additions & 41 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CITATION.cff

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ authors:
1919
- family-names: Bauer
2020
given-names: Daniel
2121
orcid: https://orcid.org/0000-0001-9447-460X
22+
- family-names: "Wetzels"
23+
given-names: "Florian"
24+
orcid: https://orcid.org/0000-0002-5526-7138
25+
- family-names: "Weil"
26+
given-names: "Heinrich Lukas"
27+
orcid: https://orcid.org/0000-0003-1945-6342
2228
repository-code: "https://github.com/crs4/rocrate-validator"
2329
url: "https://github.com/crs4/rocrate-validator"
2430
keywords:

rocrate_validator/cli/commands/validate.py

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
from rich.rule import Rule
2626

2727
from rocrate_validator.utils import log as logging
28-
from rocrate_validator import services
28+
from rocrate_validator import constants, services
2929
from rocrate_validator.cli.commands.errors import handle_error
3030
from rocrate_validator.cli.main import cli
3131
from rocrate_validator.cli.ui.text.validate import ValidationCommandView
@@ -205,6 +205,29 @@ def validate_uri(ctx, param, value):
205205
show_default=True,
206206
help="Width of the output line",
207207
)
208+
@click.option(
209+
'--cache-max-age',
210+
type=click.INT,
211+
default=constants.DEFAULT_HTTP_CACHE_MAX_AGE,
212+
show_default=True,
213+
help="Maximum age of the HTTP cache in seconds ([bold green]-1[/bold green] for no expiration)",
214+
)
215+
@click.option(
216+
'--cache-path',
217+
type=click.Path(),
218+
default=None,
219+
show_default=True,
220+
help="Path to the HTTP cache directory",
221+
)
222+
@click.option(
223+
'-nc',
224+
'--no-cache',
225+
is_flag=True,
226+
help="Disable the HTTP cache",
227+
default=False,
228+
show_default=True,
229+
hidden=True
230+
)
208231
@click.pass_context
209232
def validate(ctx,
210233
profiles_path: Path = DEFAULT_PROFILES_PATH,
@@ -223,7 +246,10 @@ def validate(ctx,
223246
verbose: bool = False,
224247
output_format: str = "text",
225248
output_file: Optional[Path] = None,
226-
output_line_width: Optional[int] = None):
249+
output_line_width: Optional[int] = None,
250+
cache_max_age: int = constants.DEFAULT_HTTP_CACHE_MAX_AGE,
251+
cache_path: Optional[Path] = None,
252+
no_cache: bool = False):
227253
"""
228254
[magenta]rocrate-validator:[/magenta] Validate a RO-Crate against a profile
229255
"""
@@ -247,6 +273,11 @@ def validate(ctx,
247273
logger.debug("fail_fast: %s", fail_fast)
248274
logger.debug("no fail fast: %s", not fail_fast)
249275

276+
# Cache settings
277+
logger.debug("cache_max_age: %s", cache_max_age)
278+
logger.debug("cache_path: %s", os.path.abspath(cache_path) if cache_path else None)
279+
logger.debug("no_cache: %s", no_cache)
280+
250281
if rocrate_uri:
251282
logger.debug("rocrate_path: %s", os.path.abspath(rocrate_uri))
252283

@@ -282,7 +313,9 @@ def validate(ctx,
282313
"rocrate_relative_root_path": relative_root_path,
283314
"abort_on_first": fail_fast,
284315
"skip_checks": skip_checks_list,
285-
"metadata_only": metadata_only
316+
"metadata_only": metadata_only,
317+
"cache_max_age": cache_max_age if not no_cache else -1,
318+
"cache_path": cache_path
286319
}
287320

288321
# Print the application header

rocrate_validator/constants.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,5 +87,5 @@
8787
JSON_OUTPUT_FORMAT_VERSION = "0.2"
8888

8989
# Http Cache Settings
90-
DEFAULT_HTTP_CACHE_TIMEOUT = 60
90+
DEFAULT_HTTP_CACHE_MAX_AGE = 300 # in seconds
9191
DEFAULT_HTTP_CACHE_PATH_PREFIX = '/tmp/rocrate_validator_cache'

rocrate_validator/models.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@
3131
import enum_tools
3232
from rdflib import RDF, RDFS, Graph, Namespace, URIRef
3333

34-
from rocrate_validator.utils import log as logging
3534
from rocrate_validator import __version__
36-
from rocrate_validator.constants import (DEFAULT_ONTOLOGY_FILE,
35+
from rocrate_validator.constants import (DEFAULT_HTTP_CACHE_MAX_AGE,
36+
DEFAULT_ONTOLOGY_FILE,
3737
DEFAULT_PROFILE_IDENTIFIER,
3838
DEFAULT_PROFILE_README_FILE,
3939
IGNORED_PROFILE_DIRECTORIES,
@@ -48,11 +48,13 @@
4848
ROCrateMetadataNotFoundError)
4949
from rocrate_validator.events import Event, EventType, Publisher, Subscriber
5050
from rocrate_validator.rocrate import ROCrate
51-
from rocrate_validator.utils.collections import (MapIndex)
51+
from rocrate_validator.utils import log as logging
52+
from rocrate_validator.utils.collections import MapIndex, MultiIndexMap
53+
from rocrate_validator.utils.http import HttpRequester
5254
from rocrate_validator.utils.paths import get_profiles_path
53-
from rocrate_validator.utils.python_helpers import get_requirement_name_from_file
55+
from rocrate_validator.utils.python_helpers import \
56+
get_requirement_name_from_file
5457
from rocrate_validator.utils.uri import URI
55-
from rocrate_validator.utils.collections import MultiIndexMap
5658

5759
# set the default profiles path
5860
DEFAULT_PROFILES_PATH = get_profiles_path()
@@ -2388,11 +2390,19 @@ class ValidationSettings:
23882390
metadata_dict: dict = None
23892391
#: Verbose output
23902392
verbose: bool = False
2393+
#: Cache max age in seconds
2394+
cache_max_age: Optional[int] = DEFAULT_HTTP_CACHE_MAX_AGE
2395+
#: Cache path
2396+
cache_path: Optional[Path] = None
23912397

23922398
def __post_init__(self):
23932399
# if requirement_severity is a str, convert to Severity
23942400
if isinstance(self.requirement_severity, str):
23952401
self.requirement_severity = Severity[self.requirement_severity]
2402+
# initialize the HTTP cache
2403+
HttpRequester.initialize_cache(cache_path=self.cache_path, cache_max_age=self.cache_max_age)
2404+
logger.debug("HTTP cache initialized at %s with max age %s seconds",
2405+
self.cache_path, self.cache_max_age)
23962406

23972407
def to_dict(self):
23982408
"""
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
# Copyright (c) 2026 DataPLANT
2+
# Copyright (c) 2026 The University of Manchester
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
@prefix ro: <./> .
17+
@prefix ro-crate: <https://github.com/crs4/rocrate-validator/profiles/ro-crate/> .
18+
@prefix isa-ro-crate: <https://github.com/crs4/rocrate-validator/profiles/isa-ro-crate/> .
19+
@prefix bioschemas: <https://bioschemas.org/> .
20+
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
21+
@prefix schema: <http://schema.org/> .
22+
@prefix sh: <http://www.w3.org/ns/shacl#> .
23+
@prefix validator: <https://github.com/crs4/rocrate-validator/> .
24+
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
25+
26+
isa-ro-crate:RootDataEntityMustBeInvestigation a sh:NodeShape ;
27+
sh:name "Root Data Entity must be Investigation" ;
28+
sh:description "The root data entity must follow the investigation profile" ;
29+
sh:targetClass ro-crate:RootDataEntity ;
30+
sh:property [
31+
a sh:PropertyShape ;
32+
sh:path schema:additionalType ;
33+
sh:minCount 1 ;
34+
sh:hasValue "Investigation";
35+
sh:description "Check if the root data entity is specified as an investigation through additionalType" ;
36+
sh:message "The root data entity must have additionalType of `Investigation`" ;
37+
sh:severity sh:Violation ;
38+
] ;
39+
sh:property [
40+
a sh:PropertyShape ;
41+
sh:path schema:identifier ;
42+
sh:minCount 1 ;
43+
sh:datatype xsd:string ;
44+
sh:not [
45+
sh:hasValue ""
46+
] ;
47+
sh:description "Check if the root data entity (investigation) has an identifier" ;
48+
sh:message "The root data entity must have a non-empty identifier" ;
49+
sh:severity sh:Violation ;
50+
] ;
51+
# sh:property [
52+
# a sh:PropertyShape ;
53+
# sh:path schema:name ;
54+
# sh:minCount 1 ;
55+
# sh:not [
56+
# sh:hasValue ""
57+
# ] ;
58+
# sh:description "Check if the root data entity (investigation) has a name" ;
59+
# sh:message "The root data entity must have a non-empty name" ;
60+
# sh:severity sh:Violation ;
61+
# ] ;
62+
# sh:property [
63+
# a sh:PropertyShape ;
64+
# sh:path schema:description ;
65+
# sh:minCount 1 ;
66+
# sh:not [
67+
# sh:hasValue ""
68+
# ] ;
69+
# sh:description "Check if the root data entity (investigation) has a description" ;
70+
# sh:message "The root data entity must have a non-empty description" ;
71+
# sh:severity sh:Violation ;
72+
# ] ;
73+
# sh:property [
74+
# a sh:PropertyShape ;
75+
# sh:path schema:license ;
76+
# sh:minCount 1 ;
77+
# sh:description "Check if the root data entity (investigation) specifies a license" ;
78+
# sh:message "The root data entity must specify a license" ;
79+
# sh:severity sh:Violation ;
80+
# ] ;
81+
# sh:property [
82+
# a sh:PropertyShape ;
83+
# sh:path schema:datePublished ;
84+
# sh:minCount 1 ;
85+
# sh:nodeKind sh:Literal ;
86+
# 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)?)?)?)$" ;
87+
# sh:description "Check if the root data entity (investigation) has a datePublished" ;
88+
# sh:message "The root data entity must have a datePublished" ;
89+
# sh:severity sh:Violation ;
90+
# ] ;
91+
.
92+
93+
isa-ro-crate:InvestigationShouldHaveCreator a sh:NodeShape ;
94+
sh:name "Investigation SHOULD have creator" ;
95+
sh:description "An Investigation SHOULD have a creator" ;
96+
sh:targetClass ro-crate:RootDataEntity ;
97+
sh:property [
98+
a sh:PropertyShape ;
99+
sh:path schema:creator ;
100+
sh:minCount 1 ;
101+
sh:description "Check that investigation does have at least one creator" ;
102+
sh:message "Investigation entity SHOULD have a creator" ;
103+
sh:severity sh:Warning ;
104+
] ;
105+
sh:property [
106+
a sh:PropertyShape ;
107+
sh:path schema:creator ;
108+
sh:class schema:Person ;
109+
sh:description "Check that if investigation does have at least one creator, it MUST be of type Person" ;
110+
sh:message "Investigation creator MUST be of type Person" ;
111+
sh:severity sh:Violation ;
112+
]
113+
.
114+
115+
isa-ro-crate:InvestigationShouldHaveDateCreated a sh:NodeShape ;
116+
sh:name "Investigation SHOULD have dateCreated" ;
117+
sh:description "An Investigation SHOULD have a dateCreated" ;
118+
sh:targetClass ro-crate:RootDataEntity ;
119+
sh:property [
120+
a sh:PropertyShape ;
121+
sh:path schema:dateCreated ;
122+
sh:minCount 1 ;
123+
sh:description "Check that investigation does have at least one dateCreated" ;
124+
sh:message "Investigation entity SHOULD have a dateCreated" ;
125+
sh:severity sh:Warning ;
126+
] ;
127+
sh:property [
128+
a sh:PropertyShape ;
129+
sh:path schema:dateCreated ;
130+
sh:nodeKind sh:Literal ;
131+
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)?)?)?)$" ;
132+
sh:description "Check that if investigation does have at least one dateCreated, it MUST be a valid ISO 8601 date." ;
133+
sh:message "Investigation dateCreated MUST be a valid ISO 8601 date" ;
134+
sh:severity sh:Violation ;
135+
]
136+
.
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# Copyright (c) 2026 DataPLANT
2+
# Copyright (c) 2026 The University of Manchester
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
@prefix ro: <./> .
17+
@prefix ro-crate: <https://github.com/crs4/rocrate-validator/profiles/ro-crate/> .
18+
@prefix isa-ro-crate: <https://github.com/crs4/rocrate-validator/profiles/isa-ro-crate/> .
19+
@prefix bioschemas: <https://bioschemas.org/> .
20+
@prefix bioschemas-prop: <https://bioschemas.org/properties/> .
21+
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
22+
@prefix schema: <http://schema.org/> .
23+
@prefix sh: <http://www.w3.org/ns/shacl#> .
24+
@prefix validator: <https://github.com/crs4/rocrate-validator/> .
25+
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
26+
27+
28+
isa-ro-crate:DefinedTermMustHaveName a sh:NodeShape ;
29+
sh:name "DefinedTerm MUST have a name" ;
30+
sh:description "A DefinedTerm MUST have a name" ;
31+
sh:targetClass schema:DefinedTerm ;
32+
sh:property [
33+
a sh:PropertyShape ;
34+
sh:path schema:name ;
35+
sh:datatype xsd:string ;
36+
sh:minCount 1 ;
37+
sh:maxCount 1 ;
38+
sh:not [
39+
sh:hasValue ""
40+
] ;
41+
sh:description "Check that DefinedTerm does have non-empty name and it's a string" ;
42+
sh:message "DefinedTerm entity MUST have a non-empty name of type string" ;
43+
sh:severity sh:Violation ;
44+
]
45+
.
46+
47+
isa-ro-crate:DefinedTermShouldHaveTermCodeOfCorrectType a sh:NodeShape ;
48+
sh:name "DefinedTerm SHOULD have termCode of correct type" ;
49+
sh:description "A DefinedTerm SHOULD have at least one termCode of correct type" ;
50+
sh:targetClass schema:DefinedTerm ;
51+
sh:property [
52+
a sh:PropertyShape ;
53+
sh:path schema:termCode ;
54+
sh:minCount 1 ;
55+
sh:not [
56+
sh:hasValue ""
57+
] ;
58+
sh:description "Check that DefinedTerm does have at least one termCode" ;
59+
sh:message "DefinedTerm entity SHOULD have at least one termCode" ;
60+
sh:severity sh:Warning ;
61+
] ;
62+
sh:property [
63+
a sh:PropertyShape ;
64+
sh:path schema:termCode ;
65+
sh:datatype xsd:string ;
66+
sh:description "The termCode of a DefinedTerm MUST be of type string" ;
67+
sh:message "DefinedTerm termCode MUST be of type string" ;
68+
sh:severity sh:Violation ;
69+
]
70+
.

0 commit comments

Comments
 (0)