Skip to content

Commit 4123bff

Browse files
authored
Merge pull request #83 from casework/release-0.8.0
Release 0.8.0
2 parents 3daebe6 + 46d6bb7 commit 4123bff

32 files changed

+21469
-47
lines changed

.github/workflows/cicd.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ jobs:
3232
matrix:
3333
python-version:
3434
- '3.7'
35-
- '3.10'
35+
- '3.11'
3636

3737
steps:
3838
- uses: actions/checkout@v2

CONTRIBUTE.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,12 @@ pushd case_utils/ontology
2727
git add case-0.6.0.ttl # Assuming CASE 0.6.0 was just released.
2828
# and/or
2929
git add uco-0.8.0.ttl # Assuming UCO 0.8.0 was adopted in CASE 0.6.0.
30+
31+
git add ontology_and_version_iris.txt
3032
popd
3133
make check
3234
# Assuming `make check` passes:
33-
git commit -m "Build CASE 0.6.0 monolithic .ttl files" case_utils/ontology/case-0.6.0-subclasses.ttl case_utils/ontology/case-0.6.0.ttl
35+
git commit -m "Build CASE 0.6.0 monolithic .ttl files" case_utils/ontology/case-0.6.0-subclasses.ttl case_utils/ontology/case-0.6.0.ttl case_utils/ontology/ontology_and_version_iris.txt
3436
git commit -m "Update CASE ontology pointer to version 0.6.0" dependencies/CASE case_utils/ontology/version_info.py
3537
```
3638

@@ -43,4 +45,4 @@ pre-commit --version
4345
The `pre-commit` tool hooks into Git's commit machinery to run a set of linters and static analyzers over each change. To install `pre-commit` into Git's hooks, run:
4446
```bash
4547
pre-commit install
46-
```
48+
```

README.md

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,17 @@ To produce the validation report as a machine-readable graph output, the `--form
5555
case_validate --format turtle input.json > result.ttl
5656
```
5757

58-
To use one or more supplementary ontology files, the `--ontology-graph` flag can be used, more than once if desired, to supplement the selected CASE version:
58+
To use one or more supplementary ontology or shape files, the `--ontology-graph` flag can be used, more than once if desired, to supplement the selected CASE version:
5959

6060
```bash
61-
case_validate --ontology-graph internal_ontology.ttl --ontology-graph experimental_shapes.ttl input.json
61+
case_validate \
62+
--ontology-graph internal_ontology.ttl \
63+
--ontology-graph experimental_shapes.ttl \
64+
input.json
6265
```
6366

