From 05f80db370510c074fc1dab2fe9971d6c9d0c86b Mon Sep 17 00:00:00 2001 From: Alex Nelson Date: Wed, 18 Sep 2024 17:55:15 -0400 Subject: [PATCH 1/9] Add single-stub generator script and Facet-by-IRI-pattern support A follow-on patch will regenerate Make-managed files. Signed-off-by: Alex Nelson --- Makefile | 35 ++- requirements.txt | 1 + src/facet_cardinalities_ttl.py | 114 ++++++++ src/generate_single_stub_json.py | 462 +++++++++++++++++++++++++++++++ tests/Makefile | 64 +++++ var/Makefile | 38 +++ 6 files changed, 711 insertions(+), 3 deletions(-) create mode 100644 src/facet_cardinalities_ttl.py create mode 100644 src/generate_single_stub_json.py create mode 100644 tests/Makefile create mode 100644 var/Makefile diff --git a/Makefile b/Makefile index ccacd0e..7683aae 100644 --- a/Makefile +++ b/Makefile @@ -19,12 +19,16 @@ SHELL := /bin/bash PYTHON3 ?= python3 py_srcfiles := \ - generate.py + src/facet_cardinalities_ttl.py \ + src/generate_single_stub_json.py all: \ - .venv-pre-commit/var/.pre-commit-built.log + .venv-pre-commit/var/.pre-commit-built.log \ + all-tests .PHONY: \ + all-tests \ + all-var \ check-supply-chain \ check-supply-chain-pre-commit @@ -37,6 +41,20 @@ all: \ $(py_srcfiles) touch $@ +.venv.done.log: \ + requirements.txt + rm -rf venv + $(PYTHON3) -m venv \ + venv + source venv/bin/activate \ + && pip install \ + --upgrade \ + pip + source venv/bin/activate \ + && pip install \ + --requirement requirements.txt + touch $@ + # This virtual environment is meant to be built once and then persist, even through 'make clean'. # If a recipe is written to remove this flag file, it should first run `pre-commit uninstall`. .venv-pre-commit/var/.pre-commit-built.log: @@ -60,8 +78,19 @@ all: \ .venv-pre-commit/var touch $@ +all-tests: \ + all-var + $(MAKE) \ + --directory tests + +all-var: \ + .mypy.done.log + $(MAKE) \ + --directory var + check: \ - .venv-pre-commit/var/.pre-commit-built.log + .venv-pre-commit/var/.pre-commit-built.log \ + all-tests check-supply-chain: \ check-supply-chain-pre-commit diff --git a/requirements.txt b/requirements.txt index 7591e8c..9aee9ff 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ +PyLD case-utils mypy diff --git a/src/facet_cardinalities_ttl.py b/src/facet_cardinalities_ttl.py new file mode 100644 index 0000000..519eca4 --- /dev/null +++ b/src/facet_cardinalities_ttl.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python3 + +# Portions of this file contributed by NIST are governed by the +# following statement: +# +# This software was developed at the National Institute of Standards +# and Technology by employees of the Federal Government in the course +# of their official duties. Pursuant to title 17 Section 105 of the +# United States Code this software is not subject to copyright +# protection and is in the public domain. NIST assumes no +# responsibility whatsoever for its use by other parties, and makes +# no guarantees, expressed or implied, about its quality, +# reliability, or any other characteristic. +# +# We would appreciate acknowledgement if the software is used. + +import argparse +import importlib.resources +import logging +from typing import Set + +import case_utils.ontology +from case_utils.namespace import NS_OWL, NS_RDF, NS_RDFS, NS_UCO_CORE, NS_XSD +from rdflib import BNode, Graph, Literal, URIRef +from rdflib.query import ResultRow + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("out_graph") + args = parser.parse_args() + + logging.basicConfig(level=logging.INFO) + + in_graph = Graph() + out_graph = Graph() + ttl_data = importlib.resources.read_text(case_utils.ontology, "case-1.3.0.ttl") + in_graph.parse(data=ttl_data) + case_utils.ontology.load_subclass_hierarchy(in_graph) + + n_leaf_facet_classes: Set[URIRef] = set() + leaf_facet_query = """\ +PREFIX owl: +PREFIX rdfs: +PREFIX uco-core: +SELECT ?nClass +WHERE { + ?nClass + a owl:Class ; + rdfs:subClassOf* uco-core:Facet ; + . + FILTER NOT EXISTS { + ?nSubClass + a owl:Class ; + rdfs:subClassOf ?nClass ; + . + } +} +""" + for result in in_graph.query(leaf_facet_query): + assert isinstance(result, ResultRow) + assert isinstance(result[0], URIRef) + n_leaf_facet_classes.add(result[0]) + + n_uco_object_classes: Set[URIRef] = set() + uco_object_query = """\ +PREFIX owl: +PREFIX rdfs: +PREFIX uco-core: +SELECT ?nClass +WHERE { + ?nClass + a owl:Class ; + rdfs:subClassOf* uco-core:UcoObject ; + . +} +""" + for result in in_graph.query(uco_object_query): + assert isinstance(result, ResultRow) + assert isinstance(result[0], URIRef) + n_uco_object_classes.add(result[0]) + + n_leaf_facet_classes_restricted: Set[URIRef] = set() + # Determine which Facets are named by the pattern of a corresponding UcoObject subclass, plus "Facet". + for n_uco_object_class in n_uco_object_classes: + uco_object_class_iri = str(n_uco_object_class) + n_maybe_leaf_facet = URIRef(uco_object_class_iri + "Facet") + if n_maybe_leaf_facet in n_leaf_facet_classes: + n_restriction = BNode() + out_graph.add((n_restriction, NS_RDF.type, NS_OWL.Restriction)) + out_graph.add((n_restriction, NS_OWL.onClass, n_maybe_leaf_facet)) + out_graph.add((n_restriction, NS_OWL.onProperty, NS_UCO_CORE.hasFacet)) + out_graph.add( + ( + n_restriction, + NS_OWL.qualifiedCardinality, + Literal("1", datatype=NS_XSD.nonNegativeInteger), + ) + ) + out_graph.add((n_uco_object_class, NS_RDFS.subClassOf, n_restriction)) + n_leaf_facet_classes_restricted.add(n_maybe_leaf_facet) + + if len(n_leaf_facet_classes_restricted) < len(n_leaf_facet_classes): + logging.info("These classes had no pattern-matched UcoObject subclasses:") + for n_leaf_facet_class in sorted( + n_leaf_facet_classes - n_leaf_facet_classes_restricted + ): + logging.info("* %s", str(n_leaf_facet_class)) + + out_graph.serialize(args.out_graph) + + +if __name__ == "__main__": + main() diff --git a/src/generate_single_stub_json.py b/src/generate_single_stub_json.py new file mode 100644 index 0000000..6e20577 --- /dev/null +++ b/src/generate_single_stub_json.py @@ -0,0 +1,462 @@ +#!/usr/bin/env python3 + +# Portions of this file contributed by NIST are governed by the +# following statement: +# +# This software was developed at the National Institute of Standards +# and Technology by employees of the Federal Government in the course +# of their official duties. Pursuant to title 17 Section 105 of the +# United States Code this software is not subject to copyright +# protection and is in the public domain. NIST assumes no +# responsibility whatsoever for its use by other parties, and makes +# no guarantees, expressed or implied, about its quality, +# reliability, or any other characteristic. +# +# We would appreciate acknowledgement if the software is used. + +import argparse +import importlib.resources +import json +import logging +from typing import Dict, List, Optional, Set + +import case_utils.ontology +import pyld # type: ignore +from case_utils.namespace import ( + NS_CASE_INVESTIGATION, + NS_CASE_VOCABULARY, + NS_CO, + NS_OWL, + NS_RDF, + NS_UCO_ACTION, + NS_UCO_CONFIGURATION, + NS_UCO_CORE, + NS_UCO_IDENTITY, + NS_UCO_LOCATION, + NS_UCO_MARKING, + NS_UCO_OBSERVABLE, + NS_UCO_PATTERN, + NS_UCO_ROLE, + NS_UCO_TOOL, + NS_UCO_TYPES, + NS_UCO_VICTIM, + NS_UCO_VOCABULARY, + NS_XSD, +) +from rdflib import Graph, Literal, Namespace, URIRef +from rdflib.query import ResultRow + +# JSON type via: +# https://github.com/python/typing/issues/182#issuecomment-1320974824 +JSON = Dict[str, "JSON"] | List["JSON"] | str | int | float | bool | None + +NS_KB = Namespace("http://example.org/kb/") + +CDO_CONTEXT: Dict[str, Namespace] = { + "case-investigation": NS_CASE_INVESTIGATION, + "case-vocabulary": NS_CASE_VOCABULARY, + "co": NS_CO, + "uco-action": NS_UCO_ACTION, + "uco-configuration": NS_UCO_CONFIGURATION, + "uco-core": NS_UCO_CORE, + "uco-identity": NS_UCO_IDENTITY, + "uco-location": NS_UCO_LOCATION, + "uco-marking": NS_UCO_MARKING, + "uco-observable": NS_UCO_OBSERVABLE, + "uco-pattern": NS_UCO_PATTERN, + "uco-role": NS_UCO_ROLE, + "uco-tool": NS_UCO_TOOL, + "uco-types": NS_UCO_TYPES, + "uco-victim": NS_UCO_VICTIM, + "uco-vocabulary": NS_UCO_VOCABULARY, +} + + +def resolve_max_cardinality( + graph: Graph, n_class: URIRef, n_property: URIRef +) -> Optional[int]: + """ + Give the maximum cardinality for a property on a specific class. + Returns None for unbounded, otherwise an int for ceiling. + Precondition: Assumes (i.e., does not check that) property is associated with n_class. + + >>> import rdflib + >>> g = rdflib.Graph() + >>> data = '''\ +@prefix ex: .\ +@prefix owl: .\ +@prefix rdf: .\ +@prefix rdfs: .\ +@prefix sh: .\ +@prefix xsd: .\ +\ +ex:A\ + a owl:Class , sh:NodeShape ;\ + sh:property [\ + sh:maxCount 4 ;\ + sh:path ex:foo ;\ + ] ;\ + .\ +\ +ex:B\ + a owl:Class , sh:NodeShape ;\ + rdfs:subClassOf ex:A ;\ + sh:property [\ + sh:maxCount 3 ;\ + sh:path ex:foo ;\ + ] ;\ + .\ +\ +ex:C\ + a owl:Class ;\ + rdfs:subClassOf\ + ex:B ,\ + [\ + a owl:Restriction ;\ + owl:onProperty ex:foo ;\ + owl:maxCardinality "2"^^xsd:nonNegativeInteger ;\ + ]\ + ;\ + .\ +\ +ex:foo\ + a owl:AnnotationProperty ;\ + .\ +''' + >>> _ = g.parse(data=data, format="turtle") + >>> ns_ex = rdflib.Namespace("http://example.org/ontology/") + >>> resolve_max_cardinality(g, ns_ex["A"], ns_ex["foo"]) + 4 + >>> resolve_max_cardinality(g, ns_ex["B"], ns_ex["foo"]) + 3 + >>> resolve_max_cardinality(g, ns_ex["C"], ns_ex["foo"]) + 2 + """ + max_counts: Set[int] = set() + query = """\ +SELECT ?lMaxCount +WHERE { + ?nClass rdfs:subClassOf* ?nSuperClass . + { + ?nSuperClass sh:property ?nPropertyShape . + ?nPropertyShape + sh:maxCount ?lMaxCount ; + sh:path ?nProperty ; + . + } + UNION + { + ?nSuperClass rdfs:subClassOf ?nRestriction . + ?nRestriction + a owl:Restriction ; + owl:onProperty ?nProperty ; + owl:maxCardinality ?lMaxCount ; + . + } +} +""" + for result in graph.query( + query, initBindings={"nClass": n_class, "nProperty": n_property} + ): + assert isinstance(result, ResultRow) + if not isinstance(result[0], Literal): + continue + max_counts.add(int(result[0])) + if len(max_counts) == 0: + return None + else: + return min(max_counts) + + +def get_properties(graph: Graph, n_class: URIRef) -> Set[URIRef]: + """ + >>> import rdflib + >>> g = rdflib.Graph() + >>> data = '''\ +@prefix ex: .\ +@prefix owl: .\ +@prefix rdf: .\ +@prefix rdfs: .\ +@prefix sh: .\ +@prefix xsd: .\ +\ +ex:A\ + a owl:Class , sh:NodeShape ;\ + sh:property [\ + sh:maxCount 4 ;\ + sh:path ex:foo ;\ + ] ;\ + .\ +\ +ex:B\ + a owl:Class , sh:NodeShape ;\ + rdfs:subClassOf ex:A ;\ + sh:property [\ + sh:maxCount 3 ;\ + sh:path ex:bar ;\ + ] ;\ + .\ +\ +ex:C\ + a owl:Class , sh:NodeShape ;\ + rdfs:subClassOf\ + ex:A ,\ + [\ + a owl:Restriction ;\ + owl:onProperty ex:baz ;\ + owl:maxCardinality "2"^^xsd:nonNegativeInteger ;\ + ]\ + ;\ + .\ +\ +ex:D\ + a owl:Class ;\ + .\ +\ +ex:bar\ + a owl:AnnotationProperty ;\ + .\ +\ +ex:baz\ + a owl:AnnotationProperty ;\ + .\ +\ +ex:foo\ + a owl:AnnotationProperty ;\ + .\ +\ +ex:cname\ + a owl:DatatypeProperty ;\ + rdfs:domain [\ + a owl:Class ;\ + owl:unionOf (\ + ex:C\ + ex:D\ + );\ + ];\ + .\ +''' + >>> _ = g.parse(data=data, format="turtle") + >>> ns_ex = rdflib.Namespace("http://example.org/ontology/") + >>> [str(x) for x in sorted(get_properties(g, ns_ex["A"]))] + ['http://example.org/ontology/foo'] + >>> [str(x) for x in sorted(get_properties(g, ns_ex["B"]))] + ['http://example.org/ontology/bar', 'http://example.org/ontology/foo'] + >>> [str(x) for x in sorted(get_properties(g, ns_ex["C"]))] + ['http://example.org/ontology/baz', 'http://example.org/ontology/cname', 'http://example.org/ontology/foo'] + >>> [str(x) for x in sorted(get_properties(g, ns_ex["D"]))] + ['http://example.org/ontology/cname'] + """ + n_properties: Set[URIRef] = set() + query = """\ +SELECT ?nProperty +WHERE { + ?nClass rdfs:subClassOf* ?nSuperClass . + { + ?nSuperClass sh:property ?nPropertyShape . + ?nPropertyShape + sh:path ?nProperty ; + . + } + UNION + { + ?nSuperClass rdfs:subClassOf ?nRestriction . + ?nRestriction + a owl:Restriction ; + owl:onProperty ?nProperty ; + . + } + UNION + { + ?nProperty rdfs:domain/(owl:unionOf/rdf:rest*/rdf:first)? ?nSuperClass . + } +} +""" + for result in graph.query(query, initBindings={"nClass": n_class}): + assert isinstance(result, ResultRow) + if not isinstance(result[0], URIRef): + continue + n_properties.add(result[0]) + return n_properties + + +def get_facet_classes(graph: Graph, n_class: URIRef) -> Set[URIRef]: + logging.debug("get_facet_classes(graph, %r) ...", n_class) + n_facet_classes: Set[URIRef] = set() + query = """\ +PREFIX uco-core: +SELECT ?nFacetClass +WHERE { + ?nClass rdfs:subClassOf+ ?nRestriction . + ?nRestriction + a owl:Restriction ; + owl:onProperty uco-core:hasFacet ; + owl:onClass ?nFacetClass ; + . + ?nFacetClass + rdfs:subClassOf+ uco-core:Facet ; + . +} + +""" + for result in graph.query(query, initBindings={"nClass": n_class}): + assert isinstance(result, ResultRow) + if not isinstance(result[0], URIRef): + continue + n_facet_classes.add(result[0]) + return n_facet_classes + + +def generate_expanded_stub(graph: Graph, n_subject_class: URIRef) -> Dict[str, JSON]: + subject_prefix, n_namespace, local_name = graph.namespace_manager.compute_qname( + n_subject_class, False + ) + + expanded_individual: Dict[str, JSON] = { + "@id": str(NS_KB[local_name + "-1"]), + "@type": str(n_subject_class), + } + + for n_property in get_properties(graph, n_subject_class): + max_cardinality = resolve_max_cardinality(graph, n_subject_class, n_property) + stub_value: JSON + if max_cardinality == 0: + continue + elif max_cardinality == 1: + stub_value = None + else: + stub_value = [] + expanded_individual[str(n_property)] = stub_value + + logging.debug(expanded_individual.get(str(NS_UCO_CORE.hasFacet))) + + if expanded_individual.get(str(NS_UCO_CORE.hasFacet)) is not None: + facet_stubs_list = expanded_individual[str(NS_UCO_CORE.hasFacet)] + assert isinstance(facet_stubs_list, list) + for n_facet_class in sorted(get_facet_classes(graph, n_subject_class)): + logging.debug("n_facet_class = %r.", n_facet_class) + expanded_facet_individual = generate_expanded_stub(graph, n_facet_class) + facet_stubs_list.append(expanded_facet_individual) + + return expanded_individual + + +def get_concept_iris(document: JSON) -> Set[URIRef]: + retval: Set[URIRef] = set() + assert isinstance(document, dict) + for key in document: + if key == "@id": + continue + elif key == "@type": + if isinstance(document[key], str): + type_iri = document[key] + assert isinstance(type_iri, str) + retval.add(URIRef(type_iri)) + elif isinstance(document[key], list): + child_things = document[key] + assert isinstance(child_things, list) + for child_thing in child_things: + assert isinstance(child_thing, str) + retval.add(URIRef(child_thing)) + else: + retval.add(URIRef(key)) + if isinstance(document[key], dict): + retval |= get_concept_iris(document[key]) + if isinstance(document[key], list): + child_things = document[key] + assert isinstance(child_things, list) + for child_thing in child_things: + if isinstance(child_thing, dict): + retval |= get_concept_iris(child_thing) + return retval + + +def swap_values( + document: JSON, from_value: Optional[int], to_value: Optional[int] +) -> None: + """ + This is a hack to place a sentinel value in place of nulls. The JSON-LD compaction algorithm does not necessarily preserve a key if its object is a null, so this function swaps in something that will be preserved. + >>> d = {"foo": None, "bar": 9} + >>> swap_values(d, None, 8) + >>> d + {'foo': 8, 'bar': 9} + >>> swap_values(d, 9, None) + >>> d + {'foo': 8, 'bar': None} + """ + assert isinstance(document, dict) + for key in document: + if isinstance(document[key], dict): + swap_values(document[key], from_value, to_value) + if isinstance(document[key], list): + child_things = document[key] + assert isinstance(child_things, list) + for child_thing in child_things: + if isinstance(child_thing, dict): + swap_values(child_thing, from_value, to_value) + elif isinstance(document[key], int): + if isinstance(from_value, int): + if document[key] == from_value: + document[key] = to_value + elif document[key] is None: + if from_value is None: + document[key] = to_value + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--debug", action="store_true") + parser.add_argument("out_json") + parser.add_argument("class_iri") + parser.add_argument("supplemental_graph", nargs="*") + args = parser.parse_args() + + logging.basicConfig(level=logging.DEBUG if args.debug else logging.INFO) + + graph = Graph() + ttl_data = importlib.resources.read_text(case_utils.ontology, "case-1.3.0.ttl") + graph.parse(data=ttl_data) + logging.debug("len(graph) = %d.", len(graph)) + + if args.supplemental_graph: + for supplemental_graph_filename in args.supplemental_graph: + logging.debug("Loading %r.", supplemental_graph_filename) + graph.parse(supplemental_graph_filename) + logging.debug("len(graph) = %d.", len(graph)) + + for key in CDO_CONTEXT: + graph.bind(key, CDO_CONTEXT[key]) + + n_subject_class = URIRef(args.class_iri) + if (n_subject_class, NS_RDF.type, NS_OWL.Class) not in graph: + raise ValueError( + "Requested class IRI not found in CASE graph: %r." % args.class_iri + ) + + expanded_stub = generate_expanded_stub(graph, n_subject_class) + + all_concept_iris = get_concept_iris(expanded_stub) + all_used_prefixes: Set[str] = set() + for concept_iri in all_concept_iris: + prefix, _1, _2 = graph.namespace_manager.compute_qname(concept_iri, False) + all_used_prefixes.add(prefix) + + # Build context dictionary that only uses prefixes for concepts that appear in the expanded document. + context: Dict[str, str] = { + "kb": str(NS_KB), + "xsd": str(NS_XSD), + } + for prefix in all_used_prefixes: + context[prefix] = str(CDO_CONTEXT[prefix]) + + swap_values(expanded_stub, None, 9) + # logging.debug("expanded_stub = %r.", expanded_stub) + compacted_graph = pyld.jsonld.compact(expanded_stub, context) + swap_values(compacted_graph, 9, None) + + with open(args.out_json, "w") as out_fh: + json.dump(compacted_graph, out_fh, indent=4, sort_keys=True) + + +if __name__ == "__main__": + main() diff --git a/tests/Makefile b/tests/Makefile new file mode 100644 index 0000000..c78882c --- /dev/null +++ b/tests/Makefile @@ -0,0 +1,64 @@ +#!/usr/bin/make -f + +# Portions of this file contributed by NIST are governed by the +# following statement: +# +# This software was developed at the National Institute of Standards +# and Technology by employees of the Federal Government in the course +# of their official duties. Pursuant to title 17 Section 105 of the +# United States Code this software is not subject to copyright +# protection and is in the public domain. NIST assumes no +# responsibility whatsoever for its use by other parties, and makes +# no guarantees, expressed or implied, about its quality, +# reliability, or any other characteristic. +# +# We would appreciate acknowledgement if the software is used. + +SHELL := /bin/bash + +top_srcdir := .. + +all: \ + InvestigativeAction.json \ + ArchiveFile.json + +ArchiveFile.json: \ + $(top_srcdir)/.venv.done.log \ + $(top_srcdir)/src/generate_single_stub_json.py \ + $(top_srcdir)/var/facet_cardinalities.ttl + rm -f __$@ _$@ + source $(top_srcdir)/venv/bin/activate \ + && python $(top_srcdir)/src/generate_single_stub_json.py \ + --debug \ + __$@ \ + https://ontology.unifiedcyberontology.org/uco/observable/ArchiveFile \ + $(top_srcdir)/var/facet_cardinalities.ttl + python3 -m json.tool \ + __$@ \ + _$@ + rm __$@ + mv _$@ $@ + +InvestigativeAction.json: \ + $(top_srcdir)/.venv.done.log \ + $(top_srcdir)/src/generate_single_stub_json.py \ + $(top_srcdir)/var/facet_cardinalities.ttl + rm -f __$@ _$@ + source $(top_srcdir)/venv/bin/activate \ + && python $(top_srcdir)/src/generate_single_stub_json.py \ + --debug \ + __$@ \ + https://ontology.caseontology.org/case/investigation/InvestigativeAction \ + $(top_srcdir)/var/facet_cardinalities.ttl + python3 -m json.tool \ + __$@ \ + _$@ + rm __$@ + mv _$@ $@ + +check: \ + all + +clean: + @rm -f \ + *.json diff --git a/var/Makefile b/var/Makefile new file mode 100644 index 0000000..bd4724c --- /dev/null +++ b/var/Makefile @@ -0,0 +1,38 @@ +#!/usr/bin/make -f + +# Portions of this file contributed by NIST are governed by the +# following statement: +# +# This software was developed at the National Institute of Standards +# and Technology by employees of the Federal Government in the course +# of their official duties. Pursuant to title 17 Section 105 of the +# United States Code this software is not subject to copyright +# protection and is in the public domain. NIST assumes no +# responsibility whatsoever for its use by other parties, and makes +# no guarantees, expressed or implied, about its quality, +# reliability, or any other characteristic. +# +# We would appreciate acknowledgement if the software is used. + +SHELL := /bin/bash + +top_srcdir := .. + +all: \ + facet_cardinalities.ttl + +check: \ + all + +clean: + @rm -f \ + _* \ + *.ttl + +facet_cardinalities.ttl: \ + $(top_srcdir)/.venv.done.log \ + $(top_srcdir)/src/facet_cardinalities_ttl.py + source $(top_srcdir)/venv/bin/activate \ + && python3 $(top_srcdir)/src/facet_cardinalities_ttl.py \ + _$@ + mv _$@ $@ From 183dc1d6f5decc25346a88b9c35877c7ac7fdab0 Mon Sep 17 00:00:00 2001 From: Alex Nelson Date: Wed, 18 Sep 2024 17:57:11 -0400 Subject: [PATCH 2/9] Add Turtle formatting to pre-commit Signed-off-by: Alex Nelson --- .gitignore | 1 + .pre-commit-config.yaml | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/.gitignore b/.gitignore index 5d9c61b..ca2f4d4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +*.jar *.swp .*.done.log .DS_Store diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4977b3b..4ab30cb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,4 +1,10 @@ repos: + - repo: https://github.com/casework/rdf-toolkit-action + rev: 2.0.3 + hooks: + - id: rdf-toolkit-normalizer + args: + - --autofix - repo: https://github.com/psf/black rev: 24.8.0 hooks: From d7bc69b516c5386d74739b936b323613a2b91257 Mon Sep 17 00:00:00 2001 From: Alex Nelson Date: Wed, 18 Sep 2024 17:58:01 -0400 Subject: [PATCH 3/9] Generate Make-managed files Signed-off-by: Alex Nelson --- tests/ArchiveFile.json | 46 ++ tests/InvestigativeAction.json | 36 ++ var/facet_cardinalities.ttl | 833 +++++++++++++++++++++++++++++++++ 3 files changed, 915 insertions(+) create mode 100644 tests/ArchiveFile.json create mode 100644 tests/InvestigativeAction.json create mode 100644 var/facet_cardinalities.ttl diff --git a/tests/ArchiveFile.json b/tests/ArchiveFile.json new file mode 100644 index 0000000..1730d99 --- /dev/null +++ b/tests/ArchiveFile.json @@ -0,0 +1,46 @@ +{ + "@context": { + "case-investigation": "https://ontology.caseontology.org/case/investigation/", + "kb": "http://example.org/kb/", + "uco-core": "https://ontology.unifiedcyberontology.org/uco/core/", + "uco-observable": "https://ontology.unifiedcyberontology.org/uco/observable/", + "xsd": "http://www.w3.org/2001/XMLSchema#" + }, + "@id": "kb:ArchiveFile-1", + "@type": "uco-observable:ArchiveFile", + "case-investigation:wasDerivedFrom": [], + "uco-core:createdBy": null, + "uco-core:description": [], + "uco-core:externalReference": [], + "uco-core:hasFacet": [ + { + "@id": "kb:ArchiveFileFacet-1", + "@type": "uco-observable:ArchiveFileFacet", + "uco-observable:archiveType": null, + "uco-observable:comment": null, + "uco-observable:version": null + }, + { + "@id": "kb:FileFacet-1", + "@type": "uco-observable:FileFacet", + "uco-observable:accessedTime": null, + "uco-observable:allocationStatus": null, + "uco-observable:extension": null, + "uco-observable:fileName": [], + "uco-observable:filePath": [], + "uco-observable:isDirectory": [], + "uco-observable:metadataChangeTime": null, + "uco-observable:modifiedTime": null, + "uco-observable:observableCreatedTime": null, + "uco-observable:sizeInBytes": null + } + ], + "uco-core:modifiedTime": [], + "uco-core:name": null, + "uco-core:objectCreatedTime": null, + "uco-core:objectMarking": [], + "uco-core:specVersion": null, + "uco-core:tag": [], + "uco-observable:hasChanged": null, + "uco-observable:state": null +} diff --git a/tests/InvestigativeAction.json b/tests/InvestigativeAction.json new file mode 100644 index 0000000..feb70db --- /dev/null +++ b/tests/InvestigativeAction.json @@ -0,0 +1,36 @@ +{ + "@context": { + "case-investigation": "https://ontology.caseontology.org/case/investigation/", + "kb": "http://example.org/kb/", + "uco-action": "https://ontology.unifiedcyberontology.org/uco/action/", + "uco-core": "https://ontology.unifiedcyberontology.org/uco/core/", + "xsd": "http://www.w3.org/2001/XMLSchema#" + }, + "@id": "kb:InvestigativeAction-1", + "@type": "case-investigation:InvestigativeAction", + "case-investigation:wasDerivedFrom": [], + "case-investigation:wasInformedBy": [], + "uco-action:actionCount": null, + "uco-action:actionStatus": null, + "uco-action:endTime": null, + "uco-action:environment": null, + "uco-action:error": [], + "uco-action:instrument": [], + "uco-action:location": [], + "uco-action:object": [], + "uco-action:participant": [], + "uco-action:performer": null, + "uco-action:result": [], + "uco-action:startTime": null, + "uco-action:subaction": [], + "uco-core:createdBy": null, + "uco-core:description": [], + "uco-core:externalReference": [], + "uco-core:hasFacet": [], + "uco-core:modifiedTime": [], + "uco-core:name": null, + "uco-core:objectCreatedTime": null, + "uco-core:objectMarking": [], + "uco-core:specVersion": null, + "uco-core:tag": [] +} diff --git a/var/facet_cardinalities.ttl b/var/facet_cardinalities.ttl new file mode 100644 index 0000000..5bc1cbd --- /dev/null +++ b/var/facet_cardinalities.ttl @@ -0,0 +1,833 @@ +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix xsd: . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + + + rdfs:subClassOf [ + a owl:Restriction ; + owl:onProperty ; + owl:onClass ; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger ; + ] ; + . + From 27d82b2d76a7a8f3629e44ceb377088e30bef076 Mon Sep 17 00:00:00 2001 From: Alex Nelson Date: Wed, 18 Sep 2024 18:12:42 -0400 Subject: [PATCH 4/9] Check doctests Signed-off-by: Alex Nelson --- Makefile | 10 ++++++++++ requirements.txt | 1 + 2 files changed, 11 insertions(+) diff --git a/Makefile b/Makefile index 7683aae..7a0a9cf 100644 --- a/Makefile +++ b/Makefile @@ -29,6 +29,7 @@ all: \ .PHONY: \ all-tests \ all-var \ + check-pytest \ check-supply-chain \ check-supply-chain-pre-commit @@ -90,8 +91,17 @@ all-var: \ check: \ .venv-pre-commit/var/.pre-commit-built.log \ + check-pytest \ all-tests +check-pytest: \ + .venv.done.log + source venv/bin/activate \ + && pytest \ + --doctest-modules \ + --log-level=DEBUG \ + $(py_srcfiles) + check-supply-chain: \ check-supply-chain-pre-commit diff --git a/requirements.txt b/requirements.txt index 9aee9ff..da096e2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ PyLD case-utils mypy +pytest From f60f47be5cb5d9c4b0636c8c92a56435f8f97e83 Mon Sep 17 00:00:00 2001 From: Alex Nelson Date: Thu, 19 Sep 2024 10:28:15 -0400 Subject: [PATCH 5/9] Revise type syntax to support Python 3.9 Signed-off-by: Alex Nelson --- src/generate_single_stub_json.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/generate_single_stub_json.py b/src/generate_single_stub_json.py index 6e20577..10b8011 100644 --- a/src/generate_single_stub_json.py +++ b/src/generate_single_stub_json.py @@ -18,7 +18,7 @@ import importlib.resources import json import logging -from typing import Dict, List, Optional, Set +from typing import Dict, List, Optional, Set, Union import case_utils.ontology import pyld # type: ignore @@ -48,7 +48,8 @@ # JSON type via: # https://github.com/python/typing/issues/182#issuecomment-1320974824 -JSON = Dict[str, "JSON"] | List["JSON"] | str | int | float | bool | None +# Union is needed instead of '|' operator before Python 3.10. +JSON = Union[Dict[str, "JSON"], List["JSON"], str, int, float, bool, None] NS_KB = Namespace("http://example.org/kb/") From 888302f7904d987e532f42c63cb16560f4e7d1f4 Mon Sep 17 00:00:00 2001 From: Alex Nelson Date: Thu, 19 Sep 2024 10:30:33 -0400 Subject: [PATCH 6/9] Add smoke tests to CI Signed-off-by: Alex Nelson --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a336193..885e87d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,3 +42,5 @@ jobs: run: | pip -q install pre-commit pre-commit run --all-files + - name: Run tests + run: make PYTHON3=python check From a30870ee3ff60b6429e3f03c624d8805fa7fcbb1 Mon Sep 17 00:00:00 2001 From: Alex Nelson Date: Thu, 19 Sep 2024 10:31:16 -0400 Subject: [PATCH 7/9] Add type review to supply chain check Signed-off-by: Alex Nelson --- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 7a0a9cf..c7c657e 100644 --- a/Makefile +++ b/Makefile @@ -103,7 +103,8 @@ check-pytest: \ $(py_srcfiles) check-supply-chain: \ - check-supply-chain-pre-commit + check-supply-chain-pre-commit \ + .mypy.done.log # Update pre-commit configuration and use the updated config file to # review code. Only have Make exit if 'pre-commit run' modifies files. From c3a7a6763d136f265ad1826bdc6de7a4cd4ec3e3 Mon Sep 17 00:00:00 2001 From: Alex Nelson Date: Thu, 19 Sep 2024 10:41:16 -0400 Subject: [PATCH 8/9] Review owl:cardinality and owl:maxCardinality Signed-off-by: Alex Nelson --- src/generate_single_stub_json.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/generate_single_stub_json.py b/src/generate_single_stub_json.py index 10b8011..ec54b3f 100644 --- a/src/generate_single_stub_json.py +++ b/src/generate_single_stub_json.py @@ -120,6 +120,18 @@ def resolve_max_cardinality( ;\ .\ \ +ex:D\ + a owl:Class ;\ + rdfs:subClassOf\ + ex:B ,\ + [\ + a owl:Restriction ;\ + owl:onProperty ex:foo ;\ + owl:cardinality "1"^^xsd:nonNegativeInteger ;\ + ]\ + ;\ + .\ +\ ex:foo\ a owl:AnnotationProperty ;\ .\ @@ -132,6 +144,8 @@ def resolve_max_cardinality( 3 >>> resolve_max_cardinality(g, ns_ex["C"], ns_ex["foo"]) 2 + >>> resolve_max_cardinality(g, ns_ex["D"], ns_ex["foo"]) + 1 """ max_counts: Set[int] = set() query = """\ @@ -151,8 +165,18 @@ def resolve_max_cardinality( ?nRestriction a owl:Restriction ; owl:onProperty ?nProperty ; - owl:maxCardinality ?lMaxCount ; . + { + ?nRestriction + owl:cardinality ?lMaxCount ; + . + } + UNION + { + ?nRestriction + owl:maxCardinality ?lMaxCount ; + . + } } } """ From bfb94f38561dc4c4c119a8a3c91c0a3ec9e74e47 Mon Sep 17 00:00:00 2001 From: Alex Nelson Date: Thu, 19 Sep 2024 10:56:15 -0400 Subject: [PATCH 9/9] Normalize generated file on pre- or post-commit re-runs Signed-off-by: Alex Nelson --- var/Makefile | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/var/Makefile b/var/Makefile index bd4724c..9e54179 100644 --- a/var/Makefile +++ b/var/Makefile @@ -32,7 +32,17 @@ clean: facet_cardinalities.ttl: \ $(top_srcdir)/.venv.done.log \ $(top_srcdir)/src/facet_cardinalities_ttl.py + rm -f __$@ _$@ source $(top_srcdir)/venv/bin/activate \ && python3 $(top_srcdir)/src/facet_cardinalities_ttl.py \ - _$@ + __$@ + # Normalize if normalizing jar has already been downloaded from pre-commit. + test ! -r $(top_srcdir)/rdf-toolkit.jar \ + || java -jar $(top_srcdir)/rdf-toolkit.jar \ + --inline-blank-nodes \ + --source __$@ \ + --source-format turtle \ + --target _$@ \ + --target-format turtle + if [ -r _$@ ]; then rm __$@ ; else mv __$@ _$@ ; fi mv _$@ $@