Skip to content

Commit 4de23bc

Browse files
anitacaronmatentzn
andauthored
Update Automated checks pages (#2656)
* add fp_020.py script to makefile to be updated automatically from OBO-Dashboard * add fp_20 to be updated automatically; fix bug to have ID * update checks page auto --------- Co-authored-by: Nico Matentzoglu <nicolas.matentzoglu@gmail.com>
1 parent fa4b255 commit 4de23bc

File tree

12 files changed

+305
-203
lines changed

12 files changed

+305
-203
lines changed

principles/Makefile

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Collect the list of query files for report.
22
SCRIPTS := build/fp_001.py build/fp_002.py build/fp_003.py build/fp_004.py \
33
build/fp_005.py build/fp_006.py build/fp_007.py build/fp_008.py build/fp_009.py \
4-
build/fp_011.py build/fp_012.py build/fp_016.py
4+
build/fp_011.py build/fp_012.py build/fp_016.py build/fp_020.py
55
DOCS := $(foreach x, $(SCRIPTS), checks/$(notdir $(basename $(x))).md)
66

77
# FP ID to the principle name
@@ -17,6 +17,7 @@ DOCS := $(foreach x, $(SCRIPTS), checks/$(notdir $(basename $(x))).md)
1717
11 = 'Locus of Authority'
1818
12 = 'Naming Conventions'
1919
16 = Maintenance
20+
20 = Responsiveness
2021

2122
# Generate all check docs
2223
all: clean
@@ -33,7 +34,7 @@ build/fp_%.py: | build
3334

3435
# Build the MD page from the check script
3536
checks/fp_%.md: build/fp_%.py | $(SCRIPTS) checks
36-
$(eval ID := $(subst 0,,$(subst fp_,,$(notdir $(basename $@)))))
37+
$(eval ID := $(shell echo $(subst fp_,,$(notdir $(basename $@))) | sed 's/^0*//'))
3738
if [ $(ID) == 1 ]; then export TITLE=$(1); \
3839
elif [ $(ID) == 2 ]; then export TITLE=$(2); \
3940
elif [ $(ID) == 3 ]; then export TITLE=$(3); \
@@ -46,6 +47,7 @@ checks/fp_%.md: build/fp_%.py | $(SCRIPTS) checks
4647
elif [ $(ID) == 11 ]; then export TITLE=$(11); \
4748
elif [ $(ID) == 12 ]; then export TITLE=$(12); \
4849
elif [ $(ID) == 16 ]; then export TITLE=$(16); \
50+
elif [ $(ID) == 20 ]; then export TITLE=$(20); \
4951
fi; \
5052
echo "---\nlayout: check\nid: $(ID)\ntitle: $$TITLE Automated Check\n---\n" > $@
5153
tail -n+3 $< \

principles/checks/fp_001.md

Lines changed: 21 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
layout: check
3-
id: fp_001
3+
id: 1
44
title: Open Automated Check
55
---
66

@@ -39,11 +39,11 @@ See [Open Implementation](http://obofoundry.org/principles/fp-001-open.html#impl
3939
The registry data entry is validated with JSON schema using the [license schema](https://raw.githubusercontent.com/OBOFoundry/OBOFoundry.github.io/master/util/schema/license.json). The license schema ensures that a license entry is present and that the entry has a `url` and `label`. The license schema also checks that the license is one of the CC0 or CC-BY licenses. OWL API is then used to check the ontology as an `OWLOntology` object. Annotations on the ontology are retrieved and the `dcterms:license` property is found. The python script ensures that the correct `dcterms:license` property is used. The script compares this license to the registry license to ensure that they are the same.
4040

4141
```python
42-
import jsonschema
4342
import dash_utils
43+
import jsonschema
4444

4545

46-
def is_open(ontology, data):
46+
def is_open(ontology, data, schema):
4747
"""Check FP 1 - Open.
4848
4949
This method checks the following:
@@ -63,7 +63,7 @@ def is_open(ontology, data):
6363
ERROR, WARN, INFO, or PASS string with optional message.
6464
"""
6565

66-
v = OpenValidator(ontology, data)
66+
v = OpenValidator(ontology, data, schema)
6767

6868
loadable = False
6969
if ontology:
@@ -91,7 +91,7 @@ class OpenValidator():
9191
license (None if missing)
9292
"""
9393

94-
def __init__(self, ontology, data):
94+
def __init__(self, ontology, data, schema):
9595
"""Instantiate an OpenValidator.
9696
9797
Args:
@@ -105,7 +105,7 @@ class OpenValidator():
105105

106106
self.is_open = None
107107
if self.registry_license is not None:
108-
self.is_open = check_registry_license(data)
108+
self.is_open = check_registry_license(data, schema)
109109

110110
self.ontology_license = None
111111
self.correct_property = None
@@ -134,18 +134,18 @@ class OpenValidator():
134134
annotations = ontology.getAnnotations()
135135
license = dash_utils.get_ontology_annotation_value(annotations,
136136
license_prop)
137-
bad_license = dash_utils.get_ontology_annotation_value(
138-
annotations, bad_license_prop)
137+
138+
bad_licenses = list(filter(None, [dash_utils.get_ontology_annotation_value(annotations, prop) for prop in bad_license_props]))
139139

140140
if license:
141141
self.ontology_license = license
142142
self.correct_property = True
143-
elif bad_license:
144-
self.ontology_license = bad_license
143+
elif len(bad_licenses) > 0:
144+
self.ontology_license = bad_licenses[0]
145145
self.correct_property = False
146146

147147

148-
def big_is_open(file, data):
148+
def big_is_open(file, data, schema):
149149
"""Check FP 1 - Open.
150150
151151
This method checks the following:
@@ -165,7 +165,7 @@ def big_is_open(file, data):
165165
ERROR, WARN, INFO, or PASS string with optional message.
166166
"""
167167

168-
v = BigOpenValidator(file, data)
168+
v = BigOpenValidator(file, data, schema)
169169
return process_results(v.registry_license,
170170
v.ontology_license,
171171
v.is_open,
@@ -188,7 +188,7 @@ class BigOpenValidator():
188188
license (None if missing)
189189
"""
190190

191-
def __init__(self, file, data):
191+
def __init__(self, file, data, schema):
192192
"""Instantiate a BigOpenValidator.
193193
194194
Args:
@@ -202,7 +202,7 @@ class BigOpenValidator():
202202

203203
self.is_open = None
204204
if self.registry_license is not None:
205-
self.is_open = check_registry_license(data)
205+
self.is_open = check_registry_license(data, schema)
206206

207207
self.ontology_license = None
208208
self.correct_property = None
@@ -276,7 +276,7 @@ class BigOpenValidator():
276276
# ---------- UTILITY METHODS ---------- #
277277

278278

279-
def check_registry_license(data):
279+
def check_registry_license(data, schema):
280280
"""Use the JSON license schema to validate the registry data.
281281
282282
This ensures that the license is present and one of the CC0 or CC-BY
@@ -290,7 +290,7 @@ def check_registry_license(data):
290290
"""
291291

292292
try:
293-
jsonschema.validate(data, license_schema)
293+
jsonschema.validate(data, schema)
294294
return True
295295
except jsonschema.exceptions.ValidationError as ve:
296296
return False
@@ -304,7 +304,7 @@ def compare_licenses(registry_license, ontology_license):
304304
ontology_license (str): license URL from the ontology
305305
306306
Return:
307-
True if registry license matches ontology licences;
307+
True if registry license matches ontology license;
308308
False if the licenses do not match;
309309
None if one or both licenses are missing.
310310
"""
@@ -380,7 +380,7 @@ def process_results(registry_license,
380380
level = 'ERROR'
381381
issues.append(missing_ontology_license)
382382

383-
# matches_ontology = None if missing ontology licenese
383+
# matches_ontology = None if missing ontology license
384384
if matches_ontology is False:
385385
level = 'ERROR'
386386
issues.append(no_match.format(ontology_license, registry_license))
@@ -395,11 +395,8 @@ def process_results(registry_license,
395395
return {'status': level, 'comment': ' '.join(issues)}
396396

397397

398-
# correct dc license property namespace
398+
# correct dc license property
399399
license_prop = 'http://purl.org/dc/terms/license'
400-
# incorrect dc license property namespace
401-
bad_license_prop = 'http://purl.org/dc/elements/1.1/license'
402-
403-
# license JSON schema for registry validation
404-
license_schema = dash_utils.load_schema('dependencies/license.json')
400+
# incorrect dc license properties
401+
bad_license_props = ['http://purl.org/dc/elements/1.1/license', 'http://purl.org/dc/elements/1.1/rights', 'http://purl.org/dc/terms/rights']
405402
```

principles/checks/fp_002.md

Lines changed: 6 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -25,36 +25,19 @@ import dash_utils
2525
from dash_utils import format_msg
2626

2727

28-
def is_common_format(ontology):
28+
def is_common_format(syntax):
2929
"""Check FP 2 - Common Format.
3030
3131
Args:
32-
ontology (OWLOntology): ontology object
32+
syntax (str): the syntax as determined by ROBOT metrics
3333
3434
Return:
3535
PASS if OWLOntology is not None, ERROR otherwise.
3636
"""
37-
if ontology is None:
38-
return {'status': 'ERROR', 'comment': 'Unable to load ontology'}
39-
else:
37+
if syntax is None:
38+
return {'status': 'ERROR', 'comment': 'Unknown format'}
39+
elif syntax == "RDF/XML Syntax":
4040
return {'status': 'PASS'}
41-
42-
43-
def big_is_common_format(good_format):
44-
"""Check FP 2 - Common Format on large ontologies
45-
46-
Args:
47-
good_format (bool): True if ontology could be parsed by Jena
48-
49-
Return:
50-
PASS if good_format, ERROR otherwise.
51-
"""
52-
if good_format is None:
53-
return {'status': 'ERROR',
54-
'comment': 'Unable to load ontology (may be too large)'}
55-
elif good_format is False:
56-
return {'status': 'ERROR',
57-
'comment': 'Unable to parse ontology'}
5841
else:
59-
return {'status': 'PASS'}
42+
return {'status': 'WARN', 'comment': f'OWL syntax ({syntax}), but should be RDF/XML'}
6043
```

principles/checks/fp_003.md

Lines changed: 44 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -33,20 +33,19 @@ The full OBO Foundry ID Policy can be found [here](http://www.obofoundry.org/id-
3333
All entity IRIs are retrieved from the ontology, excluding annotation properties. Annotation properties may use hashtags and words due to legacy OBO conversions for subset properties. All other IRIs are checked if they are in the ontology's namespace. If the IRI begins with the ontology namespace, the next character must be an underscore. If not, this is an error. The IRI is also compared to a regex pattern to check if the local ID after the underscore is numeric. If not, this is a warning.
3434

3535
```python
36-
import dash_utils
3736
import os
3837
import re
3938

40-
from dash_utils import format_msg
39+
import dash_utils
4140

4241
iri_pattern = r'http:\/\/purl\.obolibrary\.org\/obo\/%s_[0-9]{1,9}'
4342
owl_deprecated = 'http://www.w3.org/2002/07/owl#deprecated'
4443

45-
error_msg = '{0} invalid IRIs'
44+
error_msg = '{} invalid IRIs. The Ontology IRI is {}valid.'
4645
warn_msg = '{0} warnings on IRIs'
4746

4847

49-
def has_valid_uris(robot_gateway, namespace, ontology):
48+
def has_valid_uris(robot_gateway, namespace, ontology, ontology_dir):
5049
"""Check FP 3 - URIs.
5150
5251
This check ensures that all ontology entities follow NS_LOCALID.
@@ -67,6 +66,7 @@ def has_valid_uris(robot_gateway, namespace, ontology):
6766
otherwise.
6867
"""
6968
if not ontology:
69+
dash_utils.write_empty(os.path.join(ontology_dir, 'fp3.tsv'), ["Status", "Issue"])
7070
return {'status': 'ERROR', 'comment': 'Unable to load ontology'}
7171

7272
entities = robot_gateway.OntologyHelper.getEntities(ontology)
@@ -95,10 +95,14 @@ def has_valid_uris(robot_gateway, namespace, ontology):
9595
elif check == 'WARN':
9696
warn.append(iri)
9797

98-
return save_invalid_uris(namespace, error, warn)
98+
ontology_iri = dash_utils.get_ontology_iri(ontology)
99+
100+
valid_iri = is_valid_ontology_iri(ontology_iri, namespace)
99101

102+
return save_invalid_uris(error, warn, ontology_dir, valid_iri)
100103

101-
def big_has_valid_uris(namespace, file):
104+
105+
def big_has_valid_uris(namespace, file, ontology_dir):
102106
"""Check FP 3 - URIs on a big ontology.
103107
104108
This check ensures that all ontology entities follow NS_LOCALID.
@@ -112,6 +116,7 @@ def big_has_valid_uris(namespace, file):
112116
Args:
113117
namespace (str): ontology ID
114118
file (str): path to ontology file
119+
ontology_dir (str):
115120
116121
Return:
117122
INFO if ontology IRIs cannot be parsed. ERROR if any errors, WARN if
@@ -134,6 +139,7 @@ def big_has_valid_uris(namespace, file):
134139
if 'Ontology' and 'about' in line:
135140
if not owl and not rdf:
136141
# did not find OWL and RDF - end now
142+
dash_utils.write_empty(os.path.join(ontology_dir, 'fp3.tsv'), ["Status", "Issue"])
137143
return {'status': 'ERROR',
138144
'comment': 'Unable to parse ontology'}
139145

@@ -171,11 +177,20 @@ def big_has_valid_uris(namespace, file):
171177

172178
if not valid:
173179
# not valid ontology
180+
dash_utils.write_empty(os.path.join(ontology_dir, 'fp3.tsv'), ["Status", "Issue"])
174181
return {'status': 'ERROR',
175182
'comment': 'Unable to parse ontology'}
176183

177-
return save_invalid_uris(namespace, error, warn)
184+
return save_invalid_uris(error, warn, ontology_dir)
185+
178186

187+
def is_valid_ontology_iri(iri, namespace):
188+
if iri:
189+
if iri == 'http://purl.obolibrary.org/obo/{0}.owl'.format(namespace):
190+
return True
191+
if iri == 'http://purl.obolibrary.org/obo/{0}/{0}-base.owl'.format(namespace):
192+
return True
193+
return False
179194

180195
def check_uri(namespace, iri):
181196
"""Check if a given IRI is valid.
@@ -193,43 +208,52 @@ def check_uri(namespace, iri):
193208
return True
194209
if iri.startswith(namespace):
195210
# all NS IRIs must follow NS_
196-
if not iri.startwith(namespace + '_'):
211+
if not iri.startswith(namespace + '_'):
197212
return 'ERROR'
198213
# it is recommended to follow NS_NUMID
199214
elif not re.match(pattern, iri, re.IGNORECASE):
200215
return 'WARN'
201216
return True
202217

203218

204-
def save_invalid_uris(ns, error, warn):
219+
def save_invalid_uris(error, warn, ontology_dir, valid_ontology_iri = True):
205220
"""Save invalid (error or warning) IRIs to a report file
206-
(reports/dashboard/*/fp3.tsv).
207221
208222
Args:
209-
ns (str): ontology ID
210223
error (list): list of ERROR IRIs
211224
warn (list): list of WARN IRIs
225+
ontology_dir (str):
212226
213227
Return:
214228
ERROR or WARN with detailed message, or PASS if no errors or warnings.
215229
"""
216-
if len(error) > 0 or len(warn) > 0:
217-
file = 'build/dashboard/{0}/fp3.tsv'.format(ns)
218-
with open(file, 'w+') as f:
219-
for e in error:
220-
f.write('ERROR\t{0}\n'.format(e))
221-
for w in warn:
222-
f.write('WARN\t{0}\n'.format(w))
230+
# write a report (maybe empty)
231+
file = os.path.join(ontology_dir, 'fp3.tsv')
232+
233+
with open(file, 'w+') as f:
234+
f.write('Status\tIssue\n')
235+
for e in error:
236+
f.write('ERROR\t{0}\n'.format(e))
237+
for w in warn:
238+
f.write('WARN\t{0}\n'.format(w))
239+
240+
o_iri_msg=""
241+
if not valid_ontology_iri:
242+
o_iri_msg = "not "
223243

224244
if len(error) > 0 and len(warn) > 0:
225245
return {'status': 'ERROR',
226246
'file': 'fp3',
227-
'comment': ' '.join([error_msg.format(len(error)),
247+
'comment': ' '.join([error_msg.format(len(error), o_iri_msg),
228248
warn_msg.format(len(warn))])}
229249
elif len(error) > 0:
230250
return {'status': 'ERROR',
231251
'file': 'fp3',
232-
'comment': error_msg.format(len(error))}
252+
'comment': error_msg.format(len(error), o_iri_msg)}
253+
elif not valid_ontology_iri:
254+
return {'status': 'ERROR',
255+
'file': 'fp3',
256+
'comment': error_msg.format(0, o_iri_msg)}
233257
elif len(warn) > 0:
234258
return {'status': 'ERROR',
235259
'file': 'fp3',

0 commit comments

Comments
 (0)