67+
This tool uses the `--built-version` flag, described [below](#built-versions).
68+
6469
Other flags are reviewable with `case_validate --help`.
6570

6671

@@ -87,6 +92,8 @@ These commands can be used with any RDF files to run arbitrary SPARQL queries.
8792

8893
Note that prefixes used in the SPARQL queries do not need to be defined in the SPARQL query. Their mapping will be inherited from their first definition in the input graph files. However, input graphs are not required to agree on prefix mappings, so there is potential for confusion from input argument order mattering if two input graph files disagree on what a prefix maps to. If there is concern of ambiguity from inputs, a `PREFIX` statement should be included in the query, such as is shown in [this test query](tests/case_utils/case_sparql_select/subclass.sparql).
8994

95+
These tools use the `--built-version` flag, described [below](#built-versions).
96+
9097

9198
#### `case_sparql_construct`
9299

@@ -116,6 +123,15 @@ case_sparql_select output.md input.sparql input.json [input-2.json ...]
116123
This [module](case_utils/local_uuid.py) provides a wrapper UUID generator, `local_uuid()`. Its main purpose is making example data generate consistent identifiers, and intentionally includes mechanisms to make it difficult to activate this mode without awareness of the caller.
117124

118125

126+
### Built versions
127+
128+
Several tools in this package include a flag `--built-version`. This flag tailors the tool's behavior to a certain CASE ontology version; typically, this involves mixing the ontology graph into the data graph for certain necessary knowledge expansion for pattern matching (such as making queries aware of the OWL subclass hierarchy).
129+
130+
If not provided, the tool will assume a default value of the latest ontology version.
131+
132+
If the special value `none` is provided, none of the ontology builds this package ships will be included in the data graph. The `none` value supports use cases that are wholly independent of CASE, such as running a test in a specialized vocabulary; and also suports use cases where a non-released CASE version is meant to be used, such as a locally revised version of CASE where some concept revisions are being reviewed.
133+
134+
119135
## Development status
120136

121137
This repository follows [CASE community guidance on describing development status](https://caseontology.org/resources/software.html#development_status), by adherence to noted support requirements.

case_utils/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,6 @@
1111
#
1212
# We would appreciate acknowledgement if the software is used.
1313

14-
__version__ = "0.7.0"
14+
__version__ = "0.8.0"
1515

1616
from . import local_uuid # noqa: F401

case_utils/case_sparql_construct/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
This script executes a SPARQL CONSTRUCT query, returning a graph of the generated triples.
1616
"""
1717

18-
__version__ = "0.2.3"
18+
__version__ = "0.2.4"
1919

2020
import argparse
2121
import logging
@@ -49,7 +49,7 @@ def main() -> None:
4949
"--built-version",
5050
choices=tuple(built_version_choices_list),
5151
default="case-" + CURRENT_CASE_VERSION,
52-
help="Ontology version to use to supplement query, such as for subclass querying. Does not require networking to use. Default is most recent CASE release.",
52+
help="Ontology version to use to supplement query, such as for subclass querying. Does not require networking to use. Default is most recent CASE release. Passing 'none' will mean no pre-built CASE ontology versions accompanying this tool will be included in the analysis.",
5353
)
5454
parser.add_argument(
5555
"--disallow-empty-results",

case_utils/case_sparql_select/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
Should a more complex query be necessary, an outer, wrapping SELECT query would let this script continue to function.
2727
"""
2828

29-
__version__ = "0.4.3"
29+
__version__ = "0.4.4"
3030

3131
import argparse
3232
import binascii
@@ -63,7 +63,7 @@ def main() -> None:
6363
"--built-version",
6464
choices=tuple(built_version_choices_list),
6565
default="case-" + CURRENT_CASE_VERSION,
66-
help="Ontology version to use to supplement query, such as for subclass querying. Does not require networking to use. Default is most recent CASE release.",
66+
help="Ontology version to use to supplement query, such as for subclass querying. Does not require networking to use. Default is most recent CASE release. Passing 'none' will mean no pre-built CASE ontology versions accompanying this tool will be included in the analysis.",
6767
)
6868
parser.add_argument(
6969
"--disallow-empty-results",

case_utils/case_validate/__init__.py

Lines changed: 115 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,27 +29,47 @@
2929
details.)
3030
"""
3131

32-
__version__ = "0.1.3"
32+
__version__ = "0.2.0"
3333

3434
import argparse
3535
import importlib.resources
3636
import logging
3737
import os
3838
import sys
3939
import typing
40+
import warnings
4041

4142
import pyshacl # type: ignore
42-
import rdflib.util
43+
import rdflib
4344

4445
import case_utils.ontology
4546
from case_utils.ontology.version_info import (
4647
CURRENT_CASE_VERSION,
4748
built_version_choices_list,
4849
)
4950

51+
NS_OWL = rdflib.OWL
52+
NS_RDF = rdflib.RDF
53+
NS_RDFS = rdflib.RDFS
54+
5055
_logger = logging.getLogger(os.path.basename(__file__))
5156

5257

58+
class NonExistentCDOConceptWarning(UserWarning):
59+
"""
60+
This class is used when a concept is encountered in the data graph that is not part of CDO ontologies, according to the --built-version flags and --ontology-graph flags.
61+
"""
62+
63+
pass
64+
65+
66+
def concept_is_cdo_concept(n_concept: rdflib.URIRef) -> bool:
67+
concept_iri = str(n_concept)
68+
return concept_iri.startswith(
69+
"https://ontology.unifiedcyberontology.org/"
70+
) or concept_iri.startswith("https://ontology.caseontology.org/")
71+
72+
5373
def main() -> None:
5474
parser = argparse.ArgumentParser(
5575
description="CASE wrapper to pySHACL command line tool."
@@ -72,7 +92,7 @@ def main() -> None:
7292
"--built-version",
7393
choices=tuple(built_version_choices_list),
7494
default="case-" + CURRENT_CASE_VERSION,
75-
help="Monolithic aggregation of CASE ontology files at certain versions. Does not require networking to use. Default is most recent CASE release.",
95+
help="Monolithic aggregation of CASE ontology files at certain versions. Does not require networking to use. Default is most recent CASE release. Passing 'none' will mean no pre-built CASE ontology versions accompanying this tool will be included in the analysis.",
7696
)
7797
parser.add_argument(
7898
"--ontology-graph",
@@ -86,10 +106,20 @@ def main() -> None:
86106
action="store_true",
87107
help="(As with pyshacl CLI) Abort on first invalid data.",
88108
)
109+
parser.add_argument(
110+
"--allow-info",
111+
"--allow-infos",
112+
dest="allow_infos",
113+
action="store_true",
114+
default=False,
115+
help="(As with pyshacl CLI) Shapes marked with severity of Info will not cause result to be invalid.",
116+
)
89117
parser.add_argument(
90118
"-w",
119+
"--allow-warning",
91120
"--allow-warnings",
92121
action="store_true",
122+
dest="allow_warnings",
93123
help="(As with pyshacl CLI) Shapes marked with severity of Warning or Info will not cause result to be invalid.",
94124
)
95125
parser.add_argument(
@@ -112,6 +142,14 @@ def main() -> None:
112142
default="none",
113143
help='(As with pyshacl CLI) Choose a type of inferencing to run against the Data Graph before validating. Default is "none".',
114144
)
145+
parser.add_argument(
146+
"-m",
147+
"--metashacl",
148+
dest="metashacl",
149+
action="store_true",
150+
default=False,
151+
help="(As with pyshacl CLI) Validate the SHACL Shapes graph against the shacl-shacl Shapes Graph before validating the Data Graph.",
152+
)
115153
parser.add_argument(
116154
"-o",
117155
"--output",
@@ -142,6 +180,71 @@ def main() -> None:
142180
_logger.debug("arg_ontology_graph = %r.", arg_ontology_graph)
143181
ontology_graph.parse(arg_ontology_graph)
144182

183+
# Construct set of CDO concepts for data graph concept-existence review.
184+
cdo_concepts: typing.Set[rdflib.URIRef] = set()
185+
186+
for n_structural_class in [
187+
NS_OWL.Class,
188+
NS_OWL.AnnotationProperty,
189+
NS_OWL.DatatypeProperty,
190+
NS_OWL.ObjectProperty,
191+
NS_RDFS.Datatype,
192+
]:
193+
for ontology_triple in ontology_graph.triples(
194+
(None, NS_RDF.type, n_structural_class)
195+
):
196+
if not isinstance(ontology_triple[0], rdflib.URIRef):
197+
continue
198+
if concept_is_cdo_concept(ontology_triple[0]):
199+
cdo_concepts.add(ontology_triple[0])
200+
for n_ontology_predicate in [
201+
NS_OWL.backwardCompatibleWith,
202+
NS_OWL.imports,
203+
NS_OWL.incompatibleWith,
204+
NS_OWL.priorVersion,
205+
NS_OWL.versionIRI,
206+
]:
207+
for ontology_triple in ontology_graph.triples(
208+
(None, n_ontology_predicate, None)
209+
):
210+
assert isinstance(ontology_triple[0], rdflib.URIRef)
211+
assert isinstance(ontology_triple[2], rdflib.URIRef)
212+
cdo_concepts.add(ontology_triple[0])
213+
cdo_concepts.add(ontology_triple[2])
214+
for ontology_triple in ontology_graph.triples((None, NS_RDF.type, NS_OWL.Ontology)):
215+
if not isinstance(ontology_triple[0], rdflib.URIRef):
216+
continue
217+
cdo_concepts.add(ontology_triple[0])
218+
219+
# Also load historical ontology and version IRIs.
220+
ontology_and_version_iris_data = importlib.resources.read_text(
221+
case_utils.ontology, "ontology_and_version_iris.txt"
222+
)
223+
for line in ontology_and_version_iris_data.split("\n"):
224+
cleaned_line = line.strip()
225+
if cleaned_line == "":
226+
continue
227+
cdo_concepts.add(rdflib.URIRef(cleaned_line))
228+
229+
data_cdo_concepts: typing.Set[rdflib.URIRef] = set()
230+
for data_triple in data_graph.triples((None, None, None)):
231+
for data_triple_member in data_triple:
232+
if isinstance(data_triple_member, rdflib.URIRef):
233+
if concept_is_cdo_concept(data_triple_member):
234+
data_cdo_concepts.add(data_triple_member)
235+
elif isinstance(data_triple_member, rdflib.Literal):
236+
if isinstance(data_triple_member.datatype, rdflib.URIRef):
237+
if concept_is_cdo_concept(data_triple_member.datatype):
238+
data_cdo_concepts.add(data_triple_member.datatype)
239+
240+
undefined_cdo_concepts = data_cdo_concepts - cdo_concepts
241+
for undefined_cdo_concept in sorted(undefined_cdo_concepts):
242+
warnings.warn(undefined_cdo_concept, NonExistentCDOConceptWarning)
243+
undefined_cdo_concepts_message = (
244+
"There were %d concepts with CDO IRIs in the data graph that are not in the ontology graph."
245+
% len(undefined_cdo_concepts)
246+
)
247+
145248
# Determine output format.
146249
# pySHACL's determination of output formatting is handled solely
147250
# through the -f flag. Other CASE CLI tools handle format
@@ -160,7 +263,9 @@ def main() -> None:
160263
shacl_graph=ontology_graph,
161264
ont_graph=ontology_graph,
162265
inference=args.inference,
266+
meta_shacl=args.metashacl,
163267
abort_on_first=args.abort,
268+
allow_infos=True if args.allow_infos else False,
164269
allow_warnings=True if args.allow_warnings else False,
165270
debug=True if args.debug else False,
166271
do_owl_imports=True if args.imports else False,
@@ -194,6 +299,13 @@ def main() -> None:
194299
% type(validation_graph)
195300
)
196301

302+
if len(undefined_cdo_concepts) > 0:
303+
warnings.warn(undefined_cdo_concepts_message)
304+
if not args.allow_warnings:
305+
undefined_cdo_concepts_alleviation_message = "The data graph is SHACL-conformant with the CDO ontologies, but nonexistent-concept references raise Warnings with this tool. Please either correct the concept names in the data graph; use the --ontology-graph flag to pass a corrected CDO ontology file, also using --built-version none; or, use the --allow-warnings flag."
306+
warnings.warn(undefined_cdo_concepts_alleviation_message)
307+
conforms = False
308+
197309
sys.exit(0 if conforms else 1)
198310

199311

case_utils/local_uuid.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
This library is a wrapper for uuid, provided to generate repeatable UUIDs if requested.
1616
"""
1717

18-
__version__ = "0.3.0"
18+
__version__ = "0.3.1"
1919

2020
import logging
2121
import os
@@ -38,7 +38,7 @@ def configure() -> None:
3838
if os.getenv("DEMO_UUID_REQUESTING_NONRANDOM") == "NONRANDOM_REQUESTED":
3939
warnings.warn(
4040
"Environment variable DEMO_UUID_REQUESTING_NONRANDOM is deprecated. See case_utils.local_uuid.demo_uuid for usage notes on its replacement, CASE_DEMO_NONRANDOM_UUID_BASE. Proceeding with random UUIDs.",
41-
DeprecationWarning,
41+
FutureWarning,
4242
)
4343
return
4444

@@ -49,12 +49,14 @@ def configure() -> None:
4949
base_dir_original_path = pathlib.Path(env_base_dir_name)
5050
if not base_dir_original_path.exists():
5151
warnings.warn(
52-
"Environment variable CASE_DEMO_NONRANDOM_UUID_BASE is expected to refer to an existing directory. Proceeding with random UUIDs."
52+
"Environment variable CASE_DEMO_NONRANDOM_UUID_BASE is expected to refer to an existing directory. Proceeding with random UUIDs.",
53+
RuntimeWarning,
5354
)
5455
return
5556
if not base_dir_original_path.is_dir():
5657
warnings.warn(
57-
"Environment variable CASE_DEMO_NONRANDOM_UUID_BASE is expected to refer to a directory. Proceeding with random UUIDs."
58+
"Environment variable CASE_DEMO_NONRANDOM_UUID_BASE is expected to refer to a directory. Proceeding with random UUIDs.",
59+
RuntimeWarning,
5860
)
5961
return
6062

@@ -108,9 +110,9 @@ def demo_uuid() -> str:
108110
"""
109111
This function generates a repeatable UUID, drawing on non-varying elements of the environment and process call for entropy.
110112
111-
WARNING: This function was developed for use ONLY for reducing (but not eliminating) version-control edits to identifiers in sample data. It creates UUIDs that are decidedly NOT random, and should remain consistent on repeated calls to the importing script.
113+
WARNING: This function was developed for use ONLY for reducing (but not eliminating) version-control edits to identifiers when generating sample data. It creates UUIDs that are decidedly NOT random, and should remain consistent on repeated calls to the importing script.
112114
113-
To prevent accidental non-random UUID usage, an environment variable must be set to a string provided by the caller. The variable's required value is the path to some directory. The variable's recommended value is the equivalent of the Make variable "top_srcdir" - that is, the root directory of the containing Git repository, some parent of the current process's current working directory.
115+
To prevent accidental non-random UUID usage, an environment variable, CASE_DEMO_NONRANDOM_UUID_BASE, must be set to a string provided by the caller. The variable's required value is the path to some directory. The variable's recommended value is the equivalent of the Make variable "top_srcdir" - that is, the root directory of the containing Git repository, some parent of the current process's current working directory.
114116
"""
115117
global DEMO_UUID_BASE
116118
global DEMO_UUID_COUNTER

case_utils/namespace.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
To use, add "from case_utils.namespace import *". Namespace variables starting with "NS_" are imported. As needs are demonstrated in CASE tooling (both in case_utils and from downstream requests), namespaces will also be imported from rdflib for a consistent "NS_*" spelling.
1818
"""
1919

20-
__version__ = "0.2.0"
20+
__version__ = "0.2.1"
2121

2222
import rdflib
2323

@@ -37,6 +37,9 @@
3737
NS_UCO_ACTION = rdflib.Namespace(
3838
"https://ontology.unifiedcyberontology.org/uco/action/"
3939
)
40+
NS_UCO_ANALYSIS = rdflib.Namespace(
41+
"https://ontology.unifiedcyberontology.org/uco/analysis/"
42+
)
4043
NS_UCO_CONFIGURATION = rdflib.Namespace(
4144
"https://ontology.unifiedcyberontology.org/uco/configuration/"
4245
)

0 commit comments

Comments
 (0)