Skip to content

Commit

Permalink
Merge pull request #5 from casework/add_new_single_stub_generator
Browse files Browse the repository at this point in the history
Add and test single-stub generator script and Facet-by-IRI-pattern support
  • Loading branch information
kchason authored Sep 19, 2024
2 parents 7871491 + bfb94f3 commit 4d834e2
Show file tree
Hide file tree
Showing 12 changed files with 1,683 additions and 4 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,5 @@ jobs:
run: |
pip -q install pre-commit
pre-commit run --all-files
- name: Run tests
run: make PYTHON3=python check
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
*.jar
*.swp
.*.done.log
.DS_Store
Expand Down
6 changes: 6 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -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:
Expand Down
48 changes: 44 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,17 @@ 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-pytest \
check-supply-chain \
check-supply-chain-pre-commit

Expand All @@ -37,6 +42,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:
Expand All @@ -60,11 +79,32 @@ 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 \
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
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.
Expand Down
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
PyLD
case-utils
mypy
pytest
114 changes: 114 additions & 0 deletions src/facet_cardinalities_ttl.py
Original file line number Diff line number Diff line change
@@ -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: <http://www.w3.org/2002/07/owl#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX uco-core: <https://ontology.unifiedcyberontology.org/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: <http://www.w3.org/2002/07/owl#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX uco-core: <https://ontology.unifiedcyberontology.org/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()
Loading

0 comments on commit 4d834e2

Please sign in to comment.