Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add and test single-stub generator script and Facet-by-IRI-pattern support #5

Merged
merged 9 commits into from
Sep 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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