From 268c9a69b21d510141567dcf0d8dafee702c5bbd Mon Sep 17 00:00:00 2001 From: Arthit Suriyawongkul Date: Sat, 27 Jun 2026 22:36:35 +0100 Subject: [PATCH 01/40] Add cross-version Protocols Signed-off-by: Arthit Suriyawongkul --- src/shacl2code/lang/python.py | 20 + .../lang/templates/python/__init__.py.j2 | 3 + .../lang/templates/python/model.py.j2 | 3 + .../lang/templates/python/model.pyi.j2 | 1 + .../lang/templates/python/protocols.py.j2 | 107 +++ tests/data/model/test-v2.ttl | 624 ++++++++++++++++++ tests/test_python.py | 258 ++++++++ 7 files changed, 1016 insertions(+) create mode 100644 src/shacl2code/lang/templates/python/protocols.py.j2 create mode 100644 tests/data/model/test-v2.ttl diff --git a/src/shacl2code/lang/python.py b/src/shacl2code/lang/python.py index 29521e23..ab230066 100644 --- a/src/shacl2code/lang/python.py +++ b/src/shacl2code/lang/python.py @@ -60,6 +60,11 @@ } +def protocol_name(cls): + """Protocol name for a class — same as the class name (identity mapping).""" + return varname(*cls.clsname) + + def varname(*name): """Make a valid Python variable name.""" name = "_".join(name) @@ -92,6 +97,7 @@ def __init__(self, args): self.__output = args.output self.__use_slots = args.use_slots self.__include_main = args.include_main == "yes" + self.__protocols = args.use_protocols == "yes" self.__version_str = args.version if args.version: self.__version = repr(convert_version_string(args.version)) @@ -126,6 +132,15 @@ def get_arguments(cls, parser): "--version", help="Specify model version", ) + parser.add_argument( + "--use-protocols", + choices=("yes", "no"), + default="no", + help=( + "Generate a protocols.py module with version-agnostic Protocol " + "types for every class. Default is '%(default)s'" + ), + ) def get_outputs(self): t = TEMPLATE_DIR / "python" @@ -141,9 +156,13 @@ def get_file(name): yield get_file("cmd.py") yield get_file("__main__.py") + if self.__protocols: + yield get_file("protocols.py") + def get_extra_env(self): return { "varname": varname, + "protocol_name": protocol_name, "DATATYPE_CLASSES": DATATYPE_CLASSES, "DATATYPE_PYTHON_TYPES": DATATYPE_PYTHON_TYPES, } @@ -158,6 +177,7 @@ def get_additional_render_args(self, model): return { "use_slots": use_slots, "include_main": self.__include_main, + "protocols": self.__protocols, "version_str": self.__version_str, "version": self.__version, } diff --git a/src/shacl2code/lang/templates/python/__init__.py.j2 b/src/shacl2code/lang/templates/python/__init__.py.j2 index 0af8e7e3..b76f9284 100644 --- a/src/shacl2code/lang/templates/python/__init__.py.j2 +++ b/src/shacl2code/lang/templates/python/__init__.py.j2 @@ -11,5 +11,8 @@ from .model import * # noqa: F401, F403 {%- if include_main %} from .cmd import main # noqa: F401, I100, I202 {%- endif %} +{%- if protocols %} +from . import protocols # noqa: F401, I100, I202 +{%- endif %} {{ '"' }}{{ '"' }}{{ '"' }}Format Guard""" # fmt on diff --git a/src/shacl2code/lang/templates/python/model.py.j2 b/src/shacl2code/lang/templates/python/model.py.j2 index b1c52de0..68d39758 100644 --- a/src/shacl2code/lang/templates/python/model.py.j2 +++ b/src/shacl2code/lang/templates/python/model.py.j2 @@ -3008,6 +3008,9 @@ class {{ varname(*class.clsname) }}( } {%- endif %} + def _protocol_{{ varname(*class.clsname) }}(self) -> None: + pass + {%- if class.properties %} PROPERTIES: ClassVar[List[ClassProp]] = [ {%- for prop in class.properties %} diff --git a/src/shacl2code/lang/templates/python/model.pyi.j2 b/src/shacl2code/lang/templates/python/model.pyi.j2 index 8906e671..8b83070d 100644 --- a/src/shacl2code/lang/templates/python/model.pyi.j2 +++ b/src/shacl2code/lang/templates/python/model.pyi.j2 @@ -465,6 +465,7 @@ class {{ varname(*class.clsname) }}( {%- endif %} **kwargs: Any ) -> None: ... + def _protocol_{{ varname(*class.clsname) }}(self) -> None: ... {%- if class.id_property %} {{ class.id_property }}: Optional[str] diff --git a/src/shacl2code/lang/templates/python/protocols.py.j2 b/src/shacl2code/lang/templates/python/protocols.py.j2 new file mode 100644 index 00000000..9494370b --- /dev/null +++ b/src/shacl2code/lang/templates/python/protocols.py.j2 @@ -0,0 +1,107 @@ +# {{ disclaimer }} +# +# SPDX-License-Identifier: {{ spdx_license }} +"""Version-agnostic Protocol types, one per generated class. + +Each Protocol is satisfied by the corresponding concrete class of any model +version that is backward-compatible with the baseline from which these Protocols +were generated. Use these types to write functions and classes that work across +multiple model versions and still pass strict static type checking. + +Object-reference and list properties expose a typed getter (covariant read) and +an ``Any`` setter (version-agnostic write). Scalar properties are plain +read-write attributes. + +Construct objects with a concrete version module; use these Protocols only for +annotations. +""" + +from __future__ import annotations + +from datetime import datetime +from typing import Any, Iterable, Iterator, Optional, Protocol, Set, Tuple + + +class SHACLObjectProtocol(Protocol): + """Version-agnostic view of the SHACLObject machinery base.""" + + def get_id(self) -> Optional[str]: ... + def set_id(self, value: Optional[str]) -> None: ... + def get_type(self) -> str: ... + def get_compact_type(self) -> Optional[str]: ... + def property_keys(self) -> Iterator[Tuple[Optional[str], str, Optional[str]]]: ... + def __getitem__(self, iri: str) -> Any: ... + def __setitem__(self, iri: str, value: Any) -> None: ... + + +class SHACLObjectSetProtocol(Protocol): + """Version-agnostic view of the SHACLObjectSet collection. + + Raw index attributes (``objects``, ``obj_by_id``, etc.) and version-coupled + methods (``encode``/``decode``, ``merge``) are omitted; use a concrete + version type for those. + """ + + def foreach(self) -> Iterable[SHACLObjectProtocol]: ... + + def foreach_type( + self, typ: str, *, match_subclass: bool = True + ) -> Iterable[SHACLObjectProtocol]: ... + + def find_by_id( + self, _id: str, default: Any = None + ) -> Optional[SHACLObjectProtocol]: ... + + def link(self) -> Set[str]: ... + def add(self, obj: Any) -> Any: ... + def remove(self, obj: Any) -> None: ... + def update(self, *others: Iterable[Any]) -> None: ... + def __contains__(self, item: Any) -> bool: ... + + +# fmt: off +"""Format Guard{{ '"' }}{{ '"' }}{{ '"' }} +# DOMAIN CLASSES +{% for class in classes %} + +class {{ protocol_name(class) }}( +{%- if class.parent_ids %} + {%- for id in class.parent_ids -%} + {{- protocol_name(classes.get(id)) }}{%- if not loop.last -%}, {% endif -%} + {%- endfor -%} +{%- else -%} + SHACLObjectProtocol +{%- endif -%} +, Protocol): +{%- if class.comment %} + {{ '"' }}{{ '"' }}{{ '"' }} + {%- for l in class.comment.split("\n") %} + {{ l.rstrip() }} + {%- endfor %} + {{ '"' }}{{ '"' }}{{ '"' }} +{%- endif %} + + def _protocol_{{ protocol_name(class) }}(self) -> None: ... + {%- if class.id_property %} + {{ class.id_property }}: Optional[str] + {%- endif %} + {%- for prop in class.properties %} + {%- set is_list = prop.max_count is none or prop.max_count != 1 %} + {%- set has_ref = prop.class_id and not prop.enum_values %} + {%- if prop.enum_values %} + {%- set ptype = "str" %} + {%- elif prop.class_id %} + {%- set ptype = "Any" %} + {%- else %} + {%- set ptype = DATATYPE_PYTHON_TYPES[prop.datatype] %} + {%- endif %} + {%- if is_list or has_ref %} + {{ varname(prop.varname) }}: Any + {%- else %} + {{ varname(prop.varname) }}: Optional[{{ ptype }}] + {%- endif %} + {%- endfor %} +{% endfor %} + +{{ '"' }}{{ '"' }}{{ '"' }}Format Guard""" +# fmt: on diff --git a/tests/data/model/test-v2.ttl b/tests/data/model/test-v2.ttl new file mode 100644 index 00000000..15327982 --- /dev/null +++ b/tests/data/model/test-v2.ttl @@ -0,0 +1,624 @@ +# Backward-compatible extension of test.ttl for cross-version Protocol tests. +# Adds: parent-class/v2-new-prop (new optional scalar) and test-another-class +# (same property set as test-class, for discriminator testing). +@base . +@prefix rdf: . +@prefix rdfs: . +@prefix sh: . +@prefix owl: . +@prefix xsd: . +@prefix sh-to-code: . + + a sh:NodeShape, owl:Class ; + rdfs:subClassOf ; + rdfs:comment "Derived class that sorts before the parent to test ordering" + . + + a sh:NodeShape, owl:Class ; + rdfs:comment "The parent class" ; + sh:property [ + sh:datatype xsd:string ; + sh:path ; + sh:maxCount 1 + ] + . + + + a sh:NodeShape, owl:Class ; + rdfs:subClassOf ; + rdfs:comment "The test class" ; + sh:property [ + sh:datatype xsd:string ; + sh:path + ], + [ + sh:path + ], + [ + sh:datatype xsd:string ; + sh:path ; + sh:maxCount 1 + ], + [ + sh:datatype xsd:string ; + sh:name "named_property" ; + sh:path ; + sh:maxCount 1 + ], + [ + sh:datatype xsd:dateTime ; + sh:path ; + sh:maxCount 1 + ], + [ + sh:datatype xsd:dateTime ; + sh:path ; + ], + [ + sh:datatype xsd:dateTimeStamp ; + sh:path ; + sh:maxCount 1 + ], + [ + sh:datatype xsd:positiveInteger ; + sh:path ; + sh:maxCount 1 + ], + [ + sh:datatype xsd:nonNegativeInteger ; + sh:path ; + sh:maxCount 1 + ], + [ + sh:datatype xsd:integer ; + sh:path ; + sh:maxCount 1 + ], + [ + sh:datatype xsd:anyURI ; + sh:path ; + sh:maxCount 1 + ], + [ + sh:datatype xsd:boolean ; + sh:path ; + sh:maxCount 1 + ], + [ + sh:datatype xsd:decimal ; + sh:path ; + sh:maxCount 1 + ], + [ + sh:class ; + sh:path ; + sh:maxCount 1 + ], + [ + sh:path ; + sh:maxCount 1 + ], + [ + sh:class ; + sh:path ; + ], + [ + sh:class ; + sh:path ; + sh:maxCount 1 ; + sh:in ( + + + + + ) + ], + [ + sh:class ; + sh:path ; + sh:in ( + + + + + ) + ], + [ + sh:path ; + sh:maxCount 1 ; + sh:in ( + + + + + ) + ], + [ + sh:datatype xsd:string ; + sh:pattern "^foo\\d" ; + sh:path ; + sh:maxCount 1 + + ], + [ + sh:datatype xsd:string ; + sh:pattern "^foo\\d" ; + sh:path ; + ], + [ + sh:datatype xsd:string ; + sh:path ; + sh:maxCount 1 + ], + [ + sh:datatype xsd:string ; + sh:path ; + sh:maxCount 1 + ], + [ + sh:datatype xsd:dateTime ; + sh:path ; + sh:maxCount 1 ; + sh:pattern "^\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\d\\+01:00$" + ], + [ + sh:datatype xsd:dateTimeStamp ; + sh:path ; + sh:maxCount 1 ; + sh:pattern "^\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\dZ$" + ], + [ + sh:class ; + sh:path ; + sh:maxCount 1 + ], + [ + sh:path ; + sh:datatype xsd:string + ], + [ + sh:path ; + sh:name "split" ; + sh:maxCount 1 + ] + . + + a owl:NamedIndividual, ; + rdfs:label "A named individual of the test class" + . + + a sh:NodeShape, owl:Class ; + rdfs:subClassOf ; + sh:property [ + sh:datatype xsd:string ; + sh:path ; + sh:maxCount 1 ; + sh:minCount 1 + ], + [ + sh:datatype xsd:string ; + sh:path ; + sh:minCount 1 ; + sh:maxCount 2 + ] + . + + a sh:NodeShape, owl:Class ; + rdfs:subClassOf ; + rdfs:comment "A class derived from test-class" ; + sh:property [ + sh:datatype xsd:string ; + sh:path ; + sh:maxCount 1 + ] + . + + a sh:NodeShape, owl:Class ; + rdfs:comment "Another class" + . + + a sh:NodeShape, owl:DeprecatedClass ; + rdfs:subClassOf ; + rdfs:comment "A deprecated class" ; + sh:property [ + sh:datatype: xsd:string ; + sh:path ; + sh:maxCount 1 + ] + . + + a owl:DeprecatedProperty ; + rdfs:comment "A deprecated property" ; + rdfs:range xsd:string + . + + a rdf:Property ; + rdfs:comment "A string list property" ; + . + + a rdf:Property ; + rdfs:comment "A string list property with no sh:datatype" ; + rdfs:range xsd:string + . + + a rdf:Property ; + rdfs:comment "A scalar string propery" ; + rdfs:range xsd:string + . + + a rdf:Property ; + rdfs:comment "A required scalar string property" ; + rdfs:range xsd:string + . + + a rdf:Property ; + rdfs:comment "A required string list property" ; + rdfs:range xsd:string + . + + a rdf:Property ; + rdfs:comment "A named property"; + rdfs:range xsd:string + . + + a rdf:Property ; + rdfs:comment "A scalar datetime property"; + rdfs:range xsd:dateTime + . + + a rdf:Property ; + rdfs:comment "A datetime list property" ; + rdfs:range xsd:dateTime + . + + a rdf:Property ; + rdfs:comment "A scalar dateTimeStamp property"; + rdfs:range xsd:dateTimeStamp + . + + a rdf:Property ; + rdfs:comment "A positive integer" ; + rdfs:range xsd:positiveInteger + . + + a rdf:Property ; + rdfs:comment "a non-negative integer" ; + rdfs:range xsd:nonNegativeInteger + . + + a rdf:Property ; + rdfs:comment "a non-negative integer" ; + rdfs:range xsd:integer + . + + a rdf:Property ; + rdfs:comment "a URI" ; + rdfs:range xsd:anyURI + . + + a rdf:Property ; + rdfs:comment "a boolean property" ; + rdfs:range xsd:boolean + . + + a rdf:Property ; + rdfs:comment "a float property" ; + rdfs:range xsd:decimal + . + + a rdf:Property ; + rdfs:comment "A test-class property" ; + rdfs:range + . + + a rdf:Property ; + rdfs:comment "A test-class property with no sh:class" ; + rdfs:range + . + + a rdf:Property ; + rdfs:comment "A test-class list property" ; + rdfs:range + . + + a rdf:Property ; + rdfs:comment "A enum property" ; + rdfs:range + . + + a rdf:Property ; + rdfs:comment "A enum list property" ; + rdfs:range + . + + a rdf:Property ; + rdfs:comment "A enum property with no sh:class" ; + rdfs:range + . + + a rdf:Property ; + rdfs:comment "A regex validated string" ; + rdfs:range xsd:string + . + + a rdf:Property ; + rdfs:comment "A regex validated string list" ; + rdfs:range xsd:string + . + + a rdf:Property ; + rdfs:comment "A split string property" ; + rdfs:range xsd:string + . + + a rdf:Property ; + rdfs:comment "A property that is a keyword" ; + rdfs:range xsd:string + . + + a rdf:Property ; + rdfs:comment "A property that conflicts with an existing SHACLObject property" ; + rdfs:range xsd:string + . + + a rdf:Property ; + rdfs:comment "A regex dateTime" ; + rdfs:range xsd:dateTime + . + + a rdf:Property ; + rdfs:comment "A regex dateTimeStamp" ; + rdfs:range xsd:dateTimeStamp + . + + a rdf:Property ; + rdfs:comment "A class with no shape" ; + rdfs:range + . + + a rdf:Property ; + rdfs:comment "A string property in a derived class" ; + rdfs:range xsd:string + . + + a owl:Class ; + rdfs:comment "A class that is not a nodeshape" + . + + a owl:Class ; + rdfs:comment "An enumerated type" + . + + a owl:NamedIndividual, ; + rdfs:label "foo" ; + rdfs:comment "The foo value of enumType" + . + + a owl:NamedIndividual, ; + rdfs:label "bar" ; + rdfs:comment "The bar value of enumType" + . + + a owl:NamedIndividual, ; + rdfs:comment "This value has no label" + . + + a ; + rdfs:comment "This value is not a named individual and won't appear in the output" + . + +# Classes to test links + + a sh:NodeShape, owl:Class ; + rdfs:comment "A class to test links" ; + sh:property [ + sh:class ; + sh:path ; + sh:maxCount 1 + ], + [ + sh:path ; + sh:maxCount 1 + ], + [ + sh:class ; + sh:path ; + ], + [ + sh:path ; + sh:maxCount 1 + ], + [ + sh:datatype xsd:string ; + sh:path ; + sh:maxCount 1 + ], + [ + sh:class ; + sh:path ; + sh:maxCount 1 + ] + . + +# Note: link-derived-class and link-derived-2-class should both have no +# properties to test an edge case in the go bindings + a sh:NodeShape, owl:Class ; + rdfs:subClassOf ; + rdfs:comment "A class derived from link-class" + . + + a sh:NodeShape, owl:Class ; + rdfs:subClassOf ; + rdfs:comment "A class derived from link-class" + . + + a rdf:Property ; + rdfs:comment "A link-class property" ; + rdfs:range + . + + a rdf:Property ; + rdfs:comment "A link-class property with no sh:class" ; + rdfs:range + . + + a rdf:Property ; + rdfs:comment "A link-class list property" ; + rdfs:range + . + + a rdf:Property ; + rdfs:comment "A link to an extensible-class" ; + rdfs:range + . + + a rdf:Property ; + rdfs:comment "Tag used to identify object for testing" ; + rdfs:range xsd:string + . + + a rdf:Property ; + rdfs:comment "A link to a derived class" ; + rdfs:range + . + + a rdf:Class, sh:NodeShape, owl:Class ; + rdfs:comment "A class with an ID alias" ; + sh-to-code:idPropertyName "testid" + . + + a rdf:Class, sh:NodeShape, owl:Class ; + rdfs:subClassOf ; + rdfs:comment "A class that inherits its idPropertyName from the parent" + . + + a rdf:Class, sh:NodeShape, owl:Class ; + rdfs:subClassOf ; + rdfs:comment "A class that must be a blank node" ; + sh:nodeKind sh:BlankNode + . + + a rdf:Class, sh:NodeShape, owl:Class ; + rdfs:subClassOf ; + rdfs:comment "A class that must be an IRI" ; + sh:nodeKind sh:IRI + . + + a rdf:Class, sh:NodeShape, owl:Class ; + rdfs:subClassOf ; + rdfs:comment "A class that can be either a blank node or an IRI" ; + sh:nodeKind sh:BlankNodeOrIRI + . + + a rdf:Class, sh:NodeShape, owl:Class ; + rdfs:subClassOf ; + rdfs:comment "A class that derives its nodeKind from parent" ; + . + + a rdf:Class, sh:NodeShape, owl:Class ; + rdfs:subClassOf ; + sh-to-code:isExtensible true ; + rdfs:comment "An extensible class" ; + sh:property [ + sh:datatype xsd:string ; + sh:path ; + sh:maxCount 1 ; + sh:minCount 0 + ], + [ + sh:datatype xsd:string ; + sh:path ; + sh:maxCount 1 ; + sh:minCount 1 + ] + . + + a rdf:Property ; + rdfs:comment "An extensible property" ; + rdfs:range xsd:string + . + + a rdf:Property ; + rdfs:comment "A required extensible property" ; + rdfs:range xsd:string + . + + a rdf:Class, sh:NodeShape, owl:Class ; + rdfs:comment "An Abstract class" ; + sh-to-code:isAbstract true + . + + a rdf:Class, sh:NodeShape, owl:Class, ; + rdfs:comment "An Abstract class using the SPDX type" + . + + a rdf:Class, sh:NodeShape, owl:Class ; + rdfs:comment: "An Abstract class using SHACL validation" ; + sh:property [ + sh:path rdf:type ; + sh:not [ sh:hasValue ] + ] . + + a rdf:Class, sh:NodeShape, owl:Class ; + rdfs:subClassOf ; + rdfs:comment "A concrete class" + . + + a rdf:Class, sh:NodeShape, owl:Class ; + rdfs:subClassOf ; + rdfs:comment "A concrete class" + . + + a rdf:Class, sh:NodeShape, owl:Class ; + rdfs:subClassOf ; + rdfs:comment "A concrete class" + . + + a rdf:Class, sh:NodeShape, owl:Class ; + rdfs:comment "A class with a mandatory abstract class" ; + sh:property [ + sh:class ; + sh:path ; + sh:minCount 1 ; + sh:maxCount 1 + ] + . + + a rdf:Property ; + rdfs:comment "A required abstract class property" ; + rdfs:range + . + + a rdf:Class, sh:NodeShape, owl:Class ; + sh-to-code:isExtensible true ; + sh-to-code:isAbstract true ; + rdfs:comment "An extensible abstract class" + . + + a rdf:Class, sh:NodeShape, owl:Class ; + rdfs:comment "A class that uses an abstract extensible class" ; + sh:property [ + sh:path ; + sh:minCount 1 ; + sh:maxCount 1 + ] + . + + a rdf:Property ; + rdfs:comment "A property that references and abstract extensible class" ; + rdfs:range + . + + a sh:NodeShape, owl:Class ; + rdfs:comment "Another class with the same own property set as test-class (for discriminator testing)" ; + sh:property [ + sh:class ; + sh:path ; + sh:maxCount 1 + ], + [ + sh:datatype xsd:string ; + sh:path ; + sh:maxCount 1 + ] + . diff --git a/tests/test_python.py b/tests/test_python.py index e31d0330..a78ac069 100644 --- a/tests/test_python.py +++ b/tests/test_python.py @@ -2276,3 +2276,261 @@ def test_version(model): assert model.VERSION_STRING == MODEL_VERSION assert model.VERSION == convert_version_string(MODEL_VERSION) + + +# --------------------------------------------------------------------------- +# Protocol generation tests +# --------------------------------------------------------------------------- + +TEST_V2_MODEL = THIS_DIR / "data" / "model" / "test-v2.ttl" + + +@pytest.fixture(scope="module") +def python_model_v1_protocols(tmp_path_factory, model_context_url): + """v1 model generated with --protocols yes.""" + tmp_directory = tmp_path_factory.mktemp("protocols_v1") + module_name = "pymodel_v1" + output_dir = tmp_directory / module_name + shacl2code_generate( + ["--input", TEST_MODEL, "--context", model_context_url], + ["--use-protocols", "yes"], + output_dir, + ) + (output_dir / "py.typed").touch() + return tmp_directory, module_name + + +@pytest.fixture(scope="module") +def python_model_v2_protocols(tmp_path_factory, model_context_url): + """v2 model (backward-compatible extension) generated with --protocols yes.""" + tmp_directory = tmp_path_factory.mktemp("protocols_v2") + module_name = "pymodel_v2" + output_dir = tmp_directory / module_name + shacl2code_generate( + ["--input", TEST_V2_MODEL, "--context", model_context_url], + ["--use-protocols", "yes"], + output_dir, + ) + (output_dir / "py.typed").touch() + return tmp_directory, module_name + + +class TestProtocolOutput: + """ + Tests for generated protocols.py — syntax, typing, and flake8. + """ + + def test_protocols_file_generated(self, tmp_path, model_context_url): + output_dir = tmp_path / "pymodel" + shacl2code_generate( + ["--input", TEST_MODEL, "--context", model_context_url], + ["--use-protocols", "yes"], + output_dir, + ) + assert (output_dir / "protocols.py").exists() + + def test_protocols_file_not_generated_by_default(self, tmp_path, model_context_url): + output_dir = tmp_path / "pymodel" + shacl2code_generate( + ["--input", TEST_MODEL, "--context", model_context_url], + [], + output_dir, + ) + assert not (output_dir / "protocols.py").exists() + + def test_mypy(self, tmp_path, model_context_url): + output_dir = tmp_path / "pymodel" + shacl2code_generate( + ["--input", TEST_MODEL, "--context", model_context_url], + ["--use-protocols", "yes"], + output_dir, + ) + (output_dir / "py.typed").touch() + subprocess.run(["mypy", output_dir], encoding="utf-8", check=True) + + def test_flake8(self, tmp_path, model_context_url): + output_dir = tmp_path / "pymodel" + shacl2code_generate( + ["--input", TEST_MODEL, "--context", model_context_url], + ["--use-protocols", "yes"], + output_dir, + ) + subprocess.run( + ["flake8", "--config", TOP_DIR / ".flake8", output_dir / "protocols.py"], + encoding="utf-8", + check=True, + ) + + +class TestProtocolConformance: + """ + Type-checked usage tests: concrete classes satisfy their protocols, + and the discriminator keeps structurally-identical classes distinct. + """ + + def test_conformance_mypy(self, python_model_v1_protocols, tmp_path): + """ + Every concrete class must satisfy its generated Protocol under mypy strict. + Verifies scalar read/write, object-ref typed read, Any-setter write. + """ + module_path, module_name = python_model_v1_protocols + env = os.environ.copy() + env["PYTHONPATH"] = str(module_path) + + script = tmp_path / "conformance.py" + script.write_text(textwrap.dedent(f"""\ + from typing import Any, Optional + import {module_name} + from {module_name} import protocols + + # Protocol conformance: assignment forces the static check. + a: protocols.test_class = {module_name}.test_class() + b: protocols.parent_class = {module_name}.parent_class() + + # Scalar read + write through protocol. + def set_scalar(o: protocols.test_class, v: Optional[str]) -> None: + o.test_class_string_scalar_prop = v + + # Object-ref Any read + Any-setter write through protocol. + def get_ref(o: protocols.test_class) -> Any: + return o.test_class_class_prop + + def set_ref(o: protocols.test_class, v: {module_name}.test_class) -> None: + o.test_class_class_prop = v + + # Version-agnostic function accepts any conforming class. + def get_scalar(o: protocols.test_class) -> Optional[str]: + result: Optional[str] = o.test_class_string_scalar_prop + return result + + get_scalar(a) + """)) + + r = subprocess.run( + ["mypy", "--strict", str(script)], + encoding="utf-8", + env=env, + capture_output=True, + ) + assert r.returncode == 0, r.stdout + r.stderr + + def test_discriminator_mypy(self, python_model_v1_protocols, tmp_path): + """ + The discriminator marker must prevent structurally-identical classes + (test_class and another_class share no own properties in v1) from + satisfying each other's protocol. + """ + module_path, module_name = python_model_v1_protocols + env = os.environ.copy() + env["PYTHONPATH"] = str(module_path) + + # test_another_class has no properties in v1 — structurally a subset of + # test_class. Without the discriminator, it would satisfy protocols.test_class. + script = tmp_path / "discriminator.py" + script.write_text(textwrap.dedent(f"""\ + import {module_name} + from {module_name} import protocols + + # This must be a type error: test_another_class != protocols.test_class + bad: protocols.test_class = {module_name}.test_another_class() # type: ignore[assignment] + """)) + + result = subprocess.run( + ["mypy", "--strict", str(script)], + encoding="utf-8", + env=env, + capture_output=True, + ) + # mypy must flag the assignment despite the type: ignore — we check that + # removing the ignore would produce an error by running without it. + script2 = tmp_path / "discriminator_no_ignore.py" + script2.write_text(textwrap.dedent(f"""\ + import {module_name} + from {module_name} import protocols + + bad: protocols.test_class = {module_name}.test_another_class() + """)) + result2 = subprocess.run( + ["mypy", "--strict", str(script2)], + encoding="utf-8", + env=env, + capture_output=True, + ) + assert result2.returncode != 0, ( + "Expected mypy to reject test_another_class as protocols.test_class " + "(discriminator should prevent it)" + ) + + +class TestProtocolCrossVersion: + """ + Cross-version: v2 concrete classes must satisfy v1-generated Protocols, + and the discriminator still prevents wrong-type assignments across versions. + """ + + def test_cross_version_mypy( + self, python_model_v1_protocols, python_model_v2_protocols, tmp_path + ): + """ + v2.test_class() satisfies v1.protocols.test_class (backward-compat). + v2.another_class() does NOT satisfy v1.protocols.test_class (discriminator). + """ + v1_path, v1_name = python_model_v1_protocols + v2_path, v2_name = python_model_v2_protocols + env = os.environ.copy() + env["PYTHONPATH"] = os.pathsep.join([str(v1_path), str(v2_path)]) + + script = tmp_path / "cross_version.py" + script.write_text(textwrap.dedent(f"""\ + from typing import Any, Optional + import {v1_name}, {v2_name} + from {v1_name} import protocols as v1p + + # v2 concrete satisfies v1 Protocol (additive-only minor version). + a: v1p.test_class = {v2_name}.test_class() + b: v1p.parent_class = {v2_name}.parent_class() + + # Scalar read through v1 protocol on v2 object. + def get_scalar(o: v1p.test_class) -> Optional[str]: + result: Optional[str] = o.test_class_string_scalar_prop + return result + + get_scalar(a) + + # Object-ref typed read through v1 protocol on v2 object. + def get_ref(o: v1p.test_class) -> Any: + return o.test_class_class_prop + + # Any-setter write through v1 protocol on v2 object. + def set_ref(o: v1p.test_class, v: {v2_name}.test_class) -> None: + o.test_class_class_prop = v + + # Discriminator: v2.another_class must NOT satisfy v1.protocols.test_class. + bad: v1p.test_class = {v2_name}.test_another_class() # type: ignore[assignment] + """)) + + subprocess.run( + ["mypy", "--strict", str(script)], + encoding="utf-8", + env=env, + check=True, + ) + + # Confirm the discriminator actually works (without the ignore). + script2 = tmp_path / "cross_version_bad.py" + script2.write_text(textwrap.dedent(f"""\ + import {v1_name}, {v2_name} + from {v1_name} import protocols as v1p + + bad: v1p.test_class = {v2_name}.test_another_class() + """)) + result = subprocess.run( + ["mypy", "--strict", str(script2)], + encoding="utf-8", + env=env, + capture_output=True, + ) + assert result.returncode != 0, ( + "Expected mypy to reject v2.another_class as v1.protocols.test_class " + "across versions" + ) From c748c4428df9bef75671d2fddb8e87d4c6a9d139 Mon Sep 17 00:00:00 2001 From: Arthit Suriyawongkul Date: Sat, 27 Jun 2026 22:50:07 +0100 Subject: [PATCH 02/40] sort command lines Signed-off-by: Arthit Suriyawongkul --- src/shacl2code/lang/python.py | 30 +++++++++---------- .../lang/templates/python/__init__.py.j2 | 2 +- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/shacl2code/lang/python.py b/src/shacl2code/lang/python.py index ab230066..b46be098 100644 --- a/src/shacl2code/lang/python.py +++ b/src/shacl2code/lang/python.py @@ -94,10 +94,10 @@ class PythonRender(JinjaTemplateRender): def __init__(self, args): super().__init__(args) + self.__include_main = args.include_main == "yes" self.__output = args.output + self.__use_protocols = args.use_protocols == "yes" self.__use_slots = args.use_slots - self.__include_main = args.include_main == "yes" - self.__protocols = args.use_protocols == "yes" self.__version_str = args.version if args.version: self.__version = repr(convert_version_string(args.version)) @@ -119,6 +119,15 @@ def get_arguments(cls, parser): default="yes", help="Generate a main function for the module. Default is '%(default)s'", ) + parser.add_argument( + "--use-protocols", + choices=("yes", "no"), + default="no", + help=( + "Generate a protocols.py module with version-agnostic Protocol " + "types for every class. Default is '%(default)s'" + ), + ) parser.add_argument( "--use-slots", choices=("auto", "yes", "no"), @@ -132,15 +141,6 @@ def get_arguments(cls, parser): "--version", help="Specify model version", ) - parser.add_argument( - "--use-protocols", - choices=("yes", "no"), - default="no", - help=( - "Generate a protocols.py module with version-agnostic Protocol " - "types for every class. Default is '%(default)s'" - ), - ) def get_outputs(self): t = TEMPLATE_DIR / "python" @@ -156,7 +156,7 @@ def get_file(name): yield get_file("cmd.py") yield get_file("__main__.py") - if self.__protocols: + if self.__use_protocols: yield get_file("protocols.py") def get_extra_env(self): @@ -175,9 +175,9 @@ def get_additional_render_args(self, model): else: use_slots = False return { - "use_slots": use_slots, "include_main": self.__include_main, - "protocols": self.__protocols, - "version_str": self.__version_str, + "use_protocols": self.__use_protocols, + "use_slots": use_slots, "version": self.__version, + "version_str": self.__version_str, } diff --git a/src/shacl2code/lang/templates/python/__init__.py.j2 b/src/shacl2code/lang/templates/python/__init__.py.j2 index b76f9284..6c0f257c 100644 --- a/src/shacl2code/lang/templates/python/__init__.py.j2 +++ b/src/shacl2code/lang/templates/python/__init__.py.j2 @@ -11,7 +11,7 @@ from .model import * # noqa: F401, F403 {%- if include_main %} from .cmd import main # noqa: F401, I100, I202 {%- endif %} -{%- if protocols %} +{%- if use_protocols %} from . import protocols # noqa: F401, I100, I202 {%- endif %} {{ '"' }}{{ '"' }}{{ '"' }}Format Guard""" From 258d3b8542062acea56af20d1fed8e708c8572d1 Mon Sep 17 00:00:00 2001 From: Arthit Suriyawongkul Date: Sun, 28 Jun 2026 04:20:31 +0100 Subject: [PATCH 03/40] Lazy import model Signed-off-by: Arthit Suriyawongkul --- src/shacl2code/lang/python.py | 2 +- .../lang/templates/python/__init__.py.j2 | 32 +++++++++++++++---- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/shacl2code/lang/python.py b/src/shacl2code/lang/python.py index b46be098..733ab377 100644 --- a/src/shacl2code/lang/python.py +++ b/src/shacl2code/lang/python.py @@ -94,8 +94,8 @@ class PythonRender(JinjaTemplateRender): def __init__(self, args): super().__init__(args) - self.__include_main = args.include_main == "yes" self.__output = args.output + self.__include_main = args.include_main == "yes" self.__use_protocols = args.use_protocols == "yes" self.__use_slots = args.use_slots self.__version_str = args.version diff --git a/src/shacl2code/lang/templates/python/__init__.py.j2 b/src/shacl2code/lang/templates/python/__init__.py.j2 index 6c0f257c..6043f5e1 100644 --- a/src/shacl2code/lang/templates/python/__init__.py.j2 +++ b/src/shacl2code/lang/templates/python/__init__.py.j2 @@ -4,15 +4,33 @@ # # SPDX-License-Identifier: {{ spdx_license }} -from .model import * # noqa: F401, F403 +from __future__ import annotations -# fmt: off -"""Format Guard{{ '"' }}{{ '"' }}{{ '"' }} +import importlib +from typing import Any, TYPE_CHECKING + +if TYPE_CHECKING: +{%- if use_protocols %} + from . import protocols # noqa: F401 +{%- endif %} {%- if include_main %} -from .cmd import main # noqa: F401, I100, I202 + from .cmd import main # noqa: F401 {%- endif %} + from .model import * # noqa: F401, F403 + + +def __getattr__(name: str) -> Any: + """Lazily load model content or submodules on first attribute access (PEP 562).""" {%- if use_protocols %} -from . import protocols # noqa: F401, I100, I202 + if name == "protocols": + return importlib.import_module(f"{__name__}.protocols") +{%- endif %} +{%- if include_main %} + if name == "main": + return importlib.import_module(f"{__name__}.cmd").main {%- endif %} -{{ '"' }}{{ '"' }}{{ '"' }}Format Guard""" -# fmt on + mod = importlib.import_module(f"{__name__}.model") + try: + return getattr(mod, name) + except AttributeError: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") From 94f1b349de0b3f3b7977a55723a5ff395586ec95 Mon Sep 17 00:00:00 2001 From: Arthit Suriyawongkul Date: Sun, 28 Jun 2026 09:47:56 +0100 Subject: [PATCH 04/40] Set Black target versions Signed-off-by: Arthit Suriyawongkul --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 6695adc9..00bd5eea 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -95,4 +95,5 @@ relative_files = true patch = ["subprocess"] [tool.black] +target-version = ["py38", "py39", "py310", "py311", "py312", "py313", "py314"] include = '(\.pyi?(\.j2)?$)|(main\.py\.j2$)' From 0c886d35ce05923d6cce7b0d535e09f8820fcf12 Mon Sep 17 00:00:00 2001 From: Arthit Suriyawongkul Date: Sun, 28 Jun 2026 09:54:39 +0100 Subject: [PATCH 05/40] Update Python version of lint CI to 3.14 Signed-off-by: Arthit Suriyawongkul --- .github/workflows/test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index bc055147..ac1d3cb3 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -65,7 +65,7 @@ jobs: - name: Setup Python uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: - python-version: "3.11" + python-version: "3.14" # should be the latest in Black's target-version - uses: psf/black@87928e6d6761a4a6d22250e1fee5601b3998086e # 26.5.1 - uses: py-actions/flake8@84ec6726560b6d5bd68f2a5bed83d62b52bb50ba # v2.3.0 - name: Install dependencies From 840ce86ff4dd872145ea09992a5c8587efee39ef Mon Sep 17 00:00:00 2001 From: Arthit Suriyawongkul Date: Sun, 28 Jun 2026 10:05:28 +0100 Subject: [PATCH 06/40] Add back format guard Signed-off-by: Arthit Suriyawongkul --- .github/workflows/test.yaml | 2 +- .../lang/templates/python/__init__.py.j2 | 15 ++++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index ac1d3cb3..e1ef2c41 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -65,7 +65,7 @@ jobs: - name: Setup Python uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: - python-version: "3.14" # should be the latest in Black's target-version + python-version: "3.14" # must matches the latest in Black's target-version - uses: psf/black@87928e6d6761a4a6d22250e1fee5601b3998086e # 26.5.1 - uses: py-actions/flake8@84ec6726560b6d5bd68f2a5bed83d62b52bb50ba # v2.3.0 - name: Install dependencies diff --git a/src/shacl2code/lang/templates/python/__init__.py.j2 b/src/shacl2code/lang/templates/python/__init__.py.j2 index 6043f5e1..fbfd40e0 100644 --- a/src/shacl2code/lang/templates/python/__init__.py.j2 +++ b/src/shacl2code/lang/templates/python/__init__.py.j2 @@ -10,13 +10,14 @@ import importlib from typing import Any, TYPE_CHECKING if TYPE_CHECKING: + from .model import * # noqa: F401, F403 + +# fmt: off +"""Format Guard{{ '"' }}{{ '"' }}{{ '"' }} {%- if use_protocols %} - from . import protocols # noqa: F401 -{%- endif %} -{%- if include_main %} - from .cmd import main # noqa: F401 +if TYPE_CHECKING: + from . import protocols # noqa: F401, I202 {%- endif %} - from .model import * # noqa: F401, F403 def __getattr__(name: str) -> Any: @@ -34,3 +35,7 @@ def __getattr__(name: str) -> Any: return getattr(mod, name) except AttributeError: raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + + +{{ '"' }}{{ '"' }}{{ '"' }}Format Guard""" +# fmt: on From 025cbdf07faf175112436f7c2d70b31d384343c3 Mon Sep 17 00:00:00 2001 From: Arthit Suriyawongkul Date: Sun, 28 Jun 2026 10:20:53 +0100 Subject: [PATCH 07/40] Fix comment and Black target-version Signed-off-by: Arthit Suriyawongkul --- .github/workflows/test.yaml | 3 ++- pyproject.toml | 4 +++- src/shacl2code/lang/templates/python/__init__.py.j2 | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index e1ef2c41..93870183 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -65,7 +65,8 @@ jobs: - name: Setup Python uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: - python-version: "3.14" # must matches the latest in Black's target-version + # Black in this Python version must support all target-versions listed in pyproject.toml + python-version: "3.14" - uses: psf/black@87928e6d6761a4a6d22250e1fee5601b3998086e # 26.5.1 - uses: py-actions/flake8@84ec6726560b6d5bd68f2a5bed83d62b52bb50ba # v2.3.0 - name: Install dependencies diff --git a/pyproject.toml b/pyproject.toml index 00bd5eea..dee8051e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -95,5 +95,7 @@ relative_files = true patch = ["subprocess"] [tool.black] -target-version = ["py38", "py39", "py310", "py311", "py312", "py313", "py314"] +# Note: "py314" can't be used as a target-version because Black 24.8.0 (latest +# supported version for Python 3.8) doesn't recognize it. +target-version = ["py38", "py39", "py310", "py311", "py312", "py313"] include = '(\.pyi?(\.j2)?$)|(main\.py\.j2$)' diff --git a/src/shacl2code/lang/templates/python/__init__.py.j2 b/src/shacl2code/lang/templates/python/__init__.py.j2 index fbfd40e0..64509598 100644 --- a/src/shacl2code/lang/templates/python/__init__.py.j2 +++ b/src/shacl2code/lang/templates/python/__init__.py.j2 @@ -21,7 +21,7 @@ if TYPE_CHECKING: def __getattr__(name: str) -> Any: - """Lazily load model content or submodules on first attribute access (PEP 562).""" + # Lazy attribute access: submodules and model content loaded on first use (PEP 562). {%- if use_protocols %} if name == "protocols": return importlib.import_module(f"{__name__}.protocols") From 5a288cea7464de689ca4da4b5f11bf952e17372a Mon Sep 17 00:00:00 2001 From: Arthit Suriyawongkul Date: Sun, 28 Jun 2026 10:29:27 +0100 Subject: [PATCH 08/40] Remove unused test Signed-off-by: Arthit Suriyawongkul --- src/shacl2code/lang/python.py | 2 +- tests/test_python.py | 28 ++++++---------------------- 2 files changed, 7 insertions(+), 23 deletions(-) diff --git a/src/shacl2code/lang/python.py b/src/shacl2code/lang/python.py index 733ab377..5d1b7d36 100644 --- a/src/shacl2code/lang/python.py +++ b/src/shacl2code/lang/python.py @@ -61,7 +61,7 @@ def protocol_name(cls): - """Protocol name for a class — same as the class name (identity mapping).""" + """Protocol name for a class - same as the class name (identity mapping).""" return varname(*cls.clsname) diff --git a/tests/test_python.py b/tests/test_python.py index a78ac069..fc3cc5f7 100644 --- a/tests/test_python.py +++ b/tests/test_python.py @@ -2317,7 +2317,7 @@ def python_model_v2_protocols(tmp_path_factory, model_context_url): class TestProtocolOutput: """ - Tests for generated protocols.py — syntax, typing, and flake8. + Tests for generated protocols.py - syntax, typing, and flake8. """ def test_protocols_file_generated(self, tmp_path, model_context_url): @@ -2424,39 +2424,23 @@ def test_discriminator_mypy(self, python_model_v1_protocols, tmp_path): env = os.environ.copy() env["PYTHONPATH"] = str(module_path) - # test_another_class has no properties in v1 — structurally a subset of - # test_class. Without the discriminator, it would satisfy protocols.test_class. + # test_another_class has no own properties in v1, making it structurally + # identical to test_class. Without the discriminator both would satisfy + # each other's protocol. script = tmp_path / "discriminator.py" script.write_text(textwrap.dedent(f"""\ import {module_name} from {module_name} import protocols - # This must be a type error: test_another_class != protocols.test_class - bad: protocols.test_class = {module_name}.test_another_class() # type: ignore[assignment] + bad: protocols.test_class = {module_name}.test_another_class() """)) - result = subprocess.run( ["mypy", "--strict", str(script)], encoding="utf-8", env=env, capture_output=True, ) - # mypy must flag the assignment despite the type: ignore — we check that - # removing the ignore would produce an error by running without it. - script2 = tmp_path / "discriminator_no_ignore.py" - script2.write_text(textwrap.dedent(f"""\ - import {module_name} - from {module_name} import protocols - - bad: protocols.test_class = {module_name}.test_another_class() - """)) - result2 = subprocess.run( - ["mypy", "--strict", str(script2)], - encoding="utf-8", - env=env, - capture_output=True, - ) - assert result2.returncode != 0, ( + assert result.returncode != 0, ( "Expected mypy to reject test_another_class as protocols.test_class " "(discriminator should prevent it)" ) From 2a7ae17019163da2a024e5decbf63a9c1574e9ef Mon Sep 17 00:00:00 2001 From: Arthit Suriyawongkul Date: Sun, 28 Jun 2026 11:11:22 +0100 Subject: [PATCH 09/40] Rename --use-protocols -> --include-protocols Follows --include-main pattern Signed-off-by: Arthit Suriyawongkul --- src/shacl2code/lang/python.py | 10 +++++----- .../lang/templates/python/__init__.py.j2 | 4 ++-- tests/test_python.py | 14 +++++++------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/shacl2code/lang/python.py b/src/shacl2code/lang/python.py index 5d1b7d36..7b882738 100644 --- a/src/shacl2code/lang/python.py +++ b/src/shacl2code/lang/python.py @@ -96,7 +96,7 @@ def __init__(self, args): super().__init__(args) self.__output = args.output self.__include_main = args.include_main == "yes" - self.__use_protocols = args.use_protocols == "yes" + self.__include_protocols = args.include_protocols == "yes" self.__use_slots = args.use_slots self.__version_str = args.version if args.version: @@ -120,11 +120,11 @@ def get_arguments(cls, parser): help="Generate a main function for the module. Default is '%(default)s'", ) parser.add_argument( - "--use-protocols", + "--include-protocols", choices=("yes", "no"), default="no", help=( - "Generate a protocols.py module with version-agnostic Protocol " + "Include a protocols.py module with version-agnostic Protocol " "types for every class. Default is '%(default)s'" ), ) @@ -156,7 +156,7 @@ def get_file(name): yield get_file("cmd.py") yield get_file("__main__.py") - if self.__use_protocols: + if self.__include_protocols: yield get_file("protocols.py") def get_extra_env(self): @@ -176,7 +176,7 @@ def get_additional_render_args(self, model): use_slots = False return { "include_main": self.__include_main, - "use_protocols": self.__use_protocols, + "include_protocols": self.__include_protocols, "use_slots": use_slots, "version": self.__version, "version_str": self.__version_str, diff --git a/src/shacl2code/lang/templates/python/__init__.py.j2 b/src/shacl2code/lang/templates/python/__init__.py.j2 index 64509598..3ef99961 100644 --- a/src/shacl2code/lang/templates/python/__init__.py.j2 +++ b/src/shacl2code/lang/templates/python/__init__.py.j2 @@ -14,7 +14,7 @@ if TYPE_CHECKING: # fmt: off """Format Guard{{ '"' }}{{ '"' }}{{ '"' }} -{%- if use_protocols %} +{%- if include_protocols %} if TYPE_CHECKING: from . import protocols # noqa: F401, I202 {%- endif %} @@ -22,7 +22,7 @@ if TYPE_CHECKING: def __getattr__(name: str) -> Any: # Lazy attribute access: submodules and model content loaded on first use (PEP 562). -{%- if use_protocols %} +{%- if include_protocols %} if name == "protocols": return importlib.import_module(f"{__name__}.protocols") {%- endif %} diff --git a/tests/test_python.py b/tests/test_python.py index fc3cc5f7..4c0df80a 100644 --- a/tests/test_python.py +++ b/tests/test_python.py @@ -2287,13 +2287,13 @@ def test_version(model): @pytest.fixture(scope="module") def python_model_v1_protocols(tmp_path_factory, model_context_url): - """v1 model generated with --protocols yes.""" + """v1 model generated with --include-protocols yes.""" tmp_directory = tmp_path_factory.mktemp("protocols_v1") module_name = "pymodel_v1" output_dir = tmp_directory / module_name shacl2code_generate( ["--input", TEST_MODEL, "--context", model_context_url], - ["--use-protocols", "yes"], + ["--include-protocols", "yes"], output_dir, ) (output_dir / "py.typed").touch() @@ -2302,13 +2302,13 @@ def python_model_v1_protocols(tmp_path_factory, model_context_url): @pytest.fixture(scope="module") def python_model_v2_protocols(tmp_path_factory, model_context_url): - """v2 model (backward-compatible extension) generated with --protocols yes.""" + """v2 model (backward-compatible extension) generated with --include-protocols yes.""" tmp_directory = tmp_path_factory.mktemp("protocols_v2") module_name = "pymodel_v2" output_dir = tmp_directory / module_name shacl2code_generate( ["--input", TEST_V2_MODEL, "--context", model_context_url], - ["--use-protocols", "yes"], + ["--include-protocols", "yes"], output_dir, ) (output_dir / "py.typed").touch() @@ -2324,7 +2324,7 @@ def test_protocols_file_generated(self, tmp_path, model_context_url): output_dir = tmp_path / "pymodel" shacl2code_generate( ["--input", TEST_MODEL, "--context", model_context_url], - ["--use-protocols", "yes"], + ["--include-protocols", "yes"], output_dir, ) assert (output_dir / "protocols.py").exists() @@ -2342,7 +2342,7 @@ def test_mypy(self, tmp_path, model_context_url): output_dir = tmp_path / "pymodel" shacl2code_generate( ["--input", TEST_MODEL, "--context", model_context_url], - ["--use-protocols", "yes"], + ["--include-protocols", "yes"], output_dir, ) (output_dir / "py.typed").touch() @@ -2352,7 +2352,7 @@ def test_flake8(self, tmp_path, model_context_url): output_dir = tmp_path / "pymodel" shacl2code_generate( ["--input", TEST_MODEL, "--context", model_context_url], - ["--use-protocols", "yes"], + ["--include-protocols", "yes"], output_dir, ) subprocess.run( From e9fcdc371e098618891c5d575f2419a96f1c7a53 Mon Sep 17 00:00:00 2001 From: Arthit Suriyawongkul Date: Wed, 1 Jul 2026 22:23:27 +0100 Subject: [PATCH 10/40] Add test for case that datetime is not used in protocol Signed-off-by: Arthit Suriyawongkul --- src/shacl2code/lang/python.py | 26 +++- .../lang/templates/python/protocols.py.j2 | 17 +- tests/data/no-datetime.ttl | 18 +++ tests/test_python.py | 146 ++++++++++++++++-- 4 files changed, 184 insertions(+), 23 deletions(-) create mode 100644 tests/data/no-datetime.ttl diff --git a/src/shacl2code/lang/python.py b/src/shacl2code/lang/python.py index 7b882738..40acf4f4 100644 --- a/src/shacl2code/lang/python.py +++ b/src/shacl2code/lang/python.py @@ -6,9 +6,11 @@ import keyword import re from pathlib import Path +from typing import Iterable from .common import JinjaTemplateRender from .lang import TEMPLATE_DIR, language +from ..model import Class from ..util import convert_version_string DATATYPE_CLASSES = { @@ -60,11 +62,6 @@ } -def protocol_name(cls): - """Protocol name for a class - same as the class name (identity mapping).""" - return varname(*cls.clsname) - - def varname(*name): """Make a valid Python variable name.""" name = "_".join(name) @@ -80,6 +77,23 @@ def varname(*name): return name +def protocols_use_datetime(classes: Iterable[Class]) -> bool: + """Whether any class has a plain (non-list, non-ref) datetime-typed property. + + Determines if protocols.py.j2 needs to import ``datetime`` -- mirrors the + scalar-property branch in that template exactly, so the import is only + emitted when it will actually be referenced (avoids flake8 F401). + """ + for cls in classes: + for prop in cls.properties: + is_list = prop.max_count is None or prop.max_count != 1 + has_ref = bool(prop.class_id) and not prop.enum_values + is_scalar = not (is_list or has_ref or prop.enum_values) + if is_scalar and DATATYPE_PYTHON_TYPES[prop.datatype] == "datetime": + return True + return False + + @language("python") class PythonRender(JinjaTemplateRender): """Render Python Language Bindings.""" @@ -162,7 +176,7 @@ def get_file(name): def get_extra_env(self): return { "varname": varname, - "protocol_name": protocol_name, + "protocols_use_datetime": protocols_use_datetime, "DATATYPE_CLASSES": DATATYPE_CLASSES, "DATATYPE_PYTHON_TYPES": DATATYPE_PYTHON_TYPES, } diff --git a/src/shacl2code/lang/templates/python/protocols.py.j2 b/src/shacl2code/lang/templates/python/protocols.py.j2 index 9494370b..b32decea 100644 --- a/src/shacl2code/lang/templates/python/protocols.py.j2 +++ b/src/shacl2code/lang/templates/python/protocols.py.j2 @@ -8,9 +8,12 @@ version that is backward-compatible with the baseline from which these Protocols were generated. Use these types to write functions and classes that work across multiple model versions and still pass strict static type checking. -Object-reference and list properties expose a typed getter (covariant read) and -an ``Any`` setter (version-agnostic write). Scalar properties are plain -read-write attributes. +Scalar properties are typed precisely and are plain read-write attributes. +Object-reference and list properties are typed ``Any`` for both read and write: +cross-module structural typing cannot resolve the circular reference between a +protocol class and the sibling protocol type its property would otherwise +return, so these properties fall back to ``Any``. Read them through the +concrete version type when a typed value is needed. Construct objects with a concrete version module; use these Protocols only for annotations. @@ -18,7 +21,7 @@ annotations. from __future__ import annotations -from datetime import datetime +{{"from datetime import datetime" if protocols_use_datetime(classes) else ""}} from typing import Any, Iterable, Iterator, Optional, Protocol, Set, Tuple @@ -64,10 +67,10 @@ class SHACLObjectSetProtocol(Protocol): # DOMAIN CLASSES {% for class in classes %} -class {{ protocol_name(class) }}( +class {{ varname(*class.clsname) }}( {%- if class.parent_ids %} {%- for id in class.parent_ids -%} - {{- protocol_name(classes.get(id)) }}{%- if not loop.last -%}, {% endif -%} + {{- varname(*classes.get(id).clsname) }}{%- if not loop.last -%}, {% endif -%} {%- endfor -%} {%- else -%} SHACLObjectProtocol @@ -81,7 +84,7 @@ class {{ protocol_name(class) }}( {{ '"' }}{{ '"' }}{{ '"' }} {%- endif %} - def _protocol_{{ protocol_name(class) }}(self) -> None: ... + def _protocol_{{ varname(*class.clsname) }}(self) -> None: ... {%- if class.id_property %} {{ class.id_property }}: Optional[str] {%- endif %} diff --git a/tests/data/no-datetime.ttl b/tests/data/no-datetime.ttl new file mode 100644 index 00000000..83a6cae8 --- /dev/null +++ b/tests/data/no-datetime.ttl @@ -0,0 +1,18 @@ +@base . +@prefix rdfs: . +@prefix sh: . +@prefix owl: . +@prefix xsd: . + +# A minimal model with no datetime-typed property. Used to guard against +# protocols.py.j2 emitting an unconditional `from datetime import datetime` +# that would be an unused import (flake8 F401) for models like this one. + + a rdfs:Class, sh:NodeShape, owl:Class ; + rdfs:comment "A class with only a string property, no datetime types" ; + sh:property [ + sh:datatype xsd:string ; + sh:path ; + sh:maxCount 1 + ] + . diff --git a/tests/test_python.py b/tests/test_python.py index 4c0df80a..a33b3499 100644 --- a/tests/test_python.py +++ b/tests/test_python.py @@ -13,6 +13,7 @@ import textwrap from datetime import datetime, timedelta, timezone from pathlib import Path +from typing import Iterable, Tuple import jsonschema @@ -22,6 +23,10 @@ import rdflib +from shacl2code.lang.python import protocols_use_datetime +from shacl2code.model import Class, Model +from shacl2code.urlcontext import UrlContext + from testfixtures import jsonvalidation, timetests THIS_FILE = Path(__file__) @@ -2283,10 +2288,40 @@ def test_version(model): # --------------------------------------------------------------------------- TEST_V2_MODEL = THIS_DIR / "data" / "model" / "test-v2.ttl" +NO_DATETIME_MODEL = DATA_DIR / "no-datetime.ttl" + + +def _load_classes(ttl_path: Path) -> Iterable[Class]: + """Parse a .ttl file in-process into Model.classes (no --context needed).""" + graph = rdflib.Graph() + graph.parse(ttl_path) + return Model(graph, UrlContext()).classes + + +class TestProtocolsUseDatetime: + """ + Direct, in-process unit tests for protocols_use_datetime(). Exercises both + branches without going through code generation, so coverage doesn't depend + on incidental property ordering in a generated model. + """ + + def test_true_when_datetime_property_present(self) -> None: + """ + TEST_MODEL has scalar datetime properties (and list/enum/ref properties + that sort before them), so this also exercises the "skip" continue + branch on the way to the True return. + """ + assert protocols_use_datetime(_load_classes(TEST_MODEL)) is True + + def test_false_when_no_datetime_property(self) -> None: + """NO_DATETIME_MODEL has only a plain string property.""" + assert protocols_use_datetime(_load_classes(NO_DATETIME_MODEL)) is False @pytest.fixture(scope="module") -def python_model_v1_protocols(tmp_path_factory, model_context_url): +def python_model_v1_protocols( + tmp_path_factory: pytest.TempPathFactory, model_context_url: str +) -> Tuple[Path, str]: """v1 model generated with --include-protocols yes.""" tmp_directory = tmp_path_factory.mktemp("protocols_v1") module_name = "pymodel_v1" @@ -2301,7 +2336,9 @@ def python_model_v1_protocols(tmp_path_factory, model_context_url): @pytest.fixture(scope="module") -def python_model_v2_protocols(tmp_path_factory, model_context_url): +def python_model_v2_protocols( + tmp_path_factory: pytest.TempPathFactory, model_context_url: str +) -> Tuple[Path, str]: """v2 model (backward-compatible extension) generated with --include-protocols yes.""" tmp_directory = tmp_path_factory.mktemp("protocols_v2") module_name = "pymodel_v2" @@ -2320,7 +2357,9 @@ class TestProtocolOutput: Tests for generated protocols.py - syntax, typing, and flake8. """ - def test_protocols_file_generated(self, tmp_path, model_context_url): + def test_protocols_file_generated( + self, tmp_path: Path, model_context_url: str + ) -> None: output_dir = tmp_path / "pymodel" shacl2code_generate( ["--input", TEST_MODEL, "--context", model_context_url], @@ -2329,7 +2368,9 @@ def test_protocols_file_generated(self, tmp_path, model_context_url): ) assert (output_dir / "protocols.py").exists() - def test_protocols_file_not_generated_by_default(self, tmp_path, model_context_url): + def test_protocols_file_not_generated_by_default( + self, tmp_path: Path, model_context_url: str + ) -> None: output_dir = tmp_path / "pymodel" shacl2code_generate( ["--input", TEST_MODEL, "--context", model_context_url], @@ -2338,7 +2379,7 @@ def test_protocols_file_not_generated_by_default(self, tmp_path, model_context_u ) assert not (output_dir / "protocols.py").exists() - def test_mypy(self, tmp_path, model_context_url): + def test_mypy(self, tmp_path: Path, model_context_url: str) -> None: output_dir = tmp_path / "pymodel" shacl2code_generate( ["--input", TEST_MODEL, "--context", model_context_url], @@ -2348,7 +2389,7 @@ def test_mypy(self, tmp_path, model_context_url): (output_dir / "py.typed").touch() subprocess.run(["mypy", output_dir], encoding="utf-8", check=True) - def test_flake8(self, tmp_path, model_context_url): + def test_flake8(self, tmp_path: Path, model_context_url: str) -> None: output_dir = tmp_path / "pymodel" shacl2code_generate( ["--input", TEST_MODEL, "--context", model_context_url], @@ -2361,6 +2402,38 @@ def test_flake8(self, tmp_path, model_context_url): check=True, ) + def test_flake8_no_datetime_properties(self, tmp_path: Path) -> None: + """ + protocols.py must not unconditionally import `datetime`. A model with + no datetime-typed property must not produce an unused import (F401). + """ + output_dir = tmp_path / "pymodel" + shacl2code_generate( + ["--input", NO_DATETIME_MODEL], + ["--include-protocols", "yes"], + output_dir, + ) + assert "import datetime" not in (output_dir / "protocols.py").read_text() + subprocess.run( + ["flake8", "--config", TOP_DIR / ".flake8", output_dir / "protocols.py"], + encoding="utf-8", + check=True, + ) + + def test_mypy_no_datetime_properties(self, tmp_path: Path) -> None: + """ + The generated package must still type-check when protocols.py omits + the `datetime` import. + """ + output_dir = tmp_path / "pymodel" + shacl2code_generate( + ["--input", NO_DATETIME_MODEL], + ["--include-protocols", "yes"], + output_dir, + ) + (output_dir / "py.typed").touch() + subprocess.run(["mypy", output_dir], encoding="utf-8", check=True) + class TestProtocolConformance: """ @@ -2368,7 +2441,9 @@ class TestProtocolConformance: and the discriminator keeps structurally-identical classes distinct. """ - def test_conformance_mypy(self, python_model_v1_protocols, tmp_path): + def test_conformance_mypy( + self, python_model_v1_protocols: Tuple[Path, str], tmp_path: Path + ) -> None: """ Every concrete class must satisfy its generated Protocol under mypy strict. Verifies scalar read/write, object-ref typed read, Any-setter write. @@ -2414,7 +2489,55 @@ def get_scalar(o: protocols.test_class) -> Optional[str]: ) assert r.returncode == 0, r.stdout + r.stderr - def test_discriminator_mypy(self, python_model_v1_protocols, tmp_path): + def test_base_protocol_conformance_mypy( + self, python_model_v1_protocols: Tuple[Path, str], tmp_path: Path + ) -> None: + """ + The hand-written SHACLObjectProtocol/SHACLObjectSetProtocol must be + satisfied by the real generated SHACLObject/SHACLObjectSet, not just by + per-class domain protocols. Guards against model.py.j2 changes to + SHACLObject/SHACLObjectSet (e.g. renaming property_keys, retyping + find_by_id's default, altering __contains__) silently breaking the + base protocols with no test catching it. + """ + module_path, module_name = python_model_v1_protocols + env = os.environ.copy() + env["PYTHONPATH"] = str(module_path) + + script = tmp_path / "base_conformance.py" + script.write_text(textwrap.dedent(f"""\ + from typing import Iterable + import {module_name} + from {module_name} import protocols + + # Protocol conformance: assignment forces the static check. + o: protocols.SHACLObjectProtocol = {module_name}.test_class() + s: protocols.SHACLObjectSetProtocol = {module_name}.SHACLObjectSet() + + # Version-agnostic function accepts any conforming object/set. + def get_id(obj: protocols.SHACLObjectProtocol) -> str: + return obj.get_type() + + def iter_objects( + objset: protocols.SHACLObjectSetProtocol, + ) -> Iterable[protocols.SHACLObjectProtocol]: + return objset.foreach() + + get_id(o) + iter_objects(s) + """)) + + r = subprocess.run( + ["mypy", "--strict", str(script)], + encoding="utf-8", + env=env, + capture_output=True, + ) + assert r.returncode == 0, r.stdout + r.stderr + + def test_discriminator_mypy( + self, python_model_v1_protocols: Tuple[Path, str], tmp_path: Path + ) -> None: """ The discriminator marker must prevent structurally-identical classes (test_class and another_class share no own properties in v1) from @@ -2453,8 +2576,11 @@ class TestProtocolCrossVersion: """ def test_cross_version_mypy( - self, python_model_v1_protocols, python_model_v2_protocols, tmp_path - ): + self, + python_model_v1_protocols: Tuple[Path, str], + python_model_v2_protocols: Tuple[Path, str], + tmp_path: Path, + ) -> None: """ v2.test_class() satisfies v1.protocols.test_class (backward-compat). v2.another_class() does NOT satisfy v1.protocols.test_class (discriminator). From 0ef385aa9cd9ea37090962cbdd702fb09021e9d9 Mon Sep 17 00:00:00 2001 From: Arthit Suriyawongkul Date: Thu, 2 Jul 2026 03:01:40 +0100 Subject: [PATCH 11/40] Fix __dir__ gap Signed-off-by: Arthit Suriyawongkul --- .../lang/templates/python/__init__.py.j2 | 18 +++++- tests/test_python.py | 60 +++++++++++++++++++ 2 files changed, 76 insertions(+), 2 deletions(-) diff --git a/src/shacl2code/lang/templates/python/__init__.py.j2 b/src/shacl2code/lang/templates/python/__init__.py.j2 index 3ef99961..7ec1f2a9 100644 --- a/src/shacl2code/lang/templates/python/__init__.py.j2 +++ b/src/shacl2code/lang/templates/python/__init__.py.j2 @@ -7,7 +7,7 @@ from __future__ import annotations import importlib -from typing import Any, TYPE_CHECKING +from typing import Any, List, TYPE_CHECKING if TYPE_CHECKING: from .model import * # noqa: F401, F403 @@ -16,7 +16,7 @@ if TYPE_CHECKING: """Format Guard{{ '"' }}{{ '"' }}{{ '"' }} {%- if include_protocols %} if TYPE_CHECKING: - from . import protocols # noqa: F401, I202 + from . import protocols # noqa: F401, I100, I202 {%- endif %} @@ -37,5 +37,19 @@ def __getattr__(name: str) -> Any: raise AttributeError(f"module {__name__!r} has no attribute {name!r}") +def __dir__() -> List[str]: + # Import model here (not at module scope) so dir() stays opt-in: it costs + # a model load only when introspection is actually requested (PEP 562). + mod = importlib.import_module(f"{__name__}.model") + names = set(globals()) | set(dir(mod)) +{%- if include_protocols %} + names.add("protocols") +{%- endif %} +{%- if include_main %} + names.add("main") +{%- endif %} + return sorted(names) + + {{ '"' }}{{ '"' }}{{ '"' }}Format Guard""" # fmt: on diff --git a/tests/test_python.py b/tests/test_python.py index a33b3499..252bbf96 100644 --- a/tests/test_python.py +++ b/tests/test_python.py @@ -2379,6 +2379,46 @@ def test_protocols_file_not_generated_by_default( ) assert not (output_dir / "protocols.py").exists() + def test_dir_includes_lazy_names( + self, tmp_path: Path, model_context_url: str + ) -> None: + """ + __dir__() must expose model classes, "protocols", and "main" for + dir()/tab-completion even though they are loaded lazily via + __getattr__ (PEP 562). Without a custom __dir__, Python falls back + to only the raw module internals (imports, dunders) -- see + PLAN-python-protocols.md Lesson 7. + """ + module_name = "pymodel_dir_check" + output_dir = tmp_path / module_name + shacl2code_generate( + ["--input", TEST_MODEL, "--context", model_context_url], + ["--include-protocols", "yes"], + output_dir, + ) + + sys.path.insert(0, str(tmp_path)) + try: + pkg = importlib.import_module(module_name) + names = dir(pkg) + + assert "test_class" in names + assert "parent_class" in names + assert "protocols" in names + assert "main" in names + + # protocols.py is not itself lazily loaded, so dir() on the + # lazy .protocols entry point must show its domain classes too -- + # confirms tab-completion works end to end through __getattr__. + proto_names = dir(pkg.protocols) + assert "SHACLObjectProtocol" in proto_names + assert "test_class" in proto_names + finally: + sys.path.remove(str(tmp_path)) + for m in list(sys.modules): + if m == module_name or m.startswith(module_name + "."): + del sys.modules[m] + def test_mypy(self, tmp_path: Path, model_context_url: str) -> None: output_dir = tmp_path / "pymodel" shacl2code_generate( @@ -2402,6 +2442,26 @@ def test_flake8(self, tmp_path: Path, model_context_url: str) -> None: check=True, ) + def test_flake8_all_files(self, tmp_path: Path, model_context_url: str) -> None: + """ + flake8 over the whole output directory with --include-protocols yes, + not just protocols.py -- catches issues in the conditional protocols + import inside __init__.py (e.g. the I100 ordering regression fixed in + PLAN-python-protocols.md Lesson 7) that a protocols.py-only check + would miss. + """ + output_dir = tmp_path / "pymodel" + shacl2code_generate( + ["--input", TEST_MODEL, "--context", model_context_url], + ["--include-protocols", "yes"], + output_dir, + ) + subprocess.run( + ["flake8", "--config", TOP_DIR / ".flake8"] + list(output_dir.iterdir()), + encoding="utf-8", + check=True, + ) + def test_flake8_no_datetime_properties(self, tmp_path: Path) -> None: """ protocols.py must not unconditionally import `datetime`. A model with From 2c7e6dc0e1332639577c2a19c73b02f87f34a5d2 Mon Sep 17 00:00:00 2001 From: Arthit Suriyawongkul Date: Fri, 3 Jul 2026 22:15:00 +0100 Subject: [PATCH 12/40] Prevent model load when load cmd Signed-off-by: Arthit Suriyawongkul --- .../lang/templates/python/__init__.py.j2 | 19 ++++- .../lang/templates/python/cmd.py.j2 | 25 ++++-- tests/test_python.py | 85 +++++++++++++++++++ 3 files changed, 119 insertions(+), 10 deletions(-) diff --git a/src/shacl2code/lang/templates/python/__init__.py.j2 b/src/shacl2code/lang/templates/python/__init__.py.j2 index 7ec1f2a9..531da8fb 100644 --- a/src/shacl2code/lang/templates/python/__init__.py.j2 +++ b/src/shacl2code/lang/templates/python/__init__.py.j2 @@ -7,7 +7,8 @@ from __future__ import annotations import importlib -from typing import Any, List, TYPE_CHECKING +from types import ModuleType +from typing import Any, List, TYPE_CHECKING, TypeVar if TYPE_CHECKING: from .model import * # noqa: F401, F403 @@ -21,7 +22,21 @@ if TYPE_CHECKING: def __getattr__(name: str) -> Any: - # Lazy attribute access: submodules and model content loaded on first use (PEP 562). + # PEP 562 lazy access: each branch imports only what it needs. + if name == "__all__": + # Only "import *" needs this; it must load the model to compute it. + mod = importlib.import_module(f"{__name__}.model") + return sorted( + n + for n, o in vars(mod).items() + if not n.startswith("_") + and n != "TYPE_CHECKING" # imported flag, not model content + and not isinstance(o, (TypeVar, ModuleType)) + and ( + getattr(o, "__module__", None) == mod.__name__ + or getattr(o, "__module__", None) is None # plain constants + ) + ) {%- if include_protocols %} if name == "protocols": return importlib.import_module(f"{__name__}.protocols") diff --git a/src/shacl2code/lang/templates/python/cmd.py.j2 b/src/shacl2code/lang/templates/python/cmd.py.j2 index 82b1dd23..eefd440d 100644 --- a/src/shacl2code/lang/templates/python/cmd.py.j2 +++ b/src/shacl2code/lang/templates/python/cmd.py.j2 @@ -4,23 +4,26 @@ # # SPDX-License-Identifier: {{ spdx_license }} +from __future__ import annotations + import argparse from pathlib import Path -from typing import Any, Iterable, List +from typing import Any, Iterable, List, TYPE_CHECKING + +if TYPE_CHECKING: + from .model import SHACLObject -from .model import ( - JSONLDDeserializer, - JSONLDSerializer, - ListProxy, - SHACLObject, - SHACLObjectSet, -) +# NOTE: .model is imported inside each function below, not here, because +# __init__.py can import this module just to fetch "main" (e.g. dir()), +# without calling it -- that must not force the model to load. def print_tree(objects: Iterable[SHACLObject], all_fields: bool = False) -> None: """ Print object tree """ + from .model import ListProxy, SHACLObject + seen = set() def callback(value: Any, path: List[str]) -> bool: @@ -52,6 +55,12 @@ def print_tree(objects: Iterable[SHACLObject], all_fields: bool = False) -> None def main() -> int: + from .model import ( + JSONLDDeserializer, + JSONLDSerializer, + SHACLObjectSet, + ) + parser = argparse.ArgumentParser(description="Python SHACL model test") parser.add_argument("infile", type=Path, help="Input file") parser.add_argument("--print", action="store_true", help="Print object tree") diff --git a/tests/test_python.py b/tests/test_python.py index 252bbf96..31d55961 100644 --- a/tests/test_python.py +++ b/tests/test_python.py @@ -2704,3 +2704,88 @@ def set_ref(o: v1p.test_class, v: {v2_name}.test_class) -> None: "Expected mypy to reject v2.another_class as v1.protocols.test_class " "across versions" ) + + +class TestModelAll: + def test_wildcard_import_is_eager_and_matches_public_names( + self, tmp_path: Path + ) -> None: + """``from mypkg import *`` yields the model's public names + and requires loading the model. + + Generated without --context, so the domain class asserted below + keeps its recognizable "http_..." varname. + """ + module_name = "pymodel_star_check" + output_dir = tmp_path / module_name + shacl2code_generate( + ["--input", TEST_MODEL], + [], + output_dir, + ) + + sys.path.insert(0, str(tmp_path)) + try: + import sys as _sys + + before = set(_sys.modules) + ns: dict = {} + exec(f"from {module_name} import *", ns) + imported = {k for k in ns if not k.startswith("__")} + + # Domain classes, from the test fixture model. + assert "http_example_org_test_class" in imported + assert "http_example_org_parent_class" in imported + + # Generator infrastructure: constants, base/encoder/decoder classes. + assert "CONTEXT_URLS" in imported + assert "NAMED_INDIVIDUALS" in imported + assert "SHACLObject" in imported + assert "SHACLObjectSet" in imported + assert "JSONLDDecoder" in imported + assert "JSONLDEncoder" in imported + # rdflib is a test dependency, so the RDF* classes are defined and + # expected to be included. + assert "RDFSerializer" in imported + + # Must not leak imports/type-checking machinery from model.py. + assert not imported & { + "TYPE_CHECKING", + "Any", + "List", + "TypeVar", + "json", + } + + # The model was loaded as a side effect of the wildcard import. + assert f"{module_name}.model" in (set(_sys.modules) - before) + finally: + sys.path.remove(str(tmp_path)) + for m in list(sys.modules): + if m == module_name or m.startswith(module_name + "."): + del sys.modules[m] + + def test_protocols_submodule_import_stays_lazy( + self, tmp_path: Path, model_context_url: str + ) -> None: + """Importing the ``protocols`` submodule must not load ``model``.""" + module_name = "pymodel_lazy_check" + output_dir = tmp_path / module_name + shacl2code_generate( + ["--input", TEST_MODEL, "--context", model_context_url], + ["--include-protocols", "yes"], + output_dir, + ) + + sys.path.insert(0, str(tmp_path)) + try: + import sys as _sys + + before = set(_sys.modules) + importlib.import_module(f"{module_name}.protocols") + assert f"{module_name}.model" not in (set(_sys.modules) - before) + finally: + sys.path.remove(str(tmp_path)) + for m in list(sys.modules): + if m == module_name or m.startswith(module_name + "."): + del sys.modules[m] From 142612238ca77de19bf3bdadd7a4ed076775121c Mon Sep 17 00:00:00 2001 From: Arthit Suriyawongkul Date: Tue, 7 Jul 2026 00:20:57 +0100 Subject: [PATCH 13/40] Update test-v2.ttl to new base Follow the updated test.ttl Signed-off-by: Arthit Suriyawongkul --- tests/data/model/test-v2.ttl | 2 +- tests/test_python.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/data/model/test-v2.ttl b/tests/data/model/test-v2.ttl index 15327982..12f05a41 100644 --- a/tests/data/model/test-v2.ttl +++ b/tests/data/model/test-v2.ttl @@ -1,7 +1,7 @@ # Backward-compatible extension of test.ttl for cross-version Protocol tests. # Adds: parent-class/v2-new-prop (new optional scalar) and test-another-class # (same property set as test-class, for discriminator testing). -@base . +@base . @prefix rdf: . @prefix rdfs: . @prefix sh: . diff --git a/tests/test_python.py b/tests/test_python.py index 1732bf4c..4bb66bce 100644 --- a/tests/test_python.py +++ b/tests/test_python.py @@ -2766,8 +2766,8 @@ def test_wildcard_import_is_eager_and_matches_public_names( imported = {k for k in ns if not k.startswith("__")} # Domain classes, from the test fixture model. - assert "http_example_org_test_class" in imported - assert "http_example_org_parent_class" in imported + assert "http_example_org_shacl2code_test_test_class" in imported + assert "http_example_org_shacl2code_test_parent_class" in imported # Generator infrastructure: constants, base/encoder/decoder classes. assert "CONTEXT_URLS" in imported From a0b8f7a527387ad07124f308c1835dbd024a19f8 Mon Sep 17 00:00:00 2001 From: Arthit Suriyawongkul Date: Tue, 7 Jul 2026 01:07:08 +0100 Subject: [PATCH 14/40] Add module-level IS_PRERELEASE For cheap check of is_release, without the need to actually load the model Signed-off-by: Arthit Suriyawongkul --- .../lang/templates/python/__init__.py.j2 | 5 +++ tests/data/prerelease.ttl | 25 ++++++++++++ tests/test_python.py | 39 ++++++++++++++++--- 3 files changed, 63 insertions(+), 6 deletions(-) create mode 100644 tests/data/prerelease.ttl diff --git a/src/shacl2code/lang/templates/python/__init__.py.j2 b/src/shacl2code/lang/templates/python/__init__.py.j2 index 531da8fb..dc836ebc 100644 --- a/src/shacl2code/lang/templates/python/__init__.py.j2 +++ b/src/shacl2code/lang/templates/python/__init__.py.j2 @@ -13,6 +13,11 @@ from typing import Any, List, TYPE_CHECKING, TypeVar if TYPE_CHECKING: from .model import * # noqa: F401, F403 +# True if any ontology behind this version's model is pre-release +# (a model can merge multiple --input files, each with its own owl:Ontology). +# Cheap check -- does not import model.py. +IS_PRERELEASE = {{ontologies | selectattr("is_prerelease") | list | length > 0}} + # fmt: off """Format Guard{{ '"' }}{{ '"' }}{{ '"' }} {%- if include_protocols %} diff --git a/tests/data/prerelease.ttl b/tests/data/prerelease.ttl new file mode 100644 index 00000000..0251f822 --- /dev/null +++ b/tests/data/prerelease.ttl @@ -0,0 +1,25 @@ +@base . +@prefix rdfs: . +@prefix sh: . +@prefix owl: . +@prefix xsd: . +@prefix sh-to-code: . + +# A minimal model whose ontology is marked pre-release. +# Used to verify IS_PRERELEASE = True is generated for a version whose +# model TTL carries sh-to-code:isPreRelease true. + + a owl:Ontology ; + rdfs:comment "A pre-release test ontology" ; + rdfs:label "prerelease-test" ; + sh-to-code:isPreRelease true + . + + a rdfs:Class, sh:NodeShape, owl:Class ; + rdfs:comment "A class in a pre-release ontology" ; + sh:property [ + sh:datatype xsd:string ; + sh:path ; + sh:maxCount 1 + ] + . diff --git a/tests/test_python.py b/tests/test_python.py index 4bb66bce..f55ee4ba 100644 --- a/tests/test_python.py +++ b/tests/test_python.py @@ -2315,6 +2315,37 @@ def test_prerelease_warning(model): with pytest.warns(FutureWarning): model.test_class() + +PRERELEASE_MODEL = DATA_DIR / "prerelease.ttl" + + +def test_is_prerelease_constant(tmp_path: Path) -> None: + """ + IS_PRERELEASE is a version-level constant a consumer can read without + loading model.py -- True when any ontology in the source TTL carries + sh-to-code:isPreRelease, False otherwise. + """ + prerelease_dir = tmp_path / "pymodel_prerelease" + shacl2code_generate(["--input", PRERELEASE_MODEL], [], prerelease_dir) + + stable_dir = tmp_path / "pymodel_stable" + shacl2code_generate(["--input", TEST_MODEL], [], stable_dir) + + assert "IS_PRERELEASE = True" in (prerelease_dir / "__init__.py").read_text() + assert "IS_PRERELEASE = False" in (stable_dir / "__init__.py").read_text() + + sys.path.insert(0, str(tmp_path)) + try: + pkg = importlib.import_module("pymodel_prerelease") + assert pkg.IS_PRERELEASE is True + # Reading the constant must not have loaded model.py. + assert "pymodel_prerelease.model" not in sys.modules + finally: + sys.path.remove(str(tmp_path)) + for m in list(sys.modules): + if m == "pymodel_prerelease" or m.startswith("pymodel_prerelease."): + del sys.modules[m] + # --------------------------------------------------------------------------- # Protocol generation tests # --------------------------------------------------------------------------- @@ -2417,9 +2448,7 @@ def test_dir_includes_lazy_names( """ __dir__() must expose model classes, "protocols", and "main" for dir()/tab-completion even though they are loaded lazily via - __getattr__ (PEP 562). Without a custom __dir__, Python falls back - to only the raw module internals (imports, dunders) -- see - PLAN-python-protocols.md Lesson 7. + __getattr__ (PEP 562). """ module_name = "pymodel_dir_check" output_dir = tmp_path / module_name @@ -2478,9 +2507,7 @@ def test_flake8_all_files(self, tmp_path: Path, model_context_url: str) -> None: """ flake8 over the whole output directory with --include-protocols yes, not just protocols.py -- catches issues in the conditional protocols - import inside __init__.py (e.g. the I100 ordering regression fixed in - PLAN-python-protocols.md Lesson 7) that a protocols.py-only check - would miss. + import inside __init__.py that a protocols.py-only check would miss. """ output_dir = tmp_path / "pymodel" shacl2code_generate( From 1118747eba4e996dbefa3de153a9e3151ff2681a Mon Sep 17 00:00:00 2001 From: Arthit Suriyawongkul Date: Tue, 7 Jul 2026 02:02:06 +0100 Subject: [PATCH 15/40] Fix formatting Signed-off-by: Arthit Suriyawongkul --- tests/test_python.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_python.py b/tests/test_python.py index f55ee4ba..4e2f77a8 100644 --- a/tests/test_python.py +++ b/tests/test_python.py @@ -2298,6 +2298,7 @@ def test_version(model): assert model.VERSION_STRING == MODEL_VERSION assert model.VERSION == convert_version_string(MODEL_VERSION) + # --------------------------------------------------------------------------- # Ontology metadata tests # --------------------------------------------------------------------------- @@ -2346,6 +2347,7 @@ def test_is_prerelease_constant(tmp_path: Path) -> None: if m == "pymodel_prerelease" or m.startswith("pymodel_prerelease."): del sys.modules[m] + # --------------------------------------------------------------------------- # Protocol generation tests # --------------------------------------------------------------------------- From e5c49c1aeac9c1d4268e6ea565f4e662870654d6 Mon Sep 17 00:00:00 2001 From: Arthit Suriyawongkul Date: Tue, 7 Jul 2026 13:01:22 +0100 Subject: [PATCH 16/40] Fires FutureWarning when import pre-release model Signed-off-by: Arthit Suriyawongkul --- src/shacl2code/lang/python.py | 7 +--- .../lang/templates/python/__init__.py.j2 | 15 ++++--- tests/test_python.py | 40 ++++++++++++++++--- 3 files changed, 45 insertions(+), 17 deletions(-) diff --git a/src/shacl2code/lang/python.py b/src/shacl2code/lang/python.py index 40acf4f4..632d5e57 100644 --- a/src/shacl2code/lang/python.py +++ b/src/shacl2code/lang/python.py @@ -78,12 +78,7 @@ def varname(*name): def protocols_use_datetime(classes: Iterable[Class]) -> bool: - """Whether any class has a plain (non-list, non-ref) datetime-typed property. - - Determines if protocols.py.j2 needs to import ``datetime`` -- mirrors the - scalar-property branch in that template exactly, so the import is only - emitted when it will actually be referenced (avoids flake8 F401). - """ + """Whether any class has a plain datetime-typed scalar property.""" for cls in classes: for prop in cls.properties: is_list = prop.max_count is None or prop.max_count != 1 diff --git a/src/shacl2code/lang/templates/python/__init__.py.j2 b/src/shacl2code/lang/templates/python/__init__.py.j2 index dc836ebc..a870ebc5 100644 --- a/src/shacl2code/lang/templates/python/__init__.py.j2 +++ b/src/shacl2code/lang/templates/python/__init__.py.j2 @@ -7,17 +7,23 @@ from __future__ import annotations import importlib +import warnings from types import ModuleType from typing import Any, List, TYPE_CHECKING, TypeVar if TYPE_CHECKING: from .model import * # noqa: F401, F403 -# True if any ontology behind this version's model is pre-release -# (a model can merge multiple --input files, each with its own owl:Ontology). -# Cheap check -- does not import model.py. +# True if any ontology behind this model is pre-release. IS_PRERELEASE = {{ontologies | selectattr("is_prerelease") | list | length > 0}} +if IS_PRERELEASE: + # Fires once on first import, regardless of import form. + warnings.warn( + f"{__name__!r} is a pre-release model version and may change without notice.", + FutureWarning, + ) + # fmt: off """Format Guard{{ '"' }}{{ '"' }}{{ '"' }} {%- if include_protocols %} @@ -58,8 +64,7 @@ def __getattr__(name: str) -> Any: def __dir__() -> List[str]: - # Import model here (not at module scope) so dir() stays opt-in: it costs - # a model load only when introspection is actually requested (PEP 562). + # Opt-in: model loads only when dir() is actually called. mod = importlib.import_module(f"{__name__}.model") names = set(globals()) | set(dir(mod)) {%- if include_protocols %} diff --git a/tests/test_python.py b/tests/test_python.py index 4e2f77a8..a7c7bd2b 100644 --- a/tests/test_python.py +++ b/tests/test_python.py @@ -11,6 +11,7 @@ import subprocess import sys import textwrap +import warnings from datetime import datetime, timedelta, timezone from pathlib import Path from typing import Iterable, Tuple @@ -2303,6 +2304,7 @@ def test_version(model): # Ontology metadata tests # --------------------------------------------------------------------------- + def test_ontology(model): classes = list(model.SHACLObject.CLASSES.values()) assert classes @@ -2321,11 +2323,7 @@ def test_prerelease_warning(model): def test_is_prerelease_constant(tmp_path: Path) -> None: - """ - IS_PRERELEASE is a version-level constant a consumer can read without - loading model.py -- True when any ontology in the source TTL carries - sh-to-code:isPreRelease, False otherwise. - """ + """IS_PRERELEASE reflects sh-to-code:isPreRelease without loading model.py.""" prerelease_dir = tmp_path / "pymodel_prerelease" shacl2code_generate(["--input", PRERELEASE_MODEL], [], prerelease_dir) @@ -2337,7 +2335,8 @@ def test_is_prerelease_constant(tmp_path: Path) -> None: sys.path.insert(0, str(tmp_path)) try: - pkg = importlib.import_module("pymodel_prerelease") + with pytest.warns(FutureWarning): + pkg = importlib.import_module("pymodel_prerelease") assert pkg.IS_PRERELEASE is True # Reading the constant must not have loaded model.py. assert "pymodel_prerelease.model" not in sys.modules @@ -2348,6 +2347,35 @@ def test_is_prerelease_constant(tmp_path: Path) -> None: del sys.modules[m] +def test_prerelease_import_warning(tmp_path: Path) -> None: + """Pre-release package warns FutureWarning on first import, any form; stable doesn't.""" + prerelease_dir = tmp_path / "pymodel_prerelease_import" + shacl2code_generate(["--input", PRERELEASE_MODEL], [], prerelease_dir) + + stable_dir = tmp_path / "pymodel_stable_import" + shacl2code_generate(["--input", TEST_MODEL], [], stable_dir) + + sys.path.insert(0, str(tmp_path)) + try: + with pytest.warns(FutureWarning): + import pymodel_prerelease_import # noqa: F401 + + # Second import of an already-loaded module must not re-warn. + with warnings.catch_warnings(): + warnings.simplefilter("error", FutureWarning) + importlib.import_module("pymodel_prerelease_import") + + with warnings.catch_warnings(): + warnings.simplefilter("error", FutureWarning) + import pymodel_stable_import # noqa: F401 + finally: + sys.path.remove(str(tmp_path)) + for prefix in ("pymodel_prerelease_import", "pymodel_stable_import"): + for m in list(sys.modules): + if m == prefix or m.startswith(prefix + "."): + del sys.modules[m] + + # --------------------------------------------------------------------------- # Protocol generation tests # --------------------------------------------------------------------------- From 9803d07bdc2bc99e54d59fe5e3b1013b3cdeb44f Mon Sep 17 00:00:00 2001 From: Arthit Suriyawongkul Date: Tue, 4 Aug 2026 12:44:36 +0100 Subject: [PATCH 17/40] Fix formatting Signed-off-by: Arthit Suriyawongkul --- tests/test_python.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_python.py b/tests/test_python.py index 30a4e4f4..083821b0 100644 --- a/tests/test_python.py +++ b/tests/test_python.py @@ -2321,6 +2321,7 @@ def test_ontology(model): # Pre-release metadata tests # --------------------------------------------------------------------------- + def test_prerelease_warning(model): model.SHACL2CODE_TEST.is_prerelease = True From 3682fa515cb8754550d54c0c24f219437623a34b Mon Sep 17 00:00:00 2001 From: Arthit Suriyawongkul Date: Fri, 14 Aug 2026 00:51:45 +0100 Subject: [PATCH 18/40] Use shared prop_shape() Signed-off-by: Arthit Suriyawongkul --- src/shacl2code/lang/python.py | 14 +++++++++++--- src/shacl2code/lang/templates/python/model.py.j2 | 4 ++-- src/shacl2code/lang/templates/python/model.pyi.j2 | 12 ++++++------ .../lang/templates/python/protocols.py.j2 | 7 +++---- tests/test_python.py | 6 ++++-- 5 files changed, 26 insertions(+), 17 deletions(-) diff --git a/src/shacl2code/lang/python.py b/src/shacl2code/lang/python.py index 632d5e57..c4db294c 100644 --- a/src/shacl2code/lang/python.py +++ b/src/shacl2code/lang/python.py @@ -77,13 +77,20 @@ def varname(*name): return name +def prop_shape(prop): + """Classify a property's container shape: (is_list, has_ref, is_enum).""" + is_list = prop.max_count is None or prop.max_count != 1 + is_enum = bool(prop.enum_values) + has_ref = bool(prop.class_id) and not is_enum + return is_list, has_ref, is_enum + + def protocols_use_datetime(classes: Iterable[Class]) -> bool: """Whether any class has a plain datetime-typed scalar property.""" for cls in classes: for prop in cls.properties: - is_list = prop.max_count is None or prop.max_count != 1 - has_ref = bool(prop.class_id) and not prop.enum_values - is_scalar = not (is_list or has_ref or prop.enum_values) + is_list, has_ref, is_enum = prop_shape(prop) + is_scalar = not (is_list or has_ref or is_enum) if is_scalar and DATATYPE_PYTHON_TYPES[prop.datatype] == "datetime": return True return False @@ -171,6 +178,7 @@ def get_file(name): def get_extra_env(self): return { "varname": varname, + "prop_shape": prop_shape, "protocols_use_datetime": protocols_use_datetime, "DATATYPE_CLASSES": DATATYPE_CLASSES, "DATATYPE_PYTHON_TYPES": DATATYPE_PYTHON_TYPES, diff --git a/src/shacl2code/lang/templates/python/model.py.j2 b/src/shacl2code/lang/templates/python/model.py.j2 index bb263205..b230ed40 100644 --- a/src/shacl2code/lang/templates/python/model.py.j2 +++ b/src/shacl2code/lang/templates/python/model.py.j2 @@ -899,7 +899,7 @@ class SHACLObjectMeta(type): SHACLObject.CLASSES[key] = c -register_lock = threading.Lock() +_register_lock = threading.Lock() _ALL_NAMED_INDIVIDUAL_IDS: Set[str] = set() T_SHACLObject = TypeVar("T_SHACLObject", bound="SHACLObject") @@ -1002,7 +1002,7 @@ class SHACLObject(metaclass=SHACLObjectMeta): if self.ONTOLOGY: _warn_ontology(self.ONTOLOGY) - with register_lock: + with _register_lock: cls = self.__class__ if cls._NEEDS_REG: for p in cls._OBJ_PY_PROPS.values(): diff --git a/src/shacl2code/lang/templates/python/model.pyi.j2 b/src/shacl2code/lang/templates/python/model.pyi.j2 index dc584ea9..1df38353 100644 --- a/src/shacl2code/lang/templates/python/model.pyi.j2 +++ b/src/shacl2code/lang/templates/python/model.pyi.j2 @@ -458,10 +458,10 @@ class {{ varname(*class.clsname) }}( {{ class.id_property }}: Optional[str] = None, {%- endif %} {%- for prop in class.properties %} - {%- set is_list = prop.max_count is none or prop.max_count != 1 %} - {%- if prop.enum_values %} + {%- set is_list, has_ref, is_enum = prop_shape(prop) %} + {%- if is_enum %} {%- set ptype = "str" %} - {%- elif prop.class_id %} + {%- elif has_ref %} {%- set ptype = "Union[str, '" ~ varname(*classes.get(prop.class_id).clsname) ~ "']" %} {%- else %} {%- set ptype = DATATYPE_PYTHON_TYPES[prop.datatype] %} @@ -481,10 +481,10 @@ class {{ varname(*class.clsname) }}( {{ class.id_property }}: Optional[str] {%- endif %} {%- for prop in class.properties %} - {%- set is_list = prop.max_count is none or prop.max_count != 1 %} - {%- if prop.enum_values %} + {%- set is_list, has_ref, is_enum = prop_shape(prop) %} + {%- if is_enum %} {%- set ptype = "str" %} - {%- elif prop.class_id %} + {%- elif has_ref %} {%- set ptype = "Union[str, '" ~ varname(*classes.get(prop.class_id).clsname) ~ "']" %} {%- else %} {%- set ptype = DATATYPE_PYTHON_TYPES[prop.datatype] %} diff --git a/src/shacl2code/lang/templates/python/protocols.py.j2 b/src/shacl2code/lang/templates/python/protocols.py.j2 index b32decea..e22f5b5d 100644 --- a/src/shacl2code/lang/templates/python/protocols.py.j2 +++ b/src/shacl2code/lang/templates/python/protocols.py.j2 @@ -89,11 +89,10 @@ class {{ varname(*class.clsname) }}( {{ class.id_property }}: Optional[str] {%- endif %} {%- for prop in class.properties %} - {%- set is_list = prop.max_count is none or prop.max_count != 1 %} - {%- set has_ref = prop.class_id and not prop.enum_values %} - {%- if prop.enum_values %} + {%- set is_list, has_ref, is_enum = prop_shape(prop) %} + {%- if is_enum %} {%- set ptype = "str" %} - {%- elif prop.class_id %} + {%- elif has_ref %} {%- set ptype = "Any" %} {%- else %} {%- set ptype = DATATYPE_PYTHON_TYPES[prop.datatype] %} diff --git a/tests/test_python.py b/tests/test_python.py index 0ee6d587..abccb77d 100644 --- a/tests/test_python.py +++ b/tests/test_python.py @@ -3344,7 +3344,6 @@ def test_wildcard_import_is_eager_and_matches_public_names( # Generator infrastructure: constants, base/encoder/decoder classes. assert "CONTEXT_URLS" in imported - assert "NAMED_INDIVIDUALS" in imported assert "SHACLObject" in imported assert "SHACLObjectSet" in imported assert "JSONLDDecoder" in imported @@ -3353,13 +3352,16 @@ def test_wildcard_import_is_eager_and_matches_public_names( # expected to be included. assert "RDFSerializer" in imported - # Must not leak imports/type-checking machinery from model.py. + # Must not leak imports/type-checking machinery from model.py, + # nor internal bookkeeping state that isn't part of the public API. assert not imported & { "TYPE_CHECKING", "Any", "List", "TypeVar", "json", + "NAMED_INDIVIDUALS", + "register_lock", } # The model was loaded as a side effect of the wildcard import. From 47808d52d86b3fa461e74117dff6c6ad86d0b7f6 Mon Sep 17 00:00:00 2001 From: Arthit Suriyawongkul Date: Fri, 14 Aug 2026 01:35:15 +0100 Subject: [PATCH 19/40] Share code for lazy import Signed-off-by: Arthit Suriyawongkul --- .../lang/templates/python/__init__.py.j2 | 30 +++++++++---------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/src/shacl2code/lang/templates/python/__init__.py.j2 b/src/shacl2code/lang/templates/python/__init__.py.j2 index a870ebc5..47379bae 100644 --- a/src/shacl2code/lang/templates/python/__init__.py.j2 +++ b/src/shacl2code/lang/templates/python/__init__.py.j2 @@ -9,7 +9,7 @@ from __future__ import annotations import importlib import warnings from types import ModuleType -from typing import Any, List, TYPE_CHECKING, TypeVar +from typing import Any, Callable, Dict, List, TYPE_CHECKING, TypeVar if TYPE_CHECKING: from .model import * # noqa: F401, F403 @@ -32,6 +32,16 @@ if TYPE_CHECKING: {%- endif %} +_LAZY_SUBMODULES: Dict[str, Callable[[], Any]] = { +{%- if include_protocols %} + "protocols": lambda: importlib.import_module(f"{__name__}.protocols"), +{%- endif %} +{%- if include_main %} + "main": lambda: importlib.import_module(f"{__name__}.cmd").main, +{%- endif %} +} + + def __getattr__(name: str) -> Any: # PEP 562 lazy access: each branch imports only what it needs. if name == "__all__": @@ -48,14 +58,8 @@ def __getattr__(name: str) -> Any: or getattr(o, "__module__", None) is None # plain constants ) ) -{%- if include_protocols %} - if name == "protocols": - return importlib.import_module(f"{__name__}.protocols") -{%- endif %} -{%- if include_main %} - if name == "main": - return importlib.import_module(f"{__name__}.cmd").main -{%- endif %} + if name in _LAZY_SUBMODULES: + return _LAZY_SUBMODULES[name]() mod = importlib.import_module(f"{__name__}.model") try: return getattr(mod, name) @@ -66,13 +70,7 @@ def __getattr__(name: str) -> Any: def __dir__() -> List[str]: # Opt-in: model loads only when dir() is actually called. mod = importlib.import_module(f"{__name__}.model") - names = set(globals()) | set(dir(mod)) -{%- if include_protocols %} - names.add("protocols") -{%- endif %} -{%- if include_main %} - names.add("main") -{%- endif %} + names = set(globals()) | set(dir(mod)) | set(_LAZY_SUBMODULES) return sorted(names) From 0fb4c5a57239497dd5bd047f928d9a1b8e31236e Mon Sep 17 00:00:00 2001 From: Arthit Suriyawongkul Date: Fri, 14 Aug 2026 01:49:25 +0100 Subject: [PATCH 20/40] Guard protocols datetime Signed-off-by: Arthit Suriyawongkul --- src/shacl2code/lang/python.py | 9 ++++++++- tests/test_python.py | 25 ++++++++++++++++++++++--- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/shacl2code/lang/python.py b/src/shacl2code/lang/python.py index c4db294c..62e6e2ef 100644 --- a/src/shacl2code/lang/python.py +++ b/src/shacl2code/lang/python.py @@ -8,6 +8,8 @@ from pathlib import Path from typing import Iterable +from jinja2 import TemplateRuntimeError + from .common import JinjaTemplateRender from .lang import TEMPLATE_DIR, language from ..model import Class @@ -91,7 +93,12 @@ def protocols_use_datetime(classes: Iterable[Class]) -> bool: for prop in cls.properties: is_list, has_ref, is_enum = prop_shape(prop) is_scalar = not (is_list or has_ref or is_enum) - if is_scalar and DATATYPE_PYTHON_TYPES[prop.datatype] == "datetime": + if not is_scalar: + continue + if prop.datatype not in DATATYPE_PYTHON_TYPES: + # Same error as model.py.j2's abort() + raise TemplateRuntimeError("Unknown data type " + prop.datatype) + if DATATYPE_PYTHON_TYPES[prop.datatype] == "datetime": return True return False diff --git a/tests/test_python.py b/tests/test_python.py index abccb77d..2b6ef607 100644 --- a/tests/test_python.py +++ b/tests/test_python.py @@ -18,6 +18,8 @@ import jsonschema +from jinja2 import TemplateRuntimeError + import pyshacl import pytest @@ -25,7 +27,7 @@ import rdflib from shacl2code.lang.python import protocols_use_datetime -from shacl2code.model import Class, Model +from shacl2code.model import Class, Model, Property from shacl2code.urlcontext import UrlContext from testfixtures import jsonvalidation, timetests @@ -2926,6 +2928,24 @@ def test_false_when_no_datetime_property(self) -> None: """NO_DATETIME_MODEL has only a plain string property.""" assert protocols_use_datetime(_load_classes(NO_DATETIME_MODEL)) is False + def test_raises_on_unknown_datatype(self) -> None: + """Unmapped datatype raises like model.py.j2's abort(), not KeyError.""" + bad_prop = Property( + path="http://example.org/bad", + varname="bad", + datatype="http://example.org/not-a-real-datatype", + max_count=1, + ) + bad_class = Class( + _id="http://example.org/BadClass", + clsname="BadClass", + parent_ids=[], + derived_ids=[], + properties=[bad_prop], + ) + with pytest.raises(TemplateRuntimeError, match="Unknown data type"): + protocols_use_datetime([bad_class]) + @pytest.fixture(scope="module") def python_model_v1_protocols( @@ -3352,8 +3372,7 @@ def test_wildcard_import_is_eager_and_matches_public_names( # expected to be included. assert "RDFSerializer" in imported - # Must not leak imports/type-checking machinery from model.py, - # nor internal bookkeeping state that isn't part of the public API. + # Must not leak model.py's imports or internal bookkeeping state. assert not imported & { "TYPE_CHECKING", "Any", From a27160fe353ee993ffdcdda183beef70ab979919 Mon Sep 17 00:00:00 2001 From: Arthit Suriyawongkul Date: Fri, 14 Aug 2026 09:10:48 +0100 Subject: [PATCH 21/40] Add missing protocol gates Signed-off-by: Arthit Suriyawongkul --- src/shacl2code/lang/templates/python/model.py.j2 | 2 ++ src/shacl2code/lang/templates/python/model.pyi.j2 | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/shacl2code/lang/templates/python/model.py.j2 b/src/shacl2code/lang/templates/python/model.py.j2 index b230ed40..f578f4e6 100644 --- a/src/shacl2code/lang/templates/python/model.py.j2 +++ b/src/shacl2code/lang/templates/python/model.py.j2 @@ -3052,9 +3052,11 @@ class {{ varname(*class.clsname) }}( {%- endfor %} } {%- endif %} + {%- if include_protocols %} def _protocol_{{ varname(*class.clsname) }}(self) -> None: pass + {%- endif %} {%- if class.properties %} PROPERTIES: ClassVar[List[ClassProp]] = [ diff --git a/src/shacl2code/lang/templates/python/model.pyi.j2 b/src/shacl2code/lang/templates/python/model.pyi.j2 index 1df38353..f26ae4db 100644 --- a/src/shacl2code/lang/templates/python/model.pyi.j2 +++ b/src/shacl2code/lang/templates/python/model.pyi.j2 @@ -475,7 +475,9 @@ class {{ varname(*class.clsname) }}( {%- endif %} **kwargs: Any ) -> None: ... + {%- if include_protocols %} def _protocol_{{ varname(*class.clsname) }}(self) -> None: ... + {%- endif %} {%- if class.id_property %} {{ class.id_property }}: Optional[str] From b29feada2a24a6c97455ce7caf80eb2fe7bf661a Mon Sep 17 00:00:00 2001 From: Arthit Suriyawongkul Date: Fri, 14 Aug 2026 09:22:42 +0100 Subject: [PATCH 22/40] Fix sort import Signed-off-by: Arthit Suriyawongkul --- tests/test_python.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_python.py b/tests/test_python.py index 2b6ef607..48a8ad5d 100644 --- a/tests/test_python.py +++ b/tests/test_python.py @@ -16,10 +16,10 @@ from pathlib import Path from typing import Iterable, Tuple -import jsonschema - from jinja2 import TemplateRuntimeError +import jsonschema + import pyshacl import pytest From 36d5d2001d0f3204534922d6c4f6098b63d55ad8 Mon Sep 17 00:00:00 2001 From: Arthit Suriyawongkul Date: Wed, 26 Aug 2026 03:48:04 +0700 Subject: [PATCH 23/40] Fix class basename Signed-off-by: Arthit Suriyawongkul --- src/shacl2code/lang/common.py | 11 ++-- .../lang/templates/python/model.py.j2 | 4 +- .../lang/templates/python/model.pyi.j2 | 2 +- .../lang/templates/python/protocols.py.j2 | 56 +++++++++++-------- tests/test_python.py | 4 +- 5 files changed, 46 insertions(+), 31 deletions(-) diff --git a/src/shacl2code/lang/common.py b/src/shacl2code/lang/common.py index 4ef2adaf..21d6324e 100644 --- a/src/shacl2code/lang/common.py +++ b/src/shacl2code/lang/common.py @@ -93,15 +93,18 @@ def output(self, model): class ObjectList(object): def __init__(self, objs): self.__objs = objs + self.__by_id = {} + for o in objs: + self.__by_id.setdefault(o._id, o) def __iter__(self): return iter(self.__objs) def get(self, _id): - for o in self.__objs: - if o._id == _id: - return o - raise KeyError(f"Object with ID {_id} not found") + try: + return self.__by_id[_id] + except KeyError: + raise KeyError(f"Object with ID {_id} not found") from None def get_all_derived(cls): def _recurse(cls): diff --git a/src/shacl2code/lang/templates/python/model.py.j2 b/src/shacl2code/lang/templates/python/model.py.j2 index 275c7a37..807dc822 100644 --- a/src/shacl2code/lang/templates/python/model.py.j2 +++ b/src/shacl2code/lang/templates/python/model.py.j2 @@ -3078,7 +3078,9 @@ class {{ varname(*class.clsname) }}( {%- endif %} {%- if include_protocols %} - def _protocol_{{ varname(*class.clsname) }}(self) -> None: + # Discriminator keyed by IRI, not the --context-compacted class name, so + # it matches across generations with different --context flags. + def _protocol_{{ varname(class._id) }}(self) -> None: pass {%- endif %} diff --git a/src/shacl2code/lang/templates/python/model.pyi.j2 b/src/shacl2code/lang/templates/python/model.pyi.j2 index f26ae4db..8429051a 100644 --- a/src/shacl2code/lang/templates/python/model.pyi.j2 +++ b/src/shacl2code/lang/templates/python/model.pyi.j2 @@ -476,7 +476,7 @@ class {{ varname(*class.clsname) }}( **kwargs: Any ) -> None: ... {%- if include_protocols %} - def _protocol_{{ varname(*class.clsname) }}(self) -> None: ... + def _protocol_{{ varname(class._id) }}(self) -> None: ... {%- endif %} {%- if class.id_property %} diff --git a/src/shacl2code/lang/templates/python/protocols.py.j2 b/src/shacl2code/lang/templates/python/protocols.py.j2 index e22f5b5d..47fc7b30 100644 --- a/src/shacl2code/lang/templates/python/protocols.py.j2 +++ b/src/shacl2code/lang/templates/python/protocols.py.j2 @@ -3,26 +3,28 @@ # SPDX-License-Identifier: {{ spdx_license }} """Version-agnostic Protocol types, one per generated class. -Each Protocol is satisfied by the corresponding concrete class of any model -version that is backward-compatible with the baseline from which these Protocols -were generated. Use these types to write functions and classes that work across -multiple model versions and still pass strict static type checking. - -Scalar properties are typed precisely and are plain read-write attributes. -Object-reference and list properties are typed ``Any`` for both read and write: -cross-module structural typing cannot resolve the circular reference between a -protocol class and the sibling protocol type its property would otherwise -return, so these properties fall back to ``Any``. Read them through the -concrete version type when a typed value is needed. - -Construct objects with a concrete version module; use these Protocols only for -annotations. +Satisfied by the corresponding concrete class of any model version +backward-compatible with the baseline these Protocols were generated from. +Use them to write functions and classes that work across model versions. + +Scalar and list-of-scalar properties are typed precisely. Object-reference +properties are typed ``Any``: structural typing can't resolve the circular +reference to the sibling protocol type they'd otherwise return. Read them +through the concrete version type when a typed value is needed. + +Construct objects with a concrete version module; use these Protocols only +for annotations. """ from __future__ import annotations -{{"from datetime import datetime" if protocols_use_datetime(classes) else ""}} +{% if protocols_use_datetime(classes) -%} +from datetime import datetime +{% endif -%} from typing import Any, Iterable, Iterator, Optional, Protocol, Set, Tuple +{%- if classes | selectattr("named_individuals") | list | length > 0 %} +from typing import ClassVar, Dict +{%- endif %} class SHACLObjectProtocol(Protocol): @@ -84,21 +86,29 @@ class {{ varname(*class.clsname) }}( {{ '"' }}{{ '"' }}{{ '"' }} {%- endif %} - def _protocol_{{ varname(*class.clsname) }}(self) -> None: ... + def _protocol_{{ varname(class._id) }}(self) -> None: ... {%- if class.id_property %} {{ class.id_property }}: Optional[str] {%- endif %} + {%- if class.named_individuals %} + NAMED_INDIVIDUALS: ClassVar[Dict[str, str]] + {%- for member in class.named_individuals %} + {{ varname(member.varname) }}: str + {%- endfor %} + {%- endif %} {%- for prop in class.properties %} {%- set is_list, has_ref, is_enum = prop_shape(prop) %} - {%- if is_enum %} - {%- set ptype = "str" %} - {%- elif has_ref %} - {%- set ptype = "Any" %} - {%- else %} - {%- set ptype = DATATYPE_PYTHON_TYPES[prop.datatype] %} + {%- if not has_ref %} + {%- if is_enum %} + {%- set ptype = "str" %} + {%- else %} + {%- set ptype = DATATYPE_PYTHON_TYPES[prop.datatype] %} + {%- endif %} {%- endif %} - {%- if is_list or has_ref %} + {%- if has_ref %} {{ varname(prop.varname) }}: Any + {%- elif is_list %} + {{ varname(prop.varname) }}: Iterable[{{ ptype }}] {%- else %} {{ varname(prop.varname) }}: Optional[{{ ptype }}] {%- endif %} diff --git a/tests/test_python.py b/tests/test_python.py index 0907b5d5..01c79433 100644 --- a/tests/test_python.py +++ b/tests/test_python.py @@ -3453,8 +3453,8 @@ def test_wildcard_import_is_eager_and_matches_public_names( "List", "TypeVar", "json", - "NAMED_INDIVIDUALS", - "register_lock", + "_ALL_NAMED_INDIVIDUAL_IDS", + "_register_lock", } # The model was loaded as a side effect of the wildcard import. From beb9e61957aaa745d4883d5c9f2e76fa3053e1d9 Mon Sep 17 00:00:00 2001 From: Arthit Suriyawongkul Date: Wed, 26 Aug 2026 14:25:46 +0700 Subject: [PATCH 24/40] Rename self.__by_id -> self.__objs_by_id Signed-off-by: Arthit Suriyawongkul --- src/shacl2code/lang/common.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/shacl2code/lang/common.py b/src/shacl2code/lang/common.py index 21d6324e..c7486d5b 100644 --- a/src/shacl2code/lang/common.py +++ b/src/shacl2code/lang/common.py @@ -93,16 +93,16 @@ def output(self, model): class ObjectList(object): def __init__(self, objs): self.__objs = objs - self.__by_id = {} + self.__objs_by_id = {} for o in objs: - self.__by_id.setdefault(o._id, o) + self.__objs_by_id.setdefault(o._id, o) def __iter__(self): return iter(self.__objs) def get(self, _id): try: - return self.__by_id[_id] + return self.__objs_by_id[_id] except KeyError: raise KeyError(f"Object with ID {_id} not found") from None From d79112c2de82fb08170d4466f783d408fb53beb7 Mon Sep 17 00:00:00 2001 From: Arthit Suriyawongkul Date: Wed, 26 Aug 2026 14:59:31 +0700 Subject: [PATCH 25/40] Unify prop_is_list and prop-shape typing helpers prop_is_list() was independently reimplemented in cpp.py, golang.py, rust.py, and python.py. Move it to common.py and share it across all language renderers. Signed-off-by: Arthit Suriyawongkul --- src/shacl2code/lang/common.py | 6 +++++ src/shacl2code/lang/cpp.py | 5 ---- src/shacl2code/lang/golang.py | 7 +----- src/shacl2code/lang/python.py | 22 ++++++++++++++--- src/shacl2code/lang/rust.py | 8 +------ .../lang/templates/python/_macros.j2 | 15 ++++++++++++ .../lang/templates/python/model.py.j2 | 8 ++----- .../lang/templates/python/model.pyi.j2 | 24 ++++--------------- .../lang/templates/python/protocols.py.j2 | 15 +++--------- 9 files changed, 51 insertions(+), 59 deletions(-) create mode 100644 src/shacl2code/lang/templates/python/_macros.j2 diff --git a/src/shacl2code/lang/common.py b/src/shacl2code/lang/common.py index c7486d5b..1c49fa75 100644 --- a/src/shacl2code/lang/common.py +++ b/src/shacl2code/lang/common.py @@ -22,6 +22,11 @@ THIS_DIR = Path(__file__).parent +def prop_is_list(prop): + """Whether a property's max_count allows more than one value.""" + return prop.max_count is None or prop.max_count != 1 + + class OutputFile(object): def __init__(self, path): self.path = path @@ -148,6 +153,7 @@ def get_all_named_individuals(cls): "get_all_derived": get_all_derived, "get_all_named_individuals": get_all_named_individuals, "include_file": include_file, + "prop_is_list": prop_is_list, **self.get_extra_env(), } diff --git a/src/shacl2code/lang/cpp.py b/src/shacl2code/lang/cpp.py index d44955ee..2b311ce7 100644 --- a/src/shacl2code/lang/cpp.py +++ b/src/shacl2code/lang/cpp.py @@ -37,10 +37,6 @@ def parent_cpp_classes(cls, classes): return parents -def prop_is_list(prop): - return prop.max_count is None or prop.max_count != 1 - - def id_str(s): return re.sub(r"[^a-zA-Z0-9_]", "_", s) @@ -182,7 +178,6 @@ def suffix(s): def get_extra_env(self): return { "varname": varname, - "prop_is_list": prop_is_list, "parent_cpp_classes": parent_cpp_classes, "macro_prefix": self.macro_prefix, "api_def_begin": comment_wrap(textwrap.dedent(f"""\ diff --git a/src/shacl2code/lang/golang.py b/src/shacl2code/lang/golang.py index a10d11ec..89965ef4 100644 --- a/src/shacl2code/lang/golang.py +++ b/src/shacl2code/lang/golang.py @@ -8,7 +8,7 @@ import textwrap from pathlib import Path -from .common import JinjaTemplateRender +from .common import JinjaTemplateRender, prop_is_list from .lang import TEMPLATE_DIR, language GO_KEYWORDS = ( @@ -75,10 +75,6 @@ def prop_name(prop): return varname(prop.varname, public=False) -def prop_is_list(prop): - return prop.max_count is None or prop.max_count != 1 - - def prop_go_type(prop, classes): if prop.enum_values: return "string" @@ -276,7 +272,6 @@ def get_extra_env(self): "interface_name": interface_name, "class_type_var": class_type_var, "prop_name": prop_name, - "prop_is_list": prop_is_list, "prop_go_type": prop_go_type, "prop_ctx_name": prop_ctx_name, "prop_decode_func": prop_decode_func, diff --git a/src/shacl2code/lang/python.py b/src/shacl2code/lang/python.py index 62e6e2ef..d8d2ccf3 100644 --- a/src/shacl2code/lang/python.py +++ b/src/shacl2code/lang/python.py @@ -10,7 +10,7 @@ from jinja2 import TemplateRuntimeError -from .common import JinjaTemplateRender +from .common import JinjaTemplateRender, prop_is_list from .lang import TEMPLATE_DIR, language from ..model import Class from ..util import convert_version_string @@ -81,10 +81,25 @@ def varname(*name): def prop_shape(prop): """Classify a property's container shape: (is_list, has_ref, is_enum).""" - is_list = prop.max_count is None or prop.max_count != 1 is_enum = bool(prop.enum_values) has_ref = bool(prop.class_id) and not is_enum - return is_list, has_ref, is_enum + return prop_is_list(prop), has_ref, is_enum + + +def prop_element_pytype(prop, classes): + """Python type of a single element of prop, ignoring container shape. + + Object-reference properties resolve to ``Union[str, 'ClassName']``, since + they may be set from either an id string or the referenced object. + """ + if prop.enum_values: + return "str" + if prop.class_id: + return "Union[str, '" + varname(*classes.get(prop.class_id).clsname) + "']" + if prop.datatype not in DATATYPE_PYTHON_TYPES: + # Same error as model.py.j2's abort() + raise TemplateRuntimeError("Unknown data type " + prop.datatype) + return DATATYPE_PYTHON_TYPES[prop.datatype] def protocols_use_datetime(classes: Iterable[Class]) -> bool: @@ -186,6 +201,7 @@ def get_extra_env(self): return { "varname": varname, "prop_shape": prop_shape, + "prop_element_pytype": prop_element_pytype, "protocols_use_datetime": protocols_use_datetime, "DATATYPE_CLASSES": DATATYPE_CLASSES, "DATATYPE_PYTHON_TYPES": DATATYPE_PYTHON_TYPES, diff --git a/src/shacl2code/lang/rust.py b/src/shacl2code/lang/rust.py index c3e46c7d..4aa5e3e6 100644 --- a/src/shacl2code/lang/rust.py +++ b/src/shacl2code/lang/rust.py @@ -7,7 +7,7 @@ import re from pathlib import Path -from .common import JinjaTemplateRender +from .common import JinjaTemplateRender, prop_is_list from .lang import TEMPLATE_DIR, language RUST_KEYWORDS = ( @@ -108,11 +108,6 @@ def prop_field_name(prop): return varname(prop.varname) -def prop_is_list(prop): - """Check if a property is a list.""" - return prop.max_count is None or prop.max_count != 1 - - def prop_rust_type(prop, classes): """Get the Rust type for a property's data type.""" if prop.enum_values: @@ -268,7 +263,6 @@ def get_extra_env(self): "type_name": type_name, "struct_name": struct_name, "prop_field_name": prop_field_name, - "prop_is_list": prop_is_list, "prop_rust_type": prop_rust_type, "prop_full_type": prop_full_type, "const_name": const_name, diff --git a/src/shacl2code/lang/templates/python/_macros.j2 b/src/shacl2code/lang/templates/python/_macros.j2 new file mode 100644 index 00000000..f1116ab4 --- /dev/null +++ b/src/shacl2code/lang/templates/python/_macros.j2 @@ -0,0 +1,15 @@ +{# +Copyright (c) 2024 Joshua Watt + +SPDX-License-Identifier: MIT + +Shared Jinja macros for the Python model.py, model.pyi and protocols.py +templates. +#} +{% macro class_docstring(comment) %} + {{ '"' }}{{ '"' }}{{ '"' }} + {%- for l in comment.split("\n") %} + {{ l.rstrip() }} + {%- endfor %} + {{ '"' }}{{ '"' }}{{ '"' }} +{%- endmacro %} diff --git a/src/shacl2code/lang/templates/python/model.py.j2 b/src/shacl2code/lang/templates/python/model.py.j2 index 807dc822..65cd77d4 100644 --- a/src/shacl2code/lang/templates/python/model.py.j2 +++ b/src/shacl2code/lang/templates/python/model.py.j2 @@ -1,3 +1,4 @@ +{%- import "_macros.j2" as pymacros -%} #! /usr/bin/env python3 # # {{ disclaimer }} @@ -3042,12 +3043,7 @@ class {{ varname(*class.clsname) }}( {%- else -%} SHACLObject {%- endif -%}): -{%- if class.comment %} - {{ '"' }}{{ '"' }}{{ '"' }} - {%- for l in class.comment.split("\n") %} - {{ l.rstrip() }} - {%- endfor %} - {{ '"' }}{{ '"' }}{{ '"' }} +{%- if class.comment %}{{ pymacros.class_docstring(class.comment) }} {% endif %} TYPE: ClassVar[str] = "{{ class._id }}" {%- if context.compact_iri(class._id) != class._id %} diff --git a/src/shacl2code/lang/templates/python/model.pyi.j2 b/src/shacl2code/lang/templates/python/model.pyi.j2 index 8429051a..f455b2ec 100644 --- a/src/shacl2code/lang/templates/python/model.pyi.j2 +++ b/src/shacl2code/lang/templates/python/model.pyi.j2 @@ -1,3 +1,4 @@ +{%- import "_macros.j2" as pymacros -%} # {{ disclaimer }} # # SPDX-License-Identifier: {{ spdx_license }} @@ -412,12 +413,7 @@ class {{ varname(*class.clsname) }}( {%- else -%} SHACLObject {%- endif -%}): -{%- if class.comment %} - {{ '"' }}{{ '"' }}{{ '"' }} - {%- for l in class.comment.split("\n") %} - {{ l.rstrip() }} - {%- endfor %} - {{ '"' }}{{ '"' }}{{ '"' }} +{%- if class.comment %}{{ pymacros.class_docstring(class.comment) }} {% endif %} TYPE: ClassVar[str] = "{{ class._id }}" {%- if context.compact_iri(class._id) != class._id %} @@ -459,13 +455,7 @@ class {{ varname(*class.clsname) }}( {%- endif %} {%- for prop in class.properties %} {%- set is_list, has_ref, is_enum = prop_shape(prop) %} - {%- if is_enum %} - {%- set ptype = "str" %} - {%- elif has_ref %} - {%- set ptype = "Union[str, '" ~ varname(*classes.get(prop.class_id).clsname) ~ "']" %} - {%- else %} - {%- set ptype = DATATYPE_PYTHON_TYPES[prop.datatype] %} - {%- endif %} + {%- set ptype = prop_element_pytype(prop, classes) %} {%- if is_list %} {{ varname(prop.varname) }}: Optional[Iterable[{{ ptype }}]] = None, {%- else %} @@ -484,13 +474,7 @@ class {{ varname(*class.clsname) }}( {%- endif %} {%- for prop in class.properties %} {%- set is_list, has_ref, is_enum = prop_shape(prop) %} - {%- if is_enum %} - {%- set ptype = "str" %} - {%- elif has_ref %} - {%- set ptype = "Union[str, '" ~ varname(*classes.get(prop.class_id).clsname) ~ "']" %} - {%- else %} - {%- set ptype = DATATYPE_PYTHON_TYPES[prop.datatype] %} - {%- endif %} + {%- set ptype = prop_element_pytype(prop, classes) %} {%- if is_list %} @property def {{ varname(prop.varname) }}(self) -> ListProxy[{{ ptype }}]: ... diff --git a/src/shacl2code/lang/templates/python/protocols.py.j2 b/src/shacl2code/lang/templates/python/protocols.py.j2 index 47fc7b30..8b6dca5c 100644 --- a/src/shacl2code/lang/templates/python/protocols.py.j2 +++ b/src/shacl2code/lang/templates/python/protocols.py.j2 @@ -1,3 +1,4 @@ +{%- import "_macros.j2" as pymacros -%} # {{ disclaimer }} # # SPDX-License-Identifier: {{ spdx_license }} @@ -78,13 +79,7 @@ class {{ varname(*class.clsname) }}( SHACLObjectProtocol {%- endif -%} , Protocol): -{%- if class.comment %} - {{ '"' }}{{ '"' }}{{ '"' }} - {%- for l in class.comment.split("\n") %} - {{ l.rstrip() }} - {%- endfor %} - {{ '"' }}{{ '"' }}{{ '"' }} -{%- endif %} +{%- if class.comment %}{{ pymacros.class_docstring(class.comment) }}{% endif %} def _protocol_{{ varname(class._id) }}(self) -> None: ... {%- if class.id_property %} @@ -99,11 +94,7 @@ class {{ varname(*class.clsname) }}( {%- for prop in class.properties %} {%- set is_list, has_ref, is_enum = prop_shape(prop) %} {%- if not has_ref %} - {%- if is_enum %} - {%- set ptype = "str" %} - {%- else %} - {%- set ptype = DATATYPE_PYTHON_TYPES[prop.datatype] %} - {%- endif %} + {%- set ptype = prop_element_pytype(prop, classes) %} {%- endif %} {%- if has_ref %} {{ varname(prop.varname) }}: Any From cacca2675aad7d411116407fc0c238554ba91f58 Mon Sep 17 00:00:00 2001 From: Arthit Suriyawongkul Date: Wed, 26 Aug 2026 15:10:22 +0700 Subject: [PATCH 26/40] Update year in _macros.j2 Signed-off-by: Arthit Suriyawongkul --- src/shacl2code/lang/templates/python/_macros.j2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shacl2code/lang/templates/python/_macros.j2 b/src/shacl2code/lang/templates/python/_macros.j2 index f1116ab4..47764d98 100644 --- a/src/shacl2code/lang/templates/python/_macros.j2 +++ b/src/shacl2code/lang/templates/python/_macros.j2 @@ -1,5 +1,5 @@ {# -Copyright (c) 2024 Joshua Watt +Copyright (c) 2026 Joshua Watt SPDX-License-Identifier: MIT From a852cf11d421565c6432c7ebb62c05115f96650b Mon Sep 17 00:00:00 2001 From: Arthit Suriyawongkul Date: Thu, 27 Aug 2026 00:58:06 +0700 Subject: [PATCH 27/40] Add test for 4 model versions case Signed-off-by: Arthit Suriyawongkul --- README.md | 36 + src/shacl2code/lang/python.py | 23 + .../lang/templates/python/_macros.j2 | 14 +- .../lang/templates/python/model.py.j2 | 5 +- .../lang/templates/python/model.pyi.j2 | 3 +- .../lang/templates/python/protocols.py.j2 | 11 +- tests/data/model/test-v3.ttl | 657 +++++++++++++++++ tests/data/model/test-v4.ttl | 688 ++++++++++++++++++ tests/test_python.py | 218 +++--- 9 files changed, 1540 insertions(+), 115 deletions(-) create mode 100644 tests/data/model/test-v3.ttl create mode 100644 tests/data/model/test-v4.ttl diff --git a/README.md b/README.md index de4833ea..09a84db7 100644 --- a/README.md +++ b/README.md @@ -330,3 +330,39 @@ status using the following order of precedence (1 = the highest priority): [pytest]: https://www.pytest.org [pytest-cov]: https://pytest-cov.readthedocs.io/en/latest/ [semver]: https://semver.org/ + +### Version-agnostic Protocol types (Python) + +By default, code written against one generated Python module can't accept +objects from another generated module, even if the two were generated from +compatible versions of the same model. + +Passing `--include-protocols yes` to the Python generator adds a +`protocols.py` module with a [`typing.Protocol`][typing-protocol] for every +class in the model. A Protocol accepts an object from *any* generated module +whose version is compatible with the one the Protocol came from, so you can +write functions and classes that work across model versions instead of +being tied to one: + +```shell +shacl2code generate -i model.jsonld python --include-protocols yes -o out +``` + +```python +from out import protocols + +def describe(obj: protocols.MyClass) -> str: + return f"{obj.get_type()}: {obj.my_property}" +``` + +`describe()` accepts a `MyClass` instance from `out`, or from any other +generated module whose `MyClass` is compatible with `out`'s. + +A couple of things to keep in mind: + +- Object-reference properties are typed `Any` on a Protocol; read those + through the concrete module when you need a precise type. +- Protocols are for type annotations only -- construct objects using a + concrete generated module, not a Protocol. + +[typing-protocol]: https://docs.python.org/3/library/typing.html#typing.Protocol diff --git a/src/shacl2code/lang/python.py b/src/shacl2code/lang/python.py index d8d2ccf3..e3844456 100644 --- a/src/shacl2code/lang/python.py +++ b/src/shacl2code/lang/python.py @@ -118,6 +118,28 @@ def protocols_use_datetime(classes: Iterable[Class]) -> bool: return False +def protocols_extra_imports(classes: Iterable[Class]) -> str: + """Conditionally-needed stdlib imports for protocols.py.j2. + + Rendered as a single ``{{ }}`` expression (not a ``{% if %}`` block) so + black can parse the .j2 source as Python. The blank lines black then + requires around that expression separate these imports from the ones + above by more than flake8-import-order allows within one group, so each + line silences that deliberate exception. Always returns a non-blank + line (a comment when there's nothing to import) so the surrounding + black-mandated blank-line groups above and below never merge into one + run long enough to trip flake8's too-many-blank-lines check. + """ + lines = [] + if protocols_use_datetime(classes): + lines.append("from datetime import datetime # noqa: E402, I100, I202") + if any(cls.named_individuals for cls in classes): + lines.append("from typing import ClassVar, Dict # noqa: E402, I100, I202") + if not lines: + lines.append("# No extra imports needed for this model.") + return "\n".join(lines) + + @language("python") class PythonRender(JinjaTemplateRender): """Render Python Language Bindings.""" @@ -203,6 +225,7 @@ def get_extra_env(self): "prop_shape": prop_shape, "prop_element_pytype": prop_element_pytype, "protocols_use_datetime": protocols_use_datetime, + "protocols_extra_imports": protocols_extra_imports, "DATATYPE_CLASSES": DATATYPE_CLASSES, "DATATYPE_PYTHON_TYPES": DATATYPE_PYTHON_TYPES, } diff --git a/src/shacl2code/lang/templates/python/_macros.j2 b/src/shacl2code/lang/templates/python/_macros.j2 index 47764d98..96eda807 100644 --- a/src/shacl2code/lang/templates/python/_macros.j2 +++ b/src/shacl2code/lang/templates/python/_macros.j2 @@ -1,11 +1,9 @@ -{# -Copyright (c) 2026 Joshua Watt - -SPDX-License-Identifier: MIT - -Shared Jinja macros for the Python model.py, model.pyi and protocols.py -templates. -#} +# Copyright (c) 2026 Joshua Watt +# +# SPDX-License-Identifier: MIT +# +# Shared Jinja macros for the Python model.py, model.pyi and protocols.py +# templates. {% macro class_docstring(comment) %} {{ '"' }}{{ '"' }}{{ '"' }} {%- for l in comment.split("\n") %} diff --git a/src/shacl2code/lang/templates/python/model.py.j2 b/src/shacl2code/lang/templates/python/model.py.j2 index 65cd77d4..0b8e75ed 100644 --- a/src/shacl2code/lang/templates/python/model.py.j2 +++ b/src/shacl2code/lang/templates/python/model.py.j2 @@ -1,6 +1,5 @@ -{%- import "_macros.j2" as pymacros -%} #! /usr/bin/env python3 -# +# {%- import "_macros.j2" as pymacros %} # {{ disclaimer }} # # SPDX-License-Identifier: {{ spdx_license }} @@ -3011,7 +3010,7 @@ CONTEXT_URLS: List[str] = [ ] # ONTOLOGIES -{%- for o in ontologies %} +{% for o in ontologies %} {%- if o.comment %} {{ '"' }}{{ '"' }}{{ '"' }} {%- for l in o.comment.split("\n") %} diff --git a/src/shacl2code/lang/templates/python/model.pyi.j2 b/src/shacl2code/lang/templates/python/model.pyi.j2 index f455b2ec..0de6824d 100644 --- a/src/shacl2code/lang/templates/python/model.pyi.j2 +++ b/src/shacl2code/lang/templates/python/model.pyi.j2 @@ -1,6 +1,5 @@ -{%- import "_macros.j2" as pymacros -%} # {{ disclaimer }} -# +# {%- import "_macros.j2" as pymacros %} # SPDX-License-Identifier: {{ spdx_license }} """Generated stub for the generated Python bindings.""" diff --git a/src/shacl2code/lang/templates/python/protocols.py.j2 b/src/shacl2code/lang/templates/python/protocols.py.j2 index 8b6dca5c..b9eb17d2 100644 --- a/src/shacl2code/lang/templates/python/protocols.py.j2 +++ b/src/shacl2code/lang/templates/python/protocols.py.j2 @@ -1,6 +1,5 @@ -{%- import "_macros.j2" as pymacros -%} # {{ disclaimer }} -# +# {%- import "_macros.j2" as pymacros %} # SPDX-License-Identifier: {{ spdx_license }} """Version-agnostic Protocol types, one per generated class. @@ -19,13 +18,9 @@ for annotations. from __future__ import annotations -{% if protocols_use_datetime(classes) -%} -from datetime import datetime -{% endif -%} from typing import Any, Iterable, Iterator, Optional, Protocol, Set, Tuple -{%- if classes | selectattr("named_individuals") | list | length > 0 %} -from typing import ClassVar, Dict -{%- endif %} + +{{protocols_extra_imports(classes)}} class SHACLObjectProtocol(Protocol): diff --git a/tests/data/model/test-v3.ttl b/tests/data/model/test-v3.ttl new file mode 100644 index 00000000..31f3e0ad --- /dev/null +++ b/tests/data/model/test-v3.ttl @@ -0,0 +1,657 @@ +# Backward-compatible extension of test-v2.ttl for cross-version Protocol tests. +# Adds (on top of v2's parent-class/v2-new-prop and test-another-class): +# - test-class/v3-new-prop: new optional scalar on test-class. +# - test-derived-class-v3: new class, one level deeper than test-derived-class. +# - enumType/baz: new named individual (enum value growth). +# - test-class/split-string-prop marked owl:DeprecatedProperty (still present). +@base . +@prefix rdf: . +@prefix rdfs: . +@prefix sh: . +@prefix owl: . +@prefix xsd: . +@prefix sh-to-code: . + + a sh:NodeShape, owl:Class ; + rdfs:subClassOf ; + rdfs:comment "Derived class that sorts before the parent to test ordering" + . + + a sh:NodeShape, owl:Class ; + rdfs:comment "The parent class" ; + sh:property [ + sh:datatype xsd:string ; + sh:path ; + sh:maxCount 1 + ] + . + + + a sh:NodeShape, owl:Class ; + rdfs:subClassOf ; + rdfs:comment "The test class" ; + sh:property [ + sh:datatype xsd:string ; + sh:path + ], + [ + sh:path + ], + [ + sh:datatype xsd:string ; + sh:path ; + sh:maxCount 1 + ], + [ + sh:datatype xsd:string ; + sh:name "named_property" ; + sh:path ; + sh:maxCount 1 + ], + [ + sh:datatype xsd:dateTime ; + sh:path ; + sh:maxCount 1 + ], + [ + sh:datatype xsd:dateTime ; + sh:path ; + ], + [ + sh:datatype xsd:dateTimeStamp ; + sh:path ; + sh:maxCount 1 + ], + [ + sh:datatype xsd:positiveInteger ; + sh:path ; + sh:maxCount 1 + ], + [ + sh:datatype xsd:nonNegativeInteger ; + sh:path ; + sh:maxCount 1 + ], + [ + sh:datatype xsd:integer ; + sh:path ; + sh:maxCount 1 + ], + [ + sh:datatype xsd:anyURI ; + sh:path ; + sh:maxCount 1 + ], + [ + sh:datatype xsd:boolean ; + sh:path ; + sh:maxCount 1 + ], + [ + sh:datatype xsd:decimal ; + sh:path ; + sh:maxCount 1 + ], + [ + sh:class ; + sh:path ; + sh:maxCount 1 + ], + [ + sh:path ; + sh:maxCount 1 + ], + [ + sh:class ; + sh:path ; + ], + [ + sh:class ; + sh:path ; + sh:maxCount 1 ; + sh:in ( + + + + + ) + ], + [ + sh:class ; + sh:path ; + sh:in ( + + + + + ) + ], + [ + sh:path ; + sh:maxCount 1 ; + sh:in ( + + + + + ) + ], + [ + sh:datatype xsd:string ; + sh:pattern "^foo\\d" ; + sh:path ; + sh:maxCount 1 + + ], + [ + sh:datatype xsd:string ; + sh:pattern "^foo\\d" ; + sh:path ; + ], + [ + sh:datatype xsd:string ; + sh:path ; + sh:maxCount 1 + ], + [ + sh:datatype xsd:string ; + sh:path ; + sh:maxCount 1 + ], + [ + sh:datatype xsd:dateTime ; + sh:path ; + sh:maxCount 1 ; + sh:pattern "^\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\d\\+01:00$" + ], + [ + sh:datatype xsd:dateTimeStamp ; + sh:path ; + sh:maxCount 1 ; + sh:pattern "^\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\dZ$" + ], + [ + sh:class ; + sh:path ; + sh:maxCount 1 + ], + [ + sh:path ; + sh:datatype xsd:string + ], + [ + sh:path ; + sh:name "split" ; + sh:maxCount 1 + ], + [ + sh:datatype xsd:string ; + sh:path ; + sh:maxCount 1 + ] + . + + a owl:NamedIndividual, ; + rdfs:label "A named individual of the test class" + . + + a sh:NodeShape, owl:Class ; + rdfs:subClassOf ; + sh:property [ + sh:datatype xsd:string ; + sh:path ; + sh:maxCount 1 ; + sh:minCount 1 + ], + [ + sh:datatype xsd:string ; + sh:path ; + sh:minCount 1 ; + sh:maxCount 2 + ] + . + + a sh:NodeShape, owl:Class ; + rdfs:subClassOf ; + rdfs:comment "A class derived from test-class" ; + sh:property [ + sh:datatype xsd:string ; + sh:path ; + sh:maxCount 1 + ] + . + + a sh:NodeShape, owl:Class ; + rdfs:subClassOf ; + rdfs:comment "A class derived from test-derived-class, added in v3" ; + sh:property [ + sh:datatype xsd:string ; + sh:path ; + sh:maxCount 1 + ] + . + + a sh:NodeShape, owl:Class ; + rdfs:comment "Another class" + . + + a sh:NodeShape, owl:DeprecatedClass ; + rdfs:subClassOf ; + rdfs:comment "A deprecated class" ; + sh:property [ + sh:datatype: xsd:string ; + sh:path ; + sh:maxCount 1 + ] + . + + a owl:DeprecatedProperty ; + rdfs:comment "A deprecated property" ; + rdfs:range xsd:string + . + + a rdf:Property ; + rdfs:comment "A string list property" ; + . + + a rdf:Property ; + rdfs:comment "A string list property with no sh:datatype" ; + rdfs:range xsd:string + . + + a rdf:Property ; + rdfs:comment "A scalar string propery" ; + rdfs:range xsd:string + . + + a rdf:Property ; + rdfs:comment "A required scalar string property" ; + rdfs:range xsd:string + . + + a rdf:Property ; + rdfs:comment "A required string list property" ; + rdfs:range xsd:string + . + + a rdf:Property ; + rdfs:comment "A named property"; + rdfs:range xsd:string + . + + a rdf:Property ; + rdfs:comment "A scalar datetime property"; + rdfs:range xsd:dateTime + . + + a rdf:Property ; + rdfs:comment "A datetime list property" ; + rdfs:range xsd:dateTime + . + + a rdf:Property ; + rdfs:comment "A scalar dateTimeStamp property"; + rdfs:range xsd:dateTimeStamp + . + + a rdf:Property ; + rdfs:comment "A positive integer" ; + rdfs:range xsd:positiveInteger + . + + a rdf:Property ; + rdfs:comment "a non-negative integer" ; + rdfs:range xsd:nonNegativeInteger + . + + a rdf:Property ; + rdfs:comment "a non-negative integer" ; + rdfs:range xsd:integer + . + + a rdf:Property ; + rdfs:comment "a URI" ; + rdfs:range xsd:anyURI + . + + a rdf:Property ; + rdfs:comment "a boolean property" ; + rdfs:range xsd:boolean + . + + a rdf:Property ; + rdfs:comment "a float property" ; + rdfs:range xsd:decimal + . + + a rdf:Property ; + rdfs:comment "A test-class property" ; + rdfs:range + . + + a rdf:Property ; + rdfs:comment "A test-class property with no sh:class" ; + rdfs:range + . + + a rdf:Property ; + rdfs:comment "A test-class list property" ; + rdfs:range + . + + a rdf:Property ; + rdfs:comment "A enum property" ; + rdfs:range + . + + a rdf:Property ; + rdfs:comment "A enum list property" ; + rdfs:range + . + + a rdf:Property ; + rdfs:comment "A enum property with no sh:class" ; + rdfs:range + . + + a rdf:Property ; + rdfs:comment "A regex validated string" ; + rdfs:range xsd:string + . + + a rdf:Property ; + rdfs:comment "A regex validated string list" ; + rdfs:range xsd:string + . + + a rdf:Property, owl:DeprecatedProperty ; + rdfs:comment "A split string property, deprecated in v3" ; + rdfs:range xsd:string + . + + a rdf:Property ; + rdfs:comment "A new optional scalar property, added in v3" ; + rdfs:range xsd:string + . + + a rdf:Property ; + rdfs:comment "A property that is a keyword" ; + rdfs:range xsd:string + . + + a rdf:Property ; + rdfs:comment "A property that conflicts with an existing SHACLObject property" ; + rdfs:range xsd:string + . + + a rdf:Property ; + rdfs:comment "A regex dateTime" ; + rdfs:range xsd:dateTime + . + + a rdf:Property ; + rdfs:comment "A regex dateTimeStamp" ; + rdfs:range xsd:dateTimeStamp + . + + a rdf:Property ; + rdfs:comment "A class with no shape" ; + rdfs:range + . + + a rdf:Property ; + rdfs:comment "A string property in a derived class" ; + rdfs:range xsd:string + . + + a rdf:Property ; + rdfs:comment "A string property in test-derived-class-v3" ; + rdfs:range xsd:string + . + + a owl:Class ; + rdfs:comment "A class that is not a nodeshape" + . + + a owl:Class ; + rdfs:comment "An enumerated type" + . + + a owl:NamedIndividual, ; + rdfs:label "foo" ; + rdfs:comment "The foo value of enumType" + . + + a owl:NamedIndividual, ; + rdfs:label "bar" ; + rdfs:comment "The bar value of enumType" + . + + a owl:NamedIndividual, ; + rdfs:comment "This value has no label" + . + + a owl:NamedIndividual, ; + rdfs:label "baz" ; + rdfs:comment "The baz value of enumType, added in v3" + . + + a ; + rdfs:comment "This value is not a named individual and won't appear in the output" + . + +# Classes to test links + + a sh:NodeShape, owl:Class ; + rdfs:comment "A class to test links" ; + sh:property [ + sh:class ; + sh:path ; + sh:maxCount 1 + ], + [ + sh:path ; + sh:maxCount 1 + ], + [ + sh:class ; + sh:path ; + ], + [ + sh:path ; + sh:maxCount 1 + ], + [ + sh:datatype xsd:string ; + sh:path ; + sh:maxCount 1 + ], + [ + sh:class ; + sh:path ; + sh:maxCount 1 + ] + . + +# Note: link-derived-class and link-derived-2-class should both have no +# properties to test an edge case in the go bindings + a sh:NodeShape, owl:Class ; + rdfs:subClassOf ; + rdfs:comment "A class derived from link-class" + . + + a sh:NodeShape, owl:Class ; + rdfs:subClassOf ; + rdfs:comment "A class derived from link-class" + . + + a rdf:Property ; + rdfs:comment "A link-class property" ; + rdfs:range + . + + a rdf:Property ; + rdfs:comment "A link-class property with no sh:class" ; + rdfs:range + . + + a rdf:Property ; + rdfs:comment "A link-class list property" ; + rdfs:range + . + + a rdf:Property ; + rdfs:comment "A link to an extensible-class" ; + rdfs:range + . + + a rdf:Property ; + rdfs:comment "Tag used to identify object for testing" ; + rdfs:range xsd:string + . + + a rdf:Property ; + rdfs:comment "A link to a derived class" ; + rdfs:range + . + + a rdf:Class, sh:NodeShape, owl:Class ; + rdfs:comment "A class with an ID alias" ; + sh-to-code:idPropertyName "testid" + . + + a rdf:Class, sh:NodeShape, owl:Class ; + rdfs:subClassOf ; + rdfs:comment "A class that inherits its idPropertyName from the parent" + . + + a rdf:Class, sh:NodeShape, owl:Class ; + rdfs:subClassOf ; + rdfs:comment "A class that must be a blank node" ; + sh:nodeKind sh:BlankNode + . + + a rdf:Class, sh:NodeShape, owl:Class ; + rdfs:subClassOf ; + rdfs:comment "A class that must be an IRI" ; + sh:nodeKind sh:IRI + . + + a rdf:Class, sh:NodeShape, owl:Class ; + rdfs:subClassOf ; + rdfs:comment "A class that can be either a blank node or an IRI" ; + sh:nodeKind sh:BlankNodeOrIRI + . + + a rdf:Class, sh:NodeShape, owl:Class ; + rdfs:subClassOf ; + rdfs:comment "A class that derives its nodeKind from parent" ; + . + + a rdf:Class, sh:NodeShape, owl:Class ; + rdfs:subClassOf ; + sh-to-code:isExtensible true ; + rdfs:comment "An extensible class" ; + sh:property [ + sh:datatype xsd:string ; + sh:path ; + sh:maxCount 1 ; + sh:minCount 0 + ], + [ + sh:datatype xsd:string ; + sh:path ; + sh:maxCount 1 ; + sh:minCount 1 + ] + . + + a rdf:Property ; + rdfs:comment "An extensible property" ; + rdfs:range xsd:string + . + + a rdf:Property ; + rdfs:comment "A required extensible property" ; + rdfs:range xsd:string + . + + a rdf:Class, sh:NodeShape, owl:Class ; + rdfs:comment "An Abstract class" ; + sh-to-code:isAbstract true + . + + a rdf:Class, sh:NodeShape, owl:Class, ; + rdfs:comment "An Abstract class using the SPDX type" + . + + a rdf:Class, sh:NodeShape, owl:Class ; + rdfs:comment: "An Abstract class using SHACL validation" ; + sh:property [ + sh:path rdf:type ; + sh:not [ sh:hasValue ] + ] . + + a rdf:Class, sh:NodeShape, owl:Class ; + rdfs:subClassOf ; + rdfs:comment "A concrete class" + . + + a rdf:Class, sh:NodeShape, owl:Class ; + rdfs:subClassOf ; + rdfs:comment "A concrete class" + . + + a rdf:Class, sh:NodeShape, owl:Class ; + rdfs:subClassOf ; + rdfs:comment "A concrete class" + . + + a rdf:Class, sh:NodeShape, owl:Class ; + rdfs:comment "A class with a mandatory abstract class" ; + sh:property [ + sh:class ; + sh:path ; + sh:minCount 1 ; + sh:maxCount 1 + ] + . + + a rdf:Property ; + rdfs:comment "A required abstract class property" ; + rdfs:range + . + + a rdf:Class, sh:NodeShape, owl:Class ; + sh-to-code:isExtensible true ; + sh-to-code:isAbstract true ; + rdfs:comment "An extensible abstract class" + . + + a rdf:Class, sh:NodeShape, owl:Class ; + rdfs:comment "A class that uses an abstract extensible class" ; + sh:property [ + sh:path ; + sh:minCount 1 ; + sh:maxCount 1 + ] + . + + a rdf:Property ; + rdfs:comment "A property that references and abstract extensible class" ; + rdfs:range + . + + a sh:NodeShape, owl:Class ; + rdfs:comment "Another class with the same own property set as test-class (for discriminator testing)" ; + sh:property [ + sh:class ; + sh:path ; + sh:maxCount 1 + ], + [ + sh:datatype xsd:string ; + sh:path ; + sh:maxCount 1 + ] + . diff --git a/tests/data/model/test-v4.ttl b/tests/data/model/test-v4.ttl new file mode 100644 index 00000000..42fde60f --- /dev/null +++ b/tests/data/model/test-v4.ttl @@ -0,0 +1,688 @@ +# Backward-compatible extension of test-v3.ttl for cross-version Protocol tests. +# Adds (on top of v3's test-class/v3-new-prop, test-derived-class-v3, +# enumType/baz, and the deprecated split-string-prop): +# - test-class/v4-new-prop: new optional scalar on test-class. +# - test-derived-class-v4: new class, one level deeper than test-derived-class-v3. +# - enumType/qux: new named individual (enum value growth). +# - test-another-class marked owl:DeprecatedClass (still present, still usable). +@base . +@prefix rdf: . +@prefix rdfs: . +@prefix sh: . +@prefix owl: . +@prefix xsd: . +@prefix sh-to-code: . + + a sh:NodeShape, owl:Class ; + rdfs:subClassOf ; + rdfs:comment "Derived class that sorts before the parent to test ordering" + . + + a sh:NodeShape, owl:Class ; + rdfs:comment "The parent class" ; + sh:property [ + sh:datatype xsd:string ; + sh:path ; + sh:maxCount 1 + ] + . + + + a sh:NodeShape, owl:Class ; + rdfs:subClassOf ; + rdfs:comment "The test class" ; + sh:property [ + sh:datatype xsd:string ; + sh:path + ], + [ + sh:path + ], + [ + sh:datatype xsd:string ; + sh:path ; + sh:maxCount 1 + ], + [ + sh:datatype xsd:string ; + sh:name "named_property" ; + sh:path ; + sh:maxCount 1 + ], + [ + sh:datatype xsd:dateTime ; + sh:path ; + sh:maxCount 1 + ], + [ + sh:datatype xsd:dateTime ; + sh:path ; + ], + [ + sh:datatype xsd:dateTimeStamp ; + sh:path ; + sh:maxCount 1 + ], + [ + sh:datatype xsd:positiveInteger ; + sh:path ; + sh:maxCount 1 + ], + [ + sh:datatype xsd:nonNegativeInteger ; + sh:path ; + sh:maxCount 1 + ], + [ + sh:datatype xsd:integer ; + sh:path ; + sh:maxCount 1 + ], + [ + sh:datatype xsd:anyURI ; + sh:path ; + sh:maxCount 1 + ], + [ + sh:datatype xsd:boolean ; + sh:path ; + sh:maxCount 1 + ], + [ + sh:datatype xsd:decimal ; + sh:path ; + sh:maxCount 1 + ], + [ + sh:class ; + sh:path ; + sh:maxCount 1 + ], + [ + sh:path ; + sh:maxCount 1 + ], + [ + sh:class ; + sh:path ; + ], + [ + sh:class ; + sh:path ; + sh:maxCount 1 ; + sh:in ( + + + + + ) + ], + [ + sh:class ; + sh:path ; + sh:in ( + + + + + ) + ], + [ + sh:path ; + sh:maxCount 1 ; + sh:in ( + + + + + ) + ], + [ + sh:datatype xsd:string ; + sh:pattern "^foo\\d" ; + sh:path ; + sh:maxCount 1 + + ], + [ + sh:datatype xsd:string ; + sh:pattern "^foo\\d" ; + sh:path ; + ], + [ + sh:datatype xsd:string ; + sh:path ; + sh:maxCount 1 + ], + [ + sh:datatype xsd:string ; + sh:path ; + sh:maxCount 1 + ], + [ + sh:datatype xsd:dateTime ; + sh:path ; + sh:maxCount 1 ; + sh:pattern "^\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\d\\+01:00$" + ], + [ + sh:datatype xsd:dateTimeStamp ; + sh:path ; + sh:maxCount 1 ; + sh:pattern "^\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\dZ$" + ], + [ + sh:class ; + sh:path ; + sh:maxCount 1 + ], + [ + sh:path ; + sh:datatype xsd:string + ], + [ + sh:path ; + sh:name "split" ; + sh:maxCount 1 + ], + [ + sh:datatype xsd:string ; + sh:path ; + sh:maxCount 1 + ], + [ + sh:datatype xsd:string ; + sh:path ; + sh:maxCount 1 + ] + . + + a owl:NamedIndividual, ; + rdfs:label "A named individual of the test class" + . + + a sh:NodeShape, owl:Class ; + rdfs:subClassOf ; + sh:property [ + sh:datatype xsd:string ; + sh:path ; + sh:maxCount 1 ; + sh:minCount 1 + ], + [ + sh:datatype xsd:string ; + sh:path ; + sh:minCount 1 ; + sh:maxCount 2 + ] + . + + a sh:NodeShape, owl:Class ; + rdfs:subClassOf ; + rdfs:comment "A class derived from test-class" ; + sh:property [ + sh:datatype xsd:string ; + sh:path ; + sh:maxCount 1 + ] + . + + a sh:NodeShape, owl:Class ; + rdfs:subClassOf ; + rdfs:comment "A class derived from test-derived-class, added in v3" ; + sh:property [ + sh:datatype xsd:string ; + sh:path ; + sh:maxCount 1 + ] + . + + a sh:NodeShape, owl:Class ; + rdfs:subClassOf ; + rdfs:comment "A class derived from test-derived-class-v3, added in v4" ; + sh:property [ + sh:datatype xsd:string ; + sh:path ; + sh:maxCount 1 + ] + . + + a sh:NodeShape, owl:Class ; + rdfs:comment "Another class" + . + + a sh:NodeShape, owl:DeprecatedClass ; + rdfs:subClassOf ; + rdfs:comment "A deprecated class" ; + sh:property [ + sh:datatype: xsd:string ; + sh:path ; + sh:maxCount 1 + ] + . + + a owl:DeprecatedProperty ; + rdfs:comment "A deprecated property" ; + rdfs:range xsd:string + . + + a rdf:Property ; + rdfs:comment "A string list property" ; + . + + a rdf:Property ; + rdfs:comment "A string list property with no sh:datatype" ; + rdfs:range xsd:string + . + + a rdf:Property ; + rdfs:comment "A scalar string propery" ; + rdfs:range xsd:string + . + + a rdf:Property ; + rdfs:comment "A required scalar string property" ; + rdfs:range xsd:string + . + + a rdf:Property ; + rdfs:comment "A required string list property" ; + rdfs:range xsd:string + . + + a rdf:Property ; + rdfs:comment "A named property"; + rdfs:range xsd:string + . + + a rdf:Property ; + rdfs:comment "A scalar datetime property"; + rdfs:range xsd:dateTime + . + + a rdf:Property ; + rdfs:comment "A datetime list property" ; + rdfs:range xsd:dateTime + . + + a rdf:Property ; + rdfs:comment "A scalar dateTimeStamp property"; + rdfs:range xsd:dateTimeStamp + . + + a rdf:Property ; + rdfs:comment "A positive integer" ; + rdfs:range xsd:positiveInteger + . + + a rdf:Property ; + rdfs:comment "a non-negative integer" ; + rdfs:range xsd:nonNegativeInteger + . + + a rdf:Property ; + rdfs:comment "a non-negative integer" ; + rdfs:range xsd:integer + . + + a rdf:Property ; + rdfs:comment "a URI" ; + rdfs:range xsd:anyURI + . + + a rdf:Property ; + rdfs:comment "a boolean property" ; + rdfs:range xsd:boolean + . + + a rdf:Property ; + rdfs:comment "a float property" ; + rdfs:range xsd:decimal + . + + a rdf:Property ; + rdfs:comment "A test-class property" ; + rdfs:range + . + + a rdf:Property ; + rdfs:comment "A test-class property with no sh:class" ; + rdfs:range + . + + a rdf:Property ; + rdfs:comment "A test-class list property" ; + rdfs:range + . + + a rdf:Property ; + rdfs:comment "A enum property" ; + rdfs:range + . + + a rdf:Property ; + rdfs:comment "A enum list property" ; + rdfs:range + . + + a rdf:Property ; + rdfs:comment "A enum property with no sh:class" ; + rdfs:range + . + + a rdf:Property ; + rdfs:comment "A regex validated string" ; + rdfs:range xsd:string + . + + a rdf:Property ; + rdfs:comment "A regex validated string list" ; + rdfs:range xsd:string + . + + a rdf:Property, owl:DeprecatedProperty ; + rdfs:comment "A split string property, deprecated in v3" ; + rdfs:range xsd:string + . + + a rdf:Property ; + rdfs:comment "A new optional scalar property, added in v3" ; + rdfs:range xsd:string + . + + a rdf:Property ; + rdfs:comment "A new optional scalar property, added in v4" ; + rdfs:range xsd:string + . + + a rdf:Property ; + rdfs:comment "A property that is a keyword" ; + rdfs:range xsd:string + . + + a rdf:Property ; + rdfs:comment "A property that conflicts with an existing SHACLObject property" ; + rdfs:range xsd:string + . + + a rdf:Property ; + rdfs:comment "A regex dateTime" ; + rdfs:range xsd:dateTime + . + + a rdf:Property ; + rdfs:comment "A regex dateTimeStamp" ; + rdfs:range xsd:dateTimeStamp + . + + a rdf:Property ; + rdfs:comment "A class with no shape" ; + rdfs:range + . + + a rdf:Property ; + rdfs:comment "A string property in a derived class" ; + rdfs:range xsd:string + . + + a rdf:Property ; + rdfs:comment "A string property in test-derived-class-v3" ; + rdfs:range xsd:string + . + + a rdf:Property ; + rdfs:comment "A string property in test-derived-class-v4" ; + rdfs:range xsd:string + . + + a owl:Class ; + rdfs:comment "A class that is not a nodeshape" + . + + a owl:Class ; + rdfs:comment "An enumerated type" + . + + a owl:NamedIndividual, ; + rdfs:label "foo" ; + rdfs:comment "The foo value of enumType" + . + + a owl:NamedIndividual, ; + rdfs:label "bar" ; + rdfs:comment "The bar value of enumType" + . + + a owl:NamedIndividual, ; + rdfs:comment "This value has no label" + . + + a owl:NamedIndividual, ; + rdfs:label "baz" ; + rdfs:comment "The baz value of enumType, added in v3" + . + + a owl:NamedIndividual, ; + rdfs:label "qux" ; + rdfs:comment "The qux value of enumType, added in v4" + . + + a ; + rdfs:comment "This value is not a named individual and won't appear in the output" + . + +# Classes to test links + + a sh:NodeShape, owl:Class ; + rdfs:comment "A class to test links" ; + sh:property [ + sh:class ; + sh:path ; + sh:maxCount 1 + ], + [ + sh:path ; + sh:maxCount 1 + ], + [ + sh:class ; + sh:path ; + ], + [ + sh:path ; + sh:maxCount 1 + ], + [ + sh:datatype xsd:string ; + sh:path ; + sh:maxCount 1 + ], + [ + sh:class ; + sh:path ; + sh:maxCount 1 + ] + . + +# Note: link-derived-class and link-derived-2-class should both have no +# properties to test an edge case in the go bindings + a sh:NodeShape, owl:Class ; + rdfs:subClassOf ; + rdfs:comment "A class derived from link-class" + . + + a sh:NodeShape, owl:Class ; + rdfs:subClassOf ; + rdfs:comment "A class derived from link-class" + . + + a rdf:Property ; + rdfs:comment "A link-class property" ; + rdfs:range + . + + a rdf:Property ; + rdfs:comment "A link-class property with no sh:class" ; + rdfs:range + . + + a rdf:Property ; + rdfs:comment "A link-class list property" ; + rdfs:range + . + + a rdf:Property ; + rdfs:comment "A link to an extensible-class" ; + rdfs:range + . + + a rdf:Property ; + rdfs:comment "Tag used to identify object for testing" ; + rdfs:range xsd:string + . + + a rdf:Property ; + rdfs:comment "A link to a derived class" ; + rdfs:range + . + + a rdf:Class, sh:NodeShape, owl:Class ; + rdfs:comment "A class with an ID alias" ; + sh-to-code:idPropertyName "testid" + . + + a rdf:Class, sh:NodeShape, owl:Class ; + rdfs:subClassOf ; + rdfs:comment "A class that inherits its idPropertyName from the parent" + . + + a rdf:Class, sh:NodeShape, owl:Class ; + rdfs:subClassOf ; + rdfs:comment "A class that must be a blank node" ; + sh:nodeKind sh:BlankNode + . + + a rdf:Class, sh:NodeShape, owl:Class ; + rdfs:subClassOf ; + rdfs:comment "A class that must be an IRI" ; + sh:nodeKind sh:IRI + . + + a rdf:Class, sh:NodeShape, owl:Class ; + rdfs:subClassOf ; + rdfs:comment "A class that can be either a blank node or an IRI" ; + sh:nodeKind sh:BlankNodeOrIRI + . + + a rdf:Class, sh:NodeShape, owl:Class ; + rdfs:subClassOf ; + rdfs:comment "A class that derives its nodeKind from parent" ; + . + + a rdf:Class, sh:NodeShape, owl:Class ; + rdfs:subClassOf ; + sh-to-code:isExtensible true ; + rdfs:comment "An extensible class" ; + sh:property [ + sh:datatype xsd:string ; + sh:path ; + sh:maxCount 1 ; + sh:minCount 0 + ], + [ + sh:datatype xsd:string ; + sh:path ; + sh:maxCount 1 ; + sh:minCount 1 + ] + . + + a rdf:Property ; + rdfs:comment "An extensible property" ; + rdfs:range xsd:string + . + + a rdf:Property ; + rdfs:comment "A required extensible property" ; + rdfs:range xsd:string + . + + a rdf:Class, sh:NodeShape, owl:Class ; + rdfs:comment "An Abstract class" ; + sh-to-code:isAbstract true + . + + a rdf:Class, sh:NodeShape, owl:Class, ; + rdfs:comment "An Abstract class using the SPDX type" + . + + a rdf:Class, sh:NodeShape, owl:Class ; + rdfs:comment: "An Abstract class using SHACL validation" ; + sh:property [ + sh:path rdf:type ; + sh:not [ sh:hasValue ] + ] . + + a rdf:Class, sh:NodeShape, owl:Class ; + rdfs:subClassOf ; + rdfs:comment "A concrete class" + . + + a rdf:Class, sh:NodeShape, owl:Class ; + rdfs:subClassOf ; + rdfs:comment "A concrete class" + . + + a rdf:Class, sh:NodeShape, owl:Class ; + rdfs:subClassOf ; + rdfs:comment "A concrete class" + . + + a rdf:Class, sh:NodeShape, owl:Class ; + rdfs:comment "A class with a mandatory abstract class" ; + sh:property [ + sh:class ; + sh:path ; + sh:minCount 1 ; + sh:maxCount 1 + ] + . + + a rdf:Property ; + rdfs:comment "A required abstract class property" ; + rdfs:range + . + + a rdf:Class, sh:NodeShape, owl:Class ; + sh-to-code:isExtensible true ; + sh-to-code:isAbstract true ; + rdfs:comment "An extensible abstract class" + . + + a rdf:Class, sh:NodeShape, owl:Class ; + rdfs:comment "A class that uses an abstract extensible class" ; + sh:property [ + sh:path ; + sh:minCount 1 ; + sh:maxCount 1 + ] + . + + a rdf:Property ; + rdfs:comment "A property that references and abstract extensible class" ; + rdfs:range + . + + a sh:NodeShape, owl:Class, owl:DeprecatedClass ; + rdfs:comment "Another class with the same own property set as test-class (for discriminator testing). Marked deprecated in v4." ; + sh:property [ + sh:class ; + sh:path ; + sh:maxCount 1 + ], + [ + sh:datatype xsd:string ; + sh:path ; + sh:maxCount 1 + ] + . diff --git a/tests/test_python.py b/tests/test_python.py index 01c79433..3a6899ea 100644 --- a/tests/test_python.py +++ b/tests/test_python.py @@ -100,13 +100,19 @@ def python_model(tmp_path_factory, model_context_url): yield tmp_directory, module_name -@pytest.fixture -def python_model_env(python_model): - module_path, module_name = python_model +def _env_with_pythonpath(*paths: Path) -> "dict[str, str]": + """A copy of the current environment with `paths` appended to PYTHONPATH.""" env = os.environ.copy() env["PYTHONPATH"] = os.pathsep.join( - env.get("PYTHONPATH", "").split(os.pathsep) + [str(module_path)] + env.get("PYTHONPATH", "").split(os.pathsep) + [str(p) for p in paths] ) + return env + + +@pytest.fixture +def python_model_env(python_model): + module_path, module_name = python_model + env = _env_with_pythonpath(module_path) return env, module_name @@ -2973,6 +2979,8 @@ def test_pre_release_multi_valued_annotations(tmp_path, model_context_url): # --------------------------------------------------------------------------- TEST_V2_MODEL = THIS_DIR / "data" / "model" / "test-v2.ttl" +TEST_V3_MODEL = THIS_DIR / "data" / "model" / "test-v3.ttl" +TEST_V4_MODEL = THIS_DIR / "data" / "model" / "test-v4.ttl" NO_DATETIME_MODEL = DATA_DIR / "no-datetime.ttl" @@ -3021,16 +3029,18 @@ def test_raises_on_unknown_datatype(self) -> None: protocols_use_datetime([bad_class]) -@pytest.fixture(scope="module") -def python_model_v1_protocols( - tmp_path_factory: pytest.TempPathFactory, model_context_url: str +def _generate_protocols_fixture( + tmp_path_factory: pytest.TempPathFactory, + model_context_url: str, + model_path: Path, + version: str, ) -> Tuple[Path, str]: - """v1 model generated with --include-protocols yes.""" - tmp_directory = tmp_path_factory.mktemp("protocols_v1") - module_name = "pymodel_v1" + """Generate a --include-protocols yes module for one version fixture.""" + tmp_directory = tmp_path_factory.mktemp(f"protocols_{version}") + module_name = f"pymodel_{version}" output_dir = tmp_directory / module_name shacl2code_generate( - ["--input", TEST_MODEL, "--context", model_context_url], + ["--input", model_path, "--context", model_context_url], ["--include-protocols", "yes"], output_dir, ) @@ -3038,21 +3048,44 @@ def python_model_v1_protocols( return tmp_directory, module_name +@pytest.fixture(scope="module") +def python_model_v1_protocols( + tmp_path_factory: pytest.TempPathFactory, model_context_url: str +) -> Tuple[Path, str]: + """v1 model generated with --include-protocols yes.""" + return _generate_protocols_fixture( + tmp_path_factory, model_context_url, TEST_MODEL, "v1" + ) + + @pytest.fixture(scope="module") def python_model_v2_protocols( tmp_path_factory: pytest.TempPathFactory, model_context_url: str ) -> Tuple[Path, str]: """v2 model (backward-compatible extension) generated with --include-protocols yes.""" - tmp_directory = tmp_path_factory.mktemp("protocols_v2") - module_name = "pymodel_v2" - output_dir = tmp_directory / module_name - shacl2code_generate( - ["--input", TEST_V2_MODEL, "--context", model_context_url], - ["--include-protocols", "yes"], - output_dir, + return _generate_protocols_fixture( + tmp_path_factory, model_context_url, TEST_V2_MODEL, "v2" + ) + + +@pytest.fixture(scope="module") +def python_model_v3_protocols( + tmp_path_factory: pytest.TempPathFactory, model_context_url: str +) -> Tuple[Path, str]: + """v3 model (backward-compatible extension of v2) with --include-protocols yes.""" + return _generate_protocols_fixture( + tmp_path_factory, model_context_url, TEST_V3_MODEL, "v3" + ) + + +@pytest.fixture(scope="module") +def python_model_v4_protocols( + tmp_path_factory: pytest.TempPathFactory, model_context_url: str +) -> Tuple[Path, str]: + """v4 model (backward-compatible extension of v3) with --include-protocols yes.""" + return _generate_protocols_fixture( + tmp_path_factory, model_context_url, TEST_V4_MODEL, "v4" ) - (output_dir / "py.typed").touch() - return tmp_directory, module_name class TestProtocolOutput: @@ -3061,15 +3094,10 @@ class TestProtocolOutput: """ def test_protocols_file_generated( - self, tmp_path: Path, model_context_url: str + self, python_model_v1_protocols: Tuple[Path, str] ) -> None: - output_dir = tmp_path / "pymodel" - shacl2code_generate( - ["--input", TEST_MODEL, "--context", model_context_url], - ["--include-protocols", "yes"], - output_dir, - ) - assert (output_dir / "protocols.py").exists() + output_path, module_name = python_model_v1_protocols + assert (output_path / module_name / "protocols.py").exists() def test_protocols_file_not_generated_by_default( self, tmp_path: Path, model_context_url: str @@ -3083,22 +3111,16 @@ def test_protocols_file_not_generated_by_default( assert not (output_dir / "protocols.py").exists() def test_dir_includes_lazy_names( - self, tmp_path: Path, model_context_url: str + self, python_model_v1_protocols: Tuple[Path, str] ) -> None: """ __dir__() must expose model classes, "protocols", and "main" for dir()/tab-completion even though they are loaded lazily via __getattr__ (PEP 562). """ - module_name = "pymodel_dir_check" - output_dir = tmp_path / module_name - shacl2code_generate( - ["--input", TEST_MODEL, "--context", model_context_url], - ["--include-protocols", "yes"], - output_dir, - ) + output_path, module_name = python_model_v1_protocols - sys.path.insert(0, str(tmp_path)) + sys.path.insert(0, str(output_path)) try: pkg = importlib.import_module(module_name) names = dir(pkg) @@ -3115,46 +3137,40 @@ def test_dir_includes_lazy_names( assert "SHACLObjectProtocol" in proto_names assert "test_class" in proto_names finally: - sys.path.remove(str(tmp_path)) + sys.path.remove(str(output_path)) for m in list(sys.modules): if m == module_name or m.startswith(module_name + "."): del sys.modules[m] - def test_mypy(self, tmp_path: Path, model_context_url: str) -> None: - output_dir = tmp_path / "pymodel" - shacl2code_generate( - ["--input", TEST_MODEL, "--context", model_context_url], - ["--include-protocols", "yes"], - output_dir, + def test_mypy(self, python_model_v1_protocols: Tuple[Path, str]) -> None: + output_path, module_name = python_model_v1_protocols + subprocess.run( + ["mypy", output_path / module_name], encoding="utf-8", check=True ) - (output_dir / "py.typed").touch() - subprocess.run(["mypy", output_dir], encoding="utf-8", check=True) - def test_flake8(self, tmp_path: Path, model_context_url: str) -> None: - output_dir = tmp_path / "pymodel" - shacl2code_generate( - ["--input", TEST_MODEL, "--context", model_context_url], - ["--include-protocols", "yes"], - output_dir, - ) + def test_flake8(self, python_model_v1_protocols: Tuple[Path, str]) -> None: + output_path, module_name = python_model_v1_protocols subprocess.run( - ["flake8", "--config", TOP_DIR / ".flake8", output_dir / "protocols.py"], + [ + "flake8", + "--config", + TOP_DIR / ".flake8", + output_path / module_name / "protocols.py", + ], encoding="utf-8", check=True, ) - def test_flake8_all_files(self, tmp_path: Path, model_context_url: str) -> None: + def test_flake8_all_files( + self, python_model_v1_protocols: Tuple[Path, str] + ) -> None: """ flake8 over the whole output directory with --include-protocols yes, not just protocols.py -- catches issues in the conditional protocols import inside __init__.py that a protocols.py-only check would miss. """ - output_dir = tmp_path / "pymodel" - shacl2code_generate( - ["--input", TEST_MODEL, "--context", model_context_url], - ["--include-protocols", "yes"], - output_dir, - ) + output_path, module_name = python_model_v1_protocols + output_dir = output_path / module_name subprocess.run( ["flake8", "--config", TOP_DIR / ".flake8"] + list(output_dir.iterdir()), encoding="utf-8", @@ -3208,8 +3224,7 @@ def test_conformance_mypy( Verifies scalar read/write, object-ref typed read, Any-setter write. """ module_path, module_name = python_model_v1_protocols - env = os.environ.copy() - env["PYTHONPATH"] = str(module_path) + env = _env_with_pythonpath(module_path) script = tmp_path / "conformance.py" script.write_text(textwrap.dedent(f"""\ @@ -3260,8 +3275,7 @@ def test_base_protocol_conformance_mypy( base protocols with no test catching it. """ module_path, module_name = python_model_v1_protocols - env = os.environ.copy() - env["PYTHONPATH"] = str(module_path) + env = _env_with_pythonpath(module_path) script = tmp_path / "base_conformance.py" script.write_text(textwrap.dedent(f"""\ @@ -3303,8 +3317,7 @@ def test_discriminator_mypy( satisfying each other's protocol. """ module_path, module_name = python_model_v1_protocols - env = os.environ.copy() - env["PYTHONPATH"] = str(module_path) + env = _env_with_pythonpath(module_path) # test_another_class has no own properties in v1, making it structurally # identical to test_class. Without the discriminator both would satisfy @@ -3330,52 +3343,69 @@ def test_discriminator_mypy( class TestProtocolCrossVersion: """ - Cross-version: v2 concrete classes must satisfy v1-generated Protocols, - and the discriminator still prevents wrong-type assignments across versions. + Cross-version: newer concrete classes must satisfy older-generated + Protocols, and the discriminator still prevents wrong-type assignments + across versions. + + Pairs cover chain-to-baseline (vN vs v1) plus adjacent (vN vs vN-1), so + each new version is checked both against the original baseline and + against the version it was directly derived from. """ + @pytest.mark.parametrize( + "older_fixture,newer_fixture", + [ + ("python_model_v1_protocols", "python_model_v2_protocols"), + ("python_model_v1_protocols", "python_model_v3_protocols"), + ("python_model_v2_protocols", "python_model_v3_protocols"), + ("python_model_v1_protocols", "python_model_v4_protocols"), + ("python_model_v3_protocols", "python_model_v4_protocols"), + ], + ) def test_cross_version_mypy( self, - python_model_v1_protocols: Tuple[Path, str], - python_model_v2_protocols: Tuple[Path, str], + older_fixture: str, + newer_fixture: str, + request: pytest.FixtureRequest, tmp_path: Path, ) -> None: """ - v2.test_class() satisfies v1.protocols.test_class (backward-compat). - v2.another_class() does NOT satisfy v1.protocols.test_class (discriminator). + newer.test_class() satisfies older.protocols.test_class (backward-compat). + newer.another_class() does NOT satisfy older.protocols.test_class + (discriminator). """ - v1_path, v1_name = python_model_v1_protocols - v2_path, v2_name = python_model_v2_protocols - env = os.environ.copy() - env["PYTHONPATH"] = os.pathsep.join([str(v1_path), str(v2_path)]) + older_path, older_name = request.getfixturevalue(older_fixture) + newer_path, newer_name = request.getfixturevalue(newer_fixture) + env = _env_with_pythonpath(older_path, newer_path) script = tmp_path / "cross_version.py" script.write_text(textwrap.dedent(f"""\ from typing import Any, Optional - import {v1_name}, {v2_name} - from {v1_name} import protocols as v1p + import {older_name}, {newer_name} + from {older_name} import protocols as op - # v2 concrete satisfies v1 Protocol (additive-only minor version). - a: v1p.test_class = {v2_name}.test_class() - b: v1p.parent_class = {v2_name}.parent_class() + # newer concrete satisfies older Protocol (additive-only versions). + a: op.test_class = {newer_name}.test_class() + b: op.parent_class = {newer_name}.parent_class() - # Scalar read through v1 protocol on v2 object. - def get_scalar(o: v1p.test_class) -> Optional[str]: + # Scalar read through older protocol on newer object. + def get_scalar(o: op.test_class) -> Optional[str]: result: Optional[str] = o.test_class_string_scalar_prop return result get_scalar(a) - # Object-ref typed read through v1 protocol on v2 object. - def get_ref(o: v1p.test_class) -> Any: + # Object-ref typed read through older protocol on newer object. + def get_ref(o: op.test_class) -> Any: return o.test_class_class_prop - # Any-setter write through v1 protocol on v2 object. - def set_ref(o: v1p.test_class, v: {v2_name}.test_class) -> None: + # Any-setter write through older protocol on newer object. + def set_ref(o: op.test_class, v: {newer_name}.test_class) -> None: o.test_class_class_prop = v - # Discriminator: v2.another_class must NOT satisfy v1.protocols.test_class. - bad: v1p.test_class = {v2_name}.test_another_class() # type: ignore[assignment] + # Discriminator: newer.another_class must NOT satisfy + # older.protocols.test_class. + bad: op.test_class = {newer_name}.test_another_class() # type: ignore[assignment] """)) subprocess.run( @@ -3388,10 +3418,10 @@ def set_ref(o: v1p.test_class, v: {v2_name}.test_class) -> None: # Confirm the discriminator actually works (without the ignore). script2 = tmp_path / "cross_version_bad.py" script2.write_text(textwrap.dedent(f"""\ - import {v1_name}, {v2_name} - from {v1_name} import protocols as v1p + import {older_name}, {newer_name} + from {older_name} import protocols as op - bad: v1p.test_class = {v2_name}.test_another_class() + bad: op.test_class = {newer_name}.test_another_class() """)) result = subprocess.run( ["mypy", "--strict", str(script2)], @@ -3400,7 +3430,7 @@ def set_ref(o: v1p.test_class, v: {v2_name}.test_class) -> None: capture_output=True, ) assert result.returncode != 0, ( - "Expected mypy to reject v2.another_class as v1.protocols.test_class " + "Expected mypy to reject newer.another_class as older.protocols.test_class " "across versions" ) From fde0b43cecbe161e52e3e3511c5be0aa77ddeb07 Mon Sep 17 00:00:00 2001 From: Arthit Suriyawongkul Date: Thu, 27 Aug 2026 01:13:13 +0700 Subject: [PATCH 28/40] Update header for _macros.j2 Signed-off-by: Arthit Suriyawongkul --- src/shacl2code/lang/templates/python/_macros.j2 | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/shacl2code/lang/templates/python/_macros.j2 b/src/shacl2code/lang/templates/python/_macros.j2 index 96eda807..3b5a0e27 100644 --- a/src/shacl2code/lang/templates/python/_macros.j2 +++ b/src/shacl2code/lang/templates/python/_macros.j2 @@ -2,8 +2,7 @@ # # SPDX-License-Identifier: MIT # -# Shared Jinja macros for the Python model.py, model.pyi and protocols.py -# templates. +# Shared Jinja macros for the Python templates. {% macro class_docstring(comment) %} {{ '"' }}{{ '"' }}{{ '"' }} {%- for l in comment.split("\n") %} From 498f2ce5bf8fc663e653a09dd4f5a9c7b225222d Mon Sep 17 00:00:00 2001 From: Arthit Suriyawongkul Date: Thu, 27 Aug 2026 01:27:52 +0700 Subject: [PATCH 29/40] Split Python Protocols into separated file Signed-off-by: Arthit Suriyawongkul --- .../lang/templates/python/_macros.j2 | 5 +- tests/test_python.py | 468 --------------- tests/test_python_protocols.py | 557 ++++++++++++++++++ 3 files changed, 560 insertions(+), 470 deletions(-) create mode 100644 tests/test_python_protocols.py diff --git a/src/shacl2code/lang/templates/python/_macros.j2 b/src/shacl2code/lang/templates/python/_macros.j2 index 3b5a0e27..e6fd9691 100644 --- a/src/shacl2code/lang/templates/python/_macros.j2 +++ b/src/shacl2code/lang/templates/python/_macros.j2 @@ -1,5 +1,6 @@ -# Copyright (c) 2026 Joshua Watt -# +# SPDX-FileContributor: Arthit Suriyawongkul +# SPDX-FileCopyrightText: 2026 Joshua Watt +# SPDX-FileType: SOURCE # SPDX-License-Identifier: MIT # # Shared Jinja macros for the Python templates. diff --git a/tests/test_python.py b/tests/test_python.py index 3a6899ea..d9b7c8a0 100644 --- a/tests/test_python.py +++ b/tests/test_python.py @@ -14,9 +14,6 @@ import warnings from datetime import datetime, timedelta, timezone from pathlib import Path -from typing import Iterable, Tuple - -from jinja2 import TemplateRuntimeError import jsonschema @@ -26,10 +23,6 @@ import rdflib -from shacl2code.lang.python import protocols_use_datetime -from shacl2code.model import Class, Model, Property -from shacl2code.urlcontext import UrlContext - from testfixtures import jsonvalidation, timetests THIS_FILE = Path(__file__) @@ -2974,467 +2967,6 @@ def test_pre_release_multi_valued_annotations(tmp_path, model_context_url): sys.path.remove(str(tmp_path)) -# --------------------------------------------------------------------------- -# Protocol generation tests -# --------------------------------------------------------------------------- - -TEST_V2_MODEL = THIS_DIR / "data" / "model" / "test-v2.ttl" -TEST_V3_MODEL = THIS_DIR / "data" / "model" / "test-v3.ttl" -TEST_V4_MODEL = THIS_DIR / "data" / "model" / "test-v4.ttl" -NO_DATETIME_MODEL = DATA_DIR / "no-datetime.ttl" - - -def _load_classes(ttl_path: Path) -> Iterable[Class]: - """Parse a .ttl file in-process into Model.classes (no --context needed).""" - graph = rdflib.Graph() - graph.parse(ttl_path) - return Model(graph, UrlContext()).classes - - -class TestProtocolsUseDatetime: - """ - Direct, in-process unit tests for protocols_use_datetime(). Exercises both - branches without going through code generation, so coverage doesn't depend - on incidental property ordering in a generated model. - """ - - def test_true_when_datetime_property_present(self) -> None: - """ - TEST_MODEL has scalar datetime properties (and list/enum/ref properties - that sort before them), so this also exercises the "skip" continue - branch on the way to the True return. - """ - assert protocols_use_datetime(_load_classes(TEST_MODEL)) is True - - def test_false_when_no_datetime_property(self) -> None: - """NO_DATETIME_MODEL has only a plain string property.""" - assert protocols_use_datetime(_load_classes(NO_DATETIME_MODEL)) is False - - def test_raises_on_unknown_datatype(self) -> None: - """Unmapped datatype raises like model.py.j2's abort(), not KeyError.""" - bad_prop = Property( - path="http://example.org/bad", - varname="bad", - datatype="http://example.org/not-a-real-datatype", - max_count=1, - ) - bad_class = Class( - _id="http://example.org/BadClass", - clsname="BadClass", - parent_ids=[], - derived_ids=[], - properties=[bad_prop], - ) - with pytest.raises(TemplateRuntimeError, match="Unknown data type"): - protocols_use_datetime([bad_class]) - - -def _generate_protocols_fixture( - tmp_path_factory: pytest.TempPathFactory, - model_context_url: str, - model_path: Path, - version: str, -) -> Tuple[Path, str]: - """Generate a --include-protocols yes module for one version fixture.""" - tmp_directory = tmp_path_factory.mktemp(f"protocols_{version}") - module_name = f"pymodel_{version}" - output_dir = tmp_directory / module_name - shacl2code_generate( - ["--input", model_path, "--context", model_context_url], - ["--include-protocols", "yes"], - output_dir, - ) - (output_dir / "py.typed").touch() - return tmp_directory, module_name - - -@pytest.fixture(scope="module") -def python_model_v1_protocols( - tmp_path_factory: pytest.TempPathFactory, model_context_url: str -) -> Tuple[Path, str]: - """v1 model generated with --include-protocols yes.""" - return _generate_protocols_fixture( - tmp_path_factory, model_context_url, TEST_MODEL, "v1" - ) - - -@pytest.fixture(scope="module") -def python_model_v2_protocols( - tmp_path_factory: pytest.TempPathFactory, model_context_url: str -) -> Tuple[Path, str]: - """v2 model (backward-compatible extension) generated with --include-protocols yes.""" - return _generate_protocols_fixture( - tmp_path_factory, model_context_url, TEST_V2_MODEL, "v2" - ) - - -@pytest.fixture(scope="module") -def python_model_v3_protocols( - tmp_path_factory: pytest.TempPathFactory, model_context_url: str -) -> Tuple[Path, str]: - """v3 model (backward-compatible extension of v2) with --include-protocols yes.""" - return _generate_protocols_fixture( - tmp_path_factory, model_context_url, TEST_V3_MODEL, "v3" - ) - - -@pytest.fixture(scope="module") -def python_model_v4_protocols( - tmp_path_factory: pytest.TempPathFactory, model_context_url: str -) -> Tuple[Path, str]: - """v4 model (backward-compatible extension of v3) with --include-protocols yes.""" - return _generate_protocols_fixture( - tmp_path_factory, model_context_url, TEST_V4_MODEL, "v4" - ) - - -class TestProtocolOutput: - """ - Tests for generated protocols.py - syntax, typing, and flake8. - """ - - def test_protocols_file_generated( - self, python_model_v1_protocols: Tuple[Path, str] - ) -> None: - output_path, module_name = python_model_v1_protocols - assert (output_path / module_name / "protocols.py").exists() - - def test_protocols_file_not_generated_by_default( - self, tmp_path: Path, model_context_url: str - ) -> None: - output_dir = tmp_path / "pymodel" - shacl2code_generate( - ["--input", TEST_MODEL, "--context", model_context_url], - [], - output_dir, - ) - assert not (output_dir / "protocols.py").exists() - - def test_dir_includes_lazy_names( - self, python_model_v1_protocols: Tuple[Path, str] - ) -> None: - """ - __dir__() must expose model classes, "protocols", and "main" for - dir()/tab-completion even though they are loaded lazily via - __getattr__ (PEP 562). - """ - output_path, module_name = python_model_v1_protocols - - sys.path.insert(0, str(output_path)) - try: - pkg = importlib.import_module(module_name) - names = dir(pkg) - - assert "test_class" in names - assert "parent_class" in names - assert "protocols" in names - assert "main" in names - - # protocols.py is not itself lazily loaded, so dir() on the - # lazy .protocols entry point must show its domain classes too -- - # confirms tab-completion works end to end through __getattr__. - proto_names = dir(pkg.protocols) - assert "SHACLObjectProtocol" in proto_names - assert "test_class" in proto_names - finally: - sys.path.remove(str(output_path)) - for m in list(sys.modules): - if m == module_name or m.startswith(module_name + "."): - del sys.modules[m] - - def test_mypy(self, python_model_v1_protocols: Tuple[Path, str]) -> None: - output_path, module_name = python_model_v1_protocols - subprocess.run( - ["mypy", output_path / module_name], encoding="utf-8", check=True - ) - - def test_flake8(self, python_model_v1_protocols: Tuple[Path, str]) -> None: - output_path, module_name = python_model_v1_protocols - subprocess.run( - [ - "flake8", - "--config", - TOP_DIR / ".flake8", - output_path / module_name / "protocols.py", - ], - encoding="utf-8", - check=True, - ) - - def test_flake8_all_files( - self, python_model_v1_protocols: Tuple[Path, str] - ) -> None: - """ - flake8 over the whole output directory with --include-protocols yes, - not just protocols.py -- catches issues in the conditional protocols - import inside __init__.py that a protocols.py-only check would miss. - """ - output_path, module_name = python_model_v1_protocols - output_dir = output_path / module_name - subprocess.run( - ["flake8", "--config", TOP_DIR / ".flake8"] + list(output_dir.iterdir()), - encoding="utf-8", - check=True, - ) - - def test_flake8_no_datetime_properties(self, tmp_path: Path) -> None: - """ - protocols.py must not unconditionally import `datetime`. A model with - no datetime-typed property must not produce an unused import (F401). - """ - output_dir = tmp_path / "pymodel" - shacl2code_generate( - ["--input", NO_DATETIME_MODEL], - ["--include-protocols", "yes"], - output_dir, - ) - assert "import datetime" not in (output_dir / "protocols.py").read_text() - subprocess.run( - ["flake8", "--config", TOP_DIR / ".flake8", output_dir / "protocols.py"], - encoding="utf-8", - check=True, - ) - - def test_mypy_no_datetime_properties(self, tmp_path: Path) -> None: - """ - The generated package must still type-check when protocols.py omits - the `datetime` import. - """ - output_dir = tmp_path / "pymodel" - shacl2code_generate( - ["--input", NO_DATETIME_MODEL], - ["--include-protocols", "yes"], - output_dir, - ) - (output_dir / "py.typed").touch() - subprocess.run(["mypy", output_dir], encoding="utf-8", check=True) - - -class TestProtocolConformance: - """ - Type-checked usage tests: concrete classes satisfy their protocols, - and the discriminator keeps structurally-identical classes distinct. - """ - - def test_conformance_mypy( - self, python_model_v1_protocols: Tuple[Path, str], tmp_path: Path - ) -> None: - """ - Every concrete class must satisfy its generated Protocol under mypy strict. - Verifies scalar read/write, object-ref typed read, Any-setter write. - """ - module_path, module_name = python_model_v1_protocols - env = _env_with_pythonpath(module_path) - - script = tmp_path / "conformance.py" - script.write_text(textwrap.dedent(f"""\ - from typing import Any, Optional - import {module_name} - from {module_name} import protocols - - # Protocol conformance: assignment forces the static check. - a: protocols.test_class = {module_name}.test_class() - b: protocols.parent_class = {module_name}.parent_class() - - # Scalar read + write through protocol. - def set_scalar(o: protocols.test_class, v: Optional[str]) -> None: - o.test_class_string_scalar_prop = v - - # Object-ref Any read + Any-setter write through protocol. - def get_ref(o: protocols.test_class) -> Any: - return o.test_class_class_prop - - def set_ref(o: protocols.test_class, v: {module_name}.test_class) -> None: - o.test_class_class_prop = v - - # Version-agnostic function accepts any conforming class. - def get_scalar(o: protocols.test_class) -> Optional[str]: - result: Optional[str] = o.test_class_string_scalar_prop - return result - - get_scalar(a) - """)) - - r = subprocess.run( - ["mypy", "--strict", str(script)], - encoding="utf-8", - env=env, - capture_output=True, - ) - assert r.returncode == 0, r.stdout + r.stderr - - def test_base_protocol_conformance_mypy( - self, python_model_v1_protocols: Tuple[Path, str], tmp_path: Path - ) -> None: - """ - The hand-written SHACLObjectProtocol/SHACLObjectSetProtocol must be - satisfied by the real generated SHACLObject/SHACLObjectSet, not just by - per-class domain protocols. Guards against model.py.j2 changes to - SHACLObject/SHACLObjectSet (e.g. renaming property_keys, retyping - find_by_id's default, altering __contains__) silently breaking the - base protocols with no test catching it. - """ - module_path, module_name = python_model_v1_protocols - env = _env_with_pythonpath(module_path) - - script = tmp_path / "base_conformance.py" - script.write_text(textwrap.dedent(f"""\ - from typing import Iterable - import {module_name} - from {module_name} import protocols - - # Protocol conformance: assignment forces the static check. - o: protocols.SHACLObjectProtocol = {module_name}.test_class() - s: protocols.SHACLObjectSetProtocol = {module_name}.SHACLObjectSet() - - # Version-agnostic function accepts any conforming object/set. - def get_id(obj: protocols.SHACLObjectProtocol) -> str: - return obj.get_type() - - def iter_objects( - objset: protocols.SHACLObjectSetProtocol, - ) -> Iterable[protocols.SHACLObjectProtocol]: - return objset.foreach() - - get_id(o) - iter_objects(s) - """)) - - r = subprocess.run( - ["mypy", "--strict", str(script)], - encoding="utf-8", - env=env, - capture_output=True, - ) - assert r.returncode == 0, r.stdout + r.stderr - - def test_discriminator_mypy( - self, python_model_v1_protocols: Tuple[Path, str], tmp_path: Path - ) -> None: - """ - The discriminator marker must prevent structurally-identical classes - (test_class and another_class share no own properties in v1) from - satisfying each other's protocol. - """ - module_path, module_name = python_model_v1_protocols - env = _env_with_pythonpath(module_path) - - # test_another_class has no own properties in v1, making it structurally - # identical to test_class. Without the discriminator both would satisfy - # each other's protocol. - script = tmp_path / "discriminator.py" - script.write_text(textwrap.dedent(f"""\ - import {module_name} - from {module_name} import protocols - - bad: protocols.test_class = {module_name}.test_another_class() - """)) - result = subprocess.run( - ["mypy", "--strict", str(script)], - encoding="utf-8", - env=env, - capture_output=True, - ) - assert result.returncode != 0, ( - "Expected mypy to reject test_another_class as protocols.test_class " - "(discriminator should prevent it)" - ) - - -class TestProtocolCrossVersion: - """ - Cross-version: newer concrete classes must satisfy older-generated - Protocols, and the discriminator still prevents wrong-type assignments - across versions. - - Pairs cover chain-to-baseline (vN vs v1) plus adjacent (vN vs vN-1), so - each new version is checked both against the original baseline and - against the version it was directly derived from. - """ - - @pytest.mark.parametrize( - "older_fixture,newer_fixture", - [ - ("python_model_v1_protocols", "python_model_v2_protocols"), - ("python_model_v1_protocols", "python_model_v3_protocols"), - ("python_model_v2_protocols", "python_model_v3_protocols"), - ("python_model_v1_protocols", "python_model_v4_protocols"), - ("python_model_v3_protocols", "python_model_v4_protocols"), - ], - ) - def test_cross_version_mypy( - self, - older_fixture: str, - newer_fixture: str, - request: pytest.FixtureRequest, - tmp_path: Path, - ) -> None: - """ - newer.test_class() satisfies older.protocols.test_class (backward-compat). - newer.another_class() does NOT satisfy older.protocols.test_class - (discriminator). - """ - older_path, older_name = request.getfixturevalue(older_fixture) - newer_path, newer_name = request.getfixturevalue(newer_fixture) - env = _env_with_pythonpath(older_path, newer_path) - - script = tmp_path / "cross_version.py" - script.write_text(textwrap.dedent(f"""\ - from typing import Any, Optional - import {older_name}, {newer_name} - from {older_name} import protocols as op - - # newer concrete satisfies older Protocol (additive-only versions). - a: op.test_class = {newer_name}.test_class() - b: op.parent_class = {newer_name}.parent_class() - - # Scalar read through older protocol on newer object. - def get_scalar(o: op.test_class) -> Optional[str]: - result: Optional[str] = o.test_class_string_scalar_prop - return result - - get_scalar(a) - - # Object-ref typed read through older protocol on newer object. - def get_ref(o: op.test_class) -> Any: - return o.test_class_class_prop - - # Any-setter write through older protocol on newer object. - def set_ref(o: op.test_class, v: {newer_name}.test_class) -> None: - o.test_class_class_prop = v - - # Discriminator: newer.another_class must NOT satisfy - # older.protocols.test_class. - bad: op.test_class = {newer_name}.test_another_class() # type: ignore[assignment] - """)) - - subprocess.run( - ["mypy", "--strict", str(script)], - encoding="utf-8", - env=env, - check=True, - ) - - # Confirm the discriminator actually works (without the ignore). - script2 = tmp_path / "cross_version_bad.py" - script2.write_text(textwrap.dedent(f"""\ - import {older_name}, {newer_name} - from {older_name} import protocols as op - - bad: op.test_class = {newer_name}.test_another_class() - """)) - result = subprocess.run( - ["mypy", "--strict", str(script2)], - encoding="utf-8", - env=env, - capture_output=True, - ) - assert result.returncode != 0, ( - "Expected mypy to reject newer.another_class as older.protocols.test_class " - "across versions" - ) - - class TestModelAll: def test_wildcard_import_is_eager_and_matches_public_names( self, tmp_path: Path diff --git a/tests/test_python_protocols.py b/tests/test_python_protocols.py new file mode 100644 index 00000000..90742962 --- /dev/null +++ b/tests/test_python_protocols.py @@ -0,0 +1,557 @@ +# SPDX-FileContributor: Arthit Suriyawongkul +# SPDX-FileCopyrightText: 2026 Joshua Watt +# SPDX-FileType: SOURCE +# SPDX-License-Identifier: MIT + +import importlib +import os +import subprocess +import sys +import textwrap +from pathlib import Path +from typing import Iterable, Tuple + +from jinja2 import TemplateRuntimeError + +import pytest + +import rdflib + +from shacl2code.lang.python import protocols_use_datetime +from shacl2code.model import Class, Model, Property +from shacl2code.urlcontext import UrlContext + +THIS_FILE = Path(__file__) +THIS_DIR = THIS_FILE.parent +TOP_DIR = THIS_DIR.parent + +DATA_DIR = THIS_DIR / "data" + +TEST_MODEL = THIS_DIR / "data" / "model" / "test.ttl" + +PRERELEASE_MODEL = DATA_DIR / "prerelease.ttl" + + +def shacl2code_generate(args, python_args, outfile): + p = subprocess.run( + [ + "shacl2code", + "generate", + ] + + args + + ["python"] + + python_args + + [ + "--output", + outfile, + ], + check=True, + stdout=subprocess.PIPE, + encoding="utf-8", + ) + + # Add a py.typed file for type checking + (outfile / "py.typed").touch() + return p + + +@pytest.fixture(scope="module") +def model_context_url(model_server): + yield model_server + "/test-context.json" + + +def _env_with_pythonpath(*paths: Path) -> "dict[str, str]": + """A copy of the current environment with `paths` appended to PYTHONPATH.""" + env = os.environ.copy() + env["PYTHONPATH"] = os.pathsep.join( + env.get("PYTHONPATH", "").split(os.pathsep) + [str(p) for p in paths] + ) + return env + + +TEST_V2_MODEL = THIS_DIR / "data" / "model" / "test-v2.ttl" +TEST_V3_MODEL = THIS_DIR / "data" / "model" / "test-v3.ttl" +TEST_V4_MODEL = THIS_DIR / "data" / "model" / "test-v4.ttl" +NO_DATETIME_MODEL = DATA_DIR / "no-datetime.ttl" + + +def _load_classes(ttl_path: Path) -> Iterable[Class]: + """Parse a .ttl file in-process into Model.classes (no --context needed).""" + graph = rdflib.Graph() + graph.parse(ttl_path) + return Model(graph, UrlContext()).classes + + +class TestProtocolsUseDatetime: + """ + Direct, in-process unit tests for protocols_use_datetime(). Exercises both + branches without going through code generation, so coverage doesn't depend + on incidental property ordering in a generated model. + """ + + def test_true_when_datetime_property_present(self) -> None: + """ + TEST_MODEL has scalar datetime properties (and list/enum/ref properties + that sort before them), so this also exercises the "skip" continue + branch on the way to the True return. + """ + assert protocols_use_datetime(_load_classes(TEST_MODEL)) is True + + def test_false_when_no_datetime_property(self) -> None: + """NO_DATETIME_MODEL has only a plain string property.""" + assert protocols_use_datetime(_load_classes(NO_DATETIME_MODEL)) is False + + def test_raises_on_unknown_datatype(self) -> None: + """Unmapped datatype raises like model.py.j2's abort(), not KeyError.""" + bad_prop = Property( + path="http://example.org/bad", + varname="bad", + datatype="http://example.org/not-a-real-datatype", + max_count=1, + ) + bad_class = Class( + _id="http://example.org/BadClass", + clsname="BadClass", + parent_ids=[], + derived_ids=[], + properties=[bad_prop], + ) + with pytest.raises(TemplateRuntimeError, match="Unknown data type"): + protocols_use_datetime([bad_class]) + + +def _generate_protocols_fixture( + tmp_path_factory: pytest.TempPathFactory, + model_context_url: str, + model_path: Path, + version: str, +) -> Tuple[Path, str]: + """Generate a --include-protocols yes module for one version fixture.""" + tmp_directory = tmp_path_factory.mktemp(f"protocols_{version}") + module_name = f"pymodel_{version}" + output_dir = tmp_directory / module_name + shacl2code_generate( + ["--input", model_path, "--context", model_context_url], + ["--include-protocols", "yes"], + output_dir, + ) + (output_dir / "py.typed").touch() + return tmp_directory, module_name + + +@pytest.fixture(scope="module") +def python_model_v1_protocols( + tmp_path_factory: pytest.TempPathFactory, model_context_url: str +) -> Tuple[Path, str]: + """v1 model generated with --include-protocols yes.""" + return _generate_protocols_fixture( + tmp_path_factory, model_context_url, TEST_MODEL, "v1" + ) + + +@pytest.fixture(scope="module") +def python_model_v2_protocols( + tmp_path_factory: pytest.TempPathFactory, model_context_url: str +) -> Tuple[Path, str]: + """v2 model (backward-compatible extension) generated with --include-protocols yes.""" + return _generate_protocols_fixture( + tmp_path_factory, model_context_url, TEST_V2_MODEL, "v2" + ) + + +@pytest.fixture(scope="module") +def python_model_v3_protocols( + tmp_path_factory: pytest.TempPathFactory, model_context_url: str +) -> Tuple[Path, str]: + """v3 model (backward-compatible extension of v2) with --include-protocols yes.""" + return _generate_protocols_fixture( + tmp_path_factory, model_context_url, TEST_V3_MODEL, "v3" + ) + + +@pytest.fixture(scope="module") +def python_model_v4_protocols( + tmp_path_factory: pytest.TempPathFactory, model_context_url: str +) -> Tuple[Path, str]: + """v4 model (backward-compatible extension of v3) with --include-protocols yes.""" + return _generate_protocols_fixture( + tmp_path_factory, model_context_url, TEST_V4_MODEL, "v4" + ) + + +class TestProtocolOutput: + """ + Tests for generated protocols.py - syntax, typing, and flake8. + """ + + def test_protocols_file_generated( + self, python_model_v1_protocols: Tuple[Path, str] + ) -> None: + output_path, module_name = python_model_v1_protocols + assert (output_path / module_name / "protocols.py").exists() + + def test_protocols_file_not_generated_by_default( + self, tmp_path: Path, model_context_url: str + ) -> None: + output_dir = tmp_path / "pymodel" + shacl2code_generate( + ["--input", TEST_MODEL, "--context", model_context_url], + [], + output_dir, + ) + assert not (output_dir / "protocols.py").exists() + + def test_dir_includes_lazy_names( + self, python_model_v1_protocols: Tuple[Path, str] + ) -> None: + """ + __dir__() must expose model classes, "protocols", and "main" for + dir()/tab-completion even though they are loaded lazily via + __getattr__ (PEP 562). + """ + output_path, module_name = python_model_v1_protocols + + sys.path.insert(0, str(output_path)) + try: + pkg = importlib.import_module(module_name) + names = dir(pkg) + + assert "test_class" in names + assert "parent_class" in names + assert "protocols" in names + assert "main" in names + + # protocols.py is not itself lazily loaded, so dir() on the + # lazy .protocols entry point must show its domain classes too -- + # confirms tab-completion works end to end through __getattr__. + proto_names = dir(pkg.protocols) + assert "SHACLObjectProtocol" in proto_names + assert "test_class" in proto_names + finally: + sys.path.remove(str(output_path)) + for m in list(sys.modules): + if m == module_name or m.startswith(module_name + "."): + del sys.modules[m] + + def test_mypy(self, python_model_v1_protocols: Tuple[Path, str]) -> None: + output_path, module_name = python_model_v1_protocols + subprocess.run( + ["mypy", output_path / module_name], encoding="utf-8", check=True + ) + + def test_flake8(self, python_model_v1_protocols: Tuple[Path, str]) -> None: + output_path, module_name = python_model_v1_protocols + subprocess.run( + [ + "flake8", + "--config", + TOP_DIR / ".flake8", + output_path / module_name / "protocols.py", + ], + encoding="utf-8", + check=True, + ) + + def test_flake8_all_files( + self, python_model_v1_protocols: Tuple[Path, str] + ) -> None: + """ + flake8 over the whole output directory with --include-protocols yes, + not just protocols.py -- catches issues in the conditional protocols + import inside __init__.py that a protocols.py-only check would miss. + """ + output_path, module_name = python_model_v1_protocols + output_dir = output_path / module_name + subprocess.run( + ["flake8", "--config", TOP_DIR / ".flake8"] + list(output_dir.iterdir()), + encoding="utf-8", + check=True, + ) + + def test_flake8_no_datetime_properties(self, tmp_path: Path) -> None: + """ + protocols.py must not unconditionally import `datetime`. A model with + no datetime-typed property must not produce an unused import (F401). + """ + output_dir = tmp_path / "pymodel" + shacl2code_generate( + ["--input", NO_DATETIME_MODEL], + ["--include-protocols", "yes"], + output_dir, + ) + assert "import datetime" not in (output_dir / "protocols.py").read_text() + subprocess.run( + ["flake8", "--config", TOP_DIR / ".flake8", output_dir / "protocols.py"], + encoding="utf-8", + check=True, + ) + + def test_mypy_no_datetime_properties(self, tmp_path: Path) -> None: + """ + The generated package must still type-check when protocols.py omits + the `datetime` import. + """ + output_dir = tmp_path / "pymodel" + shacl2code_generate( + ["--input", NO_DATETIME_MODEL], + ["--include-protocols", "yes"], + output_dir, + ) + (output_dir / "py.typed").touch() + subprocess.run(["mypy", output_dir], encoding="utf-8", check=True) + + +class TestProtocolConformance: + """ + Type-checked usage tests: concrete classes satisfy their protocols, + and the discriminator keeps structurally-identical classes distinct. + """ + + def test_conformance_mypy( + self, python_model_v1_protocols: Tuple[Path, str], tmp_path: Path + ) -> None: + """ + Every concrete class must satisfy its generated Protocol under mypy strict. + Verifies scalar read/write, object-ref typed read, Any-setter write. + """ + module_path, module_name = python_model_v1_protocols + env = _env_with_pythonpath(module_path) + + script = tmp_path / "conformance.py" + script.write_text(textwrap.dedent(f"""\ + from typing import Any, Optional + import {module_name} + from {module_name} import protocols + + # Protocol conformance: assignment forces the static check. + a: protocols.test_class = {module_name}.test_class() + b: protocols.parent_class = {module_name}.parent_class() + + # Scalar read + write through protocol. + def set_scalar(o: protocols.test_class, v: Optional[str]) -> None: + o.test_class_string_scalar_prop = v + + # Object-ref Any read + Any-setter write through protocol. + def get_ref(o: protocols.test_class) -> Any: + return o.test_class_class_prop + + def set_ref(o: protocols.test_class, v: {module_name}.test_class) -> None: + o.test_class_class_prop = v + + # Version-agnostic function accepts any conforming class. + def get_scalar(o: protocols.test_class) -> Optional[str]: + result: Optional[str] = o.test_class_string_scalar_prop + return result + + get_scalar(a) + """)) + + r = subprocess.run( + ["mypy", "--strict", str(script)], + encoding="utf-8", + env=env, + capture_output=True, + ) + assert r.returncode == 0, r.stdout + r.stderr + + def test_base_protocol_conformance_mypy( + self, python_model_v1_protocols: Tuple[Path, str], tmp_path: Path + ) -> None: + """ + The hand-written SHACLObjectProtocol/SHACLObjectSetProtocol must be + satisfied by the real generated SHACLObject/SHACLObjectSet, not just by + per-class domain protocols. Guards against model.py.j2 changes to + SHACLObject/SHACLObjectSet (e.g. renaming property_keys, retyping + find_by_id's default, altering __contains__) silently breaking the + base protocols with no test catching it. + """ + module_path, module_name = python_model_v1_protocols + env = _env_with_pythonpath(module_path) + + script = tmp_path / "base_conformance.py" + script.write_text(textwrap.dedent(f"""\ + from typing import Iterable + import {module_name} + from {module_name} import protocols + + # Protocol conformance: assignment forces the static check. + o: protocols.SHACLObjectProtocol = {module_name}.test_class() + s: protocols.SHACLObjectSetProtocol = {module_name}.SHACLObjectSet() + + # Version-agnostic function accepts any conforming object/set. + def get_id(obj: protocols.SHACLObjectProtocol) -> str: + return obj.get_type() + + def iter_objects( + objset: protocols.SHACLObjectSetProtocol, + ) -> Iterable[protocols.SHACLObjectProtocol]: + return objset.foreach() + + get_id(o) + iter_objects(s) + """)) + + r = subprocess.run( + ["mypy", "--strict", str(script)], + encoding="utf-8", + env=env, + capture_output=True, + ) + assert r.returncode == 0, r.stdout + r.stderr + + def test_discriminator_mypy( + self, python_model_v1_protocols: Tuple[Path, str], tmp_path: Path + ) -> None: + """ + The discriminator marker must prevent structurally-identical classes + (test_class and another_class share no own properties in v1) from + satisfying each other's protocol. + """ + module_path, module_name = python_model_v1_protocols + env = _env_with_pythonpath(module_path) + + # test_another_class has no own properties in v1, making it structurally + # identical to test_class. Without the discriminator both would satisfy + # each other's protocol. + script = tmp_path / "discriminator.py" + script.write_text(textwrap.dedent(f"""\ + import {module_name} + from {module_name} import protocols + + bad: protocols.test_class = {module_name}.test_another_class() + """)) + result = subprocess.run( + ["mypy", "--strict", str(script)], + encoding="utf-8", + env=env, + capture_output=True, + ) + assert result.returncode != 0, ( + "Expected mypy to reject test_another_class as protocols.test_class " + "(discriminator should prevent it)" + ) + + +class TestProtocolCrossVersion: + """ + Cross-version: newer concrete classes must satisfy older-generated + Protocols, and the discriminator still prevents wrong-type assignments + across versions. + + Pairs cover chain-to-baseline (vN vs v1) plus adjacent (vN vs vN-1), so + each new version is checked both against the original baseline and + against the version it was directly derived from. + """ + + @pytest.mark.parametrize( + "older_fixture,newer_fixture", + [ + ("python_model_v1_protocols", "python_model_v2_protocols"), + ("python_model_v1_protocols", "python_model_v3_protocols"), + ("python_model_v2_protocols", "python_model_v3_protocols"), + ("python_model_v1_protocols", "python_model_v4_protocols"), + ("python_model_v3_protocols", "python_model_v4_protocols"), + ], + ) + def test_cross_version_mypy( + self, + older_fixture: str, + newer_fixture: str, + request: pytest.FixtureRequest, + tmp_path: Path, + ) -> None: + """ + newer.test_class() satisfies older.protocols.test_class (backward-compat). + newer.another_class() does NOT satisfy older.protocols.test_class + (discriminator). + """ + older_path, older_name = request.getfixturevalue(older_fixture) + newer_path, newer_name = request.getfixturevalue(newer_fixture) + env = _env_with_pythonpath(older_path, newer_path) + + script = tmp_path / "cross_version.py" + script.write_text(textwrap.dedent(f"""\ + from typing import Any, Optional + import {older_name}, {newer_name} + from {older_name} import protocols as op + + # newer concrete satisfies older Protocol (additive-only versions). + a: op.test_class = {newer_name}.test_class() + b: op.parent_class = {newer_name}.parent_class() + + # Scalar read through older protocol on newer object. + def get_scalar(o: op.test_class) -> Optional[str]: + result: Optional[str] = o.test_class_string_scalar_prop + return result + + get_scalar(a) + + # Object-ref typed read through older protocol on newer object. + def get_ref(o: op.test_class) -> Any: + return o.test_class_class_prop + + # Any-setter write through older protocol on newer object. + def set_ref(o: op.test_class, v: {newer_name}.test_class) -> None: + o.test_class_class_prop = v + + # Discriminator: newer.another_class must NOT satisfy + # older.protocols.test_class. + bad: op.test_class = {newer_name}.test_another_class() # type: ignore[assignment] + """)) + + subprocess.run( + ["mypy", "--strict", str(script)], + encoding="utf-8", + env=env, + check=True, + ) + + # Confirm the discriminator actually works (without the ignore). + script2 = tmp_path / "cross_version_bad.py" + script2.write_text(textwrap.dedent(f"""\ + import {older_name}, {newer_name} + from {older_name} import protocols as op + + bad: op.test_class = {newer_name}.test_another_class() + """)) + result = subprocess.run( + ["mypy", "--strict", str(script2)], + encoding="utf-8", + env=env, + capture_output=True, + ) + assert result.returncode != 0, ( + "Expected mypy to reject newer.another_class as older.protocols.test_class " + "across versions" + ) + + + + +def test_prerelease_with_protocols(tmp_path: Path) -> None: + """ + --include-protocols and a pre-release model don't interact: the + import-time warning still fires, and protocols.py is still generated + and importable (protocols.py itself carries no pre-release awareness). + """ + output_dir = tmp_path / "pymodel_prerelease_protocols" + shacl2code_generate( + ["--input", PRERELEASE_MODEL], + ["--include-protocols", "yes"], + output_dir, + ) + assert "IS_PRERELEASE = True" in (output_dir / "__init__.py").read_text() + assert (output_dir / "protocols.py").exists() + + sys.path.insert(0, str(tmp_path)) + try: + with pytest.warns(FutureWarning): + pkg = importlib.import_module("pymodel_prerelease_protocols") + assert pkg.protocols is not None + finally: + sys.path.remove(str(tmp_path)) + for m in list(sys.modules): + if m == "pymodel_prerelease_protocols" or m.startswith( + "pymodel_prerelease_protocols." + ): + del sys.modules[m] From d48f7e470623eb498caec05da0abd6a6aae250a3 Mon Sep 17 00:00:00 2001 From: Arthit Suriyawongkul Date: Thu, 27 Aug 2026 01:32:55 +0700 Subject: [PATCH 30/40] Fix formatting Signed-off-by: Arthit Suriyawongkul --- tests/test_python_protocols.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/test_python_protocols.py b/tests/test_python_protocols.py index 90742962..83a49470 100644 --- a/tests/test_python_protocols.py +++ b/tests/test_python_protocols.py @@ -526,8 +526,6 @@ def set_ref(o: op.test_class, v: {newer_name}.test_class) -> None: ) - - def test_prerelease_with_protocols(tmp_path: Path) -> None: """ --include-protocols and a pre-release model don't interact: the From 644af75b0096188ac3f172c581a009b84cddc3b9 Mon Sep 17 00:00:00 2001 From: Arthit Suriyawongkul Date: Thu, 27 Aug 2026 03:15:19 +0700 Subject: [PATCH 31/40] Split tests Signed-off-by: Arthit Suriyawongkul --- tests/data/no-datetime.ttl | 7 +- tests/test_python.py | 143 ------------------------------ tests/test_python_lazy_loading.py | 132 +++++++++++++++++++++++++++ tests/test_python_prerelease.py | 99 +++++++++++++++++++++ 4 files changed, 235 insertions(+), 146 deletions(-) create mode 100644 tests/test_python_lazy_loading.py create mode 100644 tests/test_python_prerelease.py diff --git a/tests/data/no-datetime.ttl b/tests/data/no-datetime.ttl index 83a6cae8..cd54746c 100644 --- a/tests/data/no-datetime.ttl +++ b/tests/data/no-datetime.ttl @@ -4,9 +4,10 @@ @prefix owl: . @prefix xsd: . -# A minimal model with no datetime-typed property. Used to guard against -# protocols.py.j2 emitting an unconditional `from datetime import datetime` -# that would be an unused import (flake8 F401) for models like this one. +# A minimal model with no datetime-typed property. +# Used to verify that protocols.py.j2 omits `from datetime import datetime` +# for models like this one, instead of emitting it unconditionally and +# leaving an unused import (flake8 F401). a rdfs:Class, sh:NodeShape, owl:Class ; rdfs:comment "A class with only a string property, no datetime types" ; diff --git a/tests/test_python.py b/tests/test_python.py index d9b7c8a0..5161202b 100644 --- a/tests/test_python.py +++ b/tests/test_python.py @@ -11,7 +11,6 @@ import subprocess import sys import textwrap -import warnings from datetime import datetime, timedelta, timezone from pathlib import Path @@ -2416,63 +2415,6 @@ def test_prerelease_warning(model): model.test_class() -PRERELEASE_MODEL = DATA_DIR / "prerelease.ttl" - - -def test_is_prerelease_constant(tmp_path: Path) -> None: - """IS_PRERELEASE reflects sh-to-code:isPreRelease without loading model.py.""" - prerelease_dir = tmp_path / "pymodel_prerelease" - shacl2code_generate(["--input", PRERELEASE_MODEL], [], prerelease_dir) - - stable_dir = tmp_path / "pymodel_stable" - shacl2code_generate(["--input", TEST_MODEL], [], stable_dir) - - assert "IS_PRERELEASE = True" in (prerelease_dir / "__init__.py").read_text() - assert "IS_PRERELEASE = False" in (stable_dir / "__init__.py").read_text() - - sys.path.insert(0, str(tmp_path)) - try: - with pytest.warns(FutureWarning): - pkg = importlib.import_module("pymodel_prerelease") - assert pkg.IS_PRERELEASE is True - # Reading the constant must not have loaded model.py. - assert "pymodel_prerelease.model" not in sys.modules - finally: - sys.path.remove(str(tmp_path)) - for m in list(sys.modules): - if m == "pymodel_prerelease" or m.startswith("pymodel_prerelease."): - del sys.modules[m] - - -def test_prerelease_import_warning(tmp_path: Path) -> None: - """Pre-release package warns FutureWarning on first import, any form; stable doesn't.""" - prerelease_dir = tmp_path / "pymodel_prerelease_import" - shacl2code_generate(["--input", PRERELEASE_MODEL], [], prerelease_dir) - - stable_dir = tmp_path / "pymodel_stable_import" - shacl2code_generate(["--input", TEST_MODEL], [], stable_dir) - - sys.path.insert(0, str(tmp_path)) - try: - with pytest.warns(FutureWarning): - import pymodel_prerelease_import # noqa: F401 - - # Second import of an already-loaded module must not re-warn. - with warnings.catch_warnings(): - warnings.simplefilter("error", FutureWarning) - importlib.import_module("pymodel_prerelease_import") - - with warnings.catch_warnings(): - warnings.simplefilter("error", FutureWarning) - import pymodel_stable_import # noqa: F401 - finally: - sys.path.remove(str(tmp_path)) - for prefix in ("pymodel_prerelease_import", "pymodel_stable_import"): - for m in list(sys.modules): - if m == prefix or m.startswith(prefix + "."): - del sys.modules[m] - - def test_pre_release_cli_option(tmp_path_factory, model_context_url): tmp_directory = tmp_path_factory.mktemp("prerelease_test") module_name = "pymodel_prerelease" @@ -2966,88 +2908,3 @@ def test_pre_release_multi_valued_annotations(tmp_path, model_context_url): finally: sys.path.remove(str(tmp_path)) - -class TestModelAll: - def test_wildcard_import_is_eager_and_matches_public_names( - self, tmp_path: Path - ) -> None: - """``from mypkg import *`` yields the model's public names - and requires loading the model. - - Generated without --context, so the domain class asserted below - keeps its recognizable "http_..." varname. - """ - module_name = "pymodel_star_check" - output_dir = tmp_path / module_name - shacl2code_generate( - ["--input", TEST_MODEL], - [], - output_dir, - ) - - sys.path.insert(0, str(tmp_path)) - try: - import sys as _sys - - before = set(_sys.modules) - ns: dict = {} - exec(f"from {module_name} import *", ns) - imported = {k for k in ns if not k.startswith("__")} - - # Domain classes, from the test fixture model. - assert "http_example_org_shacl2code_test_test_class" in imported - assert "http_example_org_shacl2code_test_parent_class" in imported - - # Generator infrastructure: constants, base/encoder/decoder classes. - assert "CONTEXT_URLS" in imported - assert "SHACLObject" in imported - assert "SHACLObjectSet" in imported - assert "JSONLDDecoder" in imported - assert "JSONLDEncoder" in imported - # rdflib is a test dependency, so the RDF* classes are defined and - # expected to be included. - assert "RDFSerializer" in imported - - # Must not leak model.py's imports or internal bookkeeping state. - assert not imported & { - "TYPE_CHECKING", - "Any", - "List", - "TypeVar", - "json", - "_ALL_NAMED_INDIVIDUAL_IDS", - "_register_lock", - } - - # The model was loaded as a side effect of the wildcard import. - assert f"{module_name}.model" in (set(_sys.modules) - before) - finally: - sys.path.remove(str(tmp_path)) - for m in list(sys.modules): - if m == module_name or m.startswith(module_name + "."): - del sys.modules[m] - - def test_protocols_submodule_import_stays_lazy( - self, tmp_path: Path, model_context_url: str - ) -> None: - """Importing the ``protocols`` submodule must not load ``model``.""" - module_name = "pymodel_lazy_check" - output_dir = tmp_path / module_name - shacl2code_generate( - ["--input", TEST_MODEL, "--context", model_context_url], - ["--include-protocols", "yes"], - output_dir, - ) - - sys.path.insert(0, str(tmp_path)) - try: - import sys as _sys - - before = set(_sys.modules) - importlib.import_module(f"{module_name}.protocols") - assert f"{module_name}.model" not in (set(_sys.modules) - before) - finally: - sys.path.remove(str(tmp_path)) - for m in list(sys.modules): - if m == module_name or m.startswith(module_name + "."): - del sys.modules[m] diff --git a/tests/test_python_lazy_loading.py b/tests/test_python_lazy_loading.py new file mode 100644 index 00000000..1f152c08 --- /dev/null +++ b/tests/test_python_lazy_loading.py @@ -0,0 +1,132 @@ +# SPDX-FileContributor: Arthit Suriyawongkul +# SPDX-FileCopyrightText: 2026 Joshua Watt +# SPDX-FileType: SOURCE +# SPDX-License-Identifier: MIT + +import importlib +import subprocess +import sys +from pathlib import Path + +import pytest + +THIS_FILE = Path(__file__) +THIS_DIR = THIS_FILE.parent + +DATA_DIR = THIS_DIR / "data" + +TEST_MODEL = THIS_DIR / "data" / "model" / "test.ttl" + + +def shacl2code_generate(args, python_args, outfile): + p = subprocess.run( + [ + "shacl2code", + "generate", + ] + + args + + ["python"] + + python_args + + [ + "--output", + outfile, + ], + check=True, + stdout=subprocess.PIPE, + encoding="utf-8", + ) + + # Add a py.typed file for type checking + (outfile / "py.typed").touch() + return p + + +@pytest.fixture(scope="module") +def model_context_url(model_server): + yield model_server + "/test-context.json" + + +class TestModelAll: + def test_wildcard_import_is_eager_and_matches_public_names( + self, tmp_path: Path + ) -> None: + """``from mypkg import *`` yields the model's public names + and requires loading the model. + + Generated without --context, so the domain class asserted below + keeps its recognizable "http_..." varname. + """ + module_name = "pymodel_star_check" + output_dir = tmp_path / module_name + shacl2code_generate( + ["--input", TEST_MODEL], + [], + output_dir, + ) + + sys.path.insert(0, str(tmp_path)) + try: + import sys as _sys + + before = set(_sys.modules) + ns: dict = {} + exec(f"from {module_name} import *", ns) + imported = {k for k in ns if not k.startswith("__")} + + # Domain classes, from the test fixture model. + assert "http_example_org_shacl2code_test_test_class" in imported + assert "http_example_org_shacl2code_test_parent_class" in imported + + # Generator infrastructure: constants, base/encoder/decoder classes. + assert "CONTEXT_URLS" in imported + assert "SHACLObject" in imported + assert "SHACLObjectSet" in imported + assert "JSONLDDecoder" in imported + assert "JSONLDEncoder" in imported + # rdflib is a test dependency, so the RDF* classes are defined and + # expected to be included. + assert "RDFSerializer" in imported + + # Must not leak model.py's imports or internal bookkeeping state. + assert not imported & { + "TYPE_CHECKING", + "Any", + "List", + "TypeVar", + "json", + "_ALL_NAMED_INDIVIDUAL_IDS", + "_register_lock", + } + + # The model was loaded as a side effect of the wildcard import. + assert f"{module_name}.model" in (set(_sys.modules) - before) + finally: + sys.path.remove(str(tmp_path)) + for m in list(sys.modules): + if m == module_name or m.startswith(module_name + "."): + del sys.modules[m] + + def test_protocols_submodule_import_stays_lazy( + self, tmp_path: Path, model_context_url: str + ) -> None: + """Importing the ``protocols`` submodule must not load ``model``.""" + module_name = "pymodel_lazy_check" + output_dir = tmp_path / module_name + shacl2code_generate( + ["--input", TEST_MODEL, "--context", model_context_url], + ["--include-protocols", "yes"], + output_dir, + ) + + sys.path.insert(0, str(tmp_path)) + try: + import sys as _sys + + before = set(_sys.modules) + importlib.import_module(f"{module_name}.protocols") + assert f"{module_name}.model" not in (set(_sys.modules) - before) + finally: + sys.path.remove(str(tmp_path)) + for m in list(sys.modules): + if m == module_name or m.startswith(module_name + "."): + del sys.modules[m] diff --git a/tests/test_python_prerelease.py b/tests/test_python_prerelease.py new file mode 100644 index 00000000..523f81eb --- /dev/null +++ b/tests/test_python_prerelease.py @@ -0,0 +1,99 @@ +# SPDX-FileContributor: Arthit Suriyawongkul +# SPDX-FileCopyrightText: 2026 Joshua Watt +# SPDX-FileType: SOURCE +# SPDX-License-Identifier: MIT + +import importlib +import subprocess +import sys +import warnings +from pathlib import Path + +import pytest + +THIS_FILE = Path(__file__) +THIS_DIR = THIS_FILE.parent + +DATA_DIR = THIS_DIR / "data" + +TEST_MODEL = THIS_DIR / "data" / "model" / "test.ttl" + + +def shacl2code_generate(args, python_args, outfile): + p = subprocess.run( + [ + "shacl2code", + "generate", + ] + + args + + ["python"] + + python_args + + [ + "--output", + outfile, + ], + check=True, + stdout=subprocess.PIPE, + encoding="utf-8", + ) + + # Add a py.typed file for type checking + (outfile / "py.typed").touch() + return p + + +PRERELEASE_MODEL = DATA_DIR / "prerelease.ttl" + + +def test_is_prerelease_constant(tmp_path: Path) -> None: + """IS_PRERELEASE reflects sh-to-code:isPreRelease without loading model.py.""" + prerelease_dir = tmp_path / "pymodel_prerelease" + shacl2code_generate(["--input", PRERELEASE_MODEL], [], prerelease_dir) + + stable_dir = tmp_path / "pymodel_stable" + shacl2code_generate(["--input", TEST_MODEL], [], stable_dir) + + assert "IS_PRERELEASE = True" in (prerelease_dir / "__init__.py").read_text() + assert "IS_PRERELEASE = False" in (stable_dir / "__init__.py").read_text() + + sys.path.insert(0, str(tmp_path)) + try: + with pytest.warns(FutureWarning): + pkg = importlib.import_module("pymodel_prerelease") + assert pkg.IS_PRERELEASE is True + # Reading the constant must not have loaded model.py. + assert "pymodel_prerelease.model" not in sys.modules + finally: + sys.path.remove(str(tmp_path)) + for m in list(sys.modules): + if m == "pymodel_prerelease" or m.startswith("pymodel_prerelease."): + del sys.modules[m] + + +def test_prerelease_import_warning(tmp_path: Path) -> None: + """Pre-release package warns FutureWarning on first import, any form; stable doesn't.""" + prerelease_dir = tmp_path / "pymodel_prerelease_import" + shacl2code_generate(["--input", PRERELEASE_MODEL], [], prerelease_dir) + + stable_dir = tmp_path / "pymodel_stable_import" + shacl2code_generate(["--input", TEST_MODEL], [], stable_dir) + + sys.path.insert(0, str(tmp_path)) + try: + with pytest.warns(FutureWarning): + import pymodel_prerelease_import # noqa: F401 + + # Second import of an already-loaded module must not re-warn. + with warnings.catch_warnings(): + warnings.simplefilter("error", FutureWarning) + importlib.import_module("pymodel_prerelease_import") + + with warnings.catch_warnings(): + warnings.simplefilter("error", FutureWarning) + import pymodel_stable_import # noqa: F401 + finally: + sys.path.remove(str(tmp_path)) + for prefix in ("pymodel_prerelease_import", "pymodel_stable_import"): + for m in list(sys.modules): + if m == prefix or m.startswith(prefix + "."): + del sys.modules[m] From 96070728644b4bfab8d8f5d50a14b616af23a349 Mon Sep 17 00:00:00 2001 From: Arthit Suriyawongkul Date: Thu, 27 Aug 2026 03:21:06 +0700 Subject: [PATCH 32/40] Fix formatting Signed-off-by: Arthit Suriyawongkul --- tests/test_python.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_python.py b/tests/test_python.py index 5161202b..91aec0df 100644 --- a/tests/test_python.py +++ b/tests/test_python.py @@ -2907,4 +2907,3 @@ def test_pre_release_multi_valued_annotations(tmp_path, model_context_url): ), f"Failed for case {idx}: {annotations}" finally: sys.path.remove(str(tmp_path)) - From d3da5c00bcad919e91a716c4bccfd67b3c6d3c93 Mon Sep 17 00:00:00 2001 From: Arthit Suriyawongkul Date: Thu, 27 Aug 2026 03:31:40 +0700 Subject: [PATCH 33/40] Remove section headings in test_python.py Will split in another PR Signed-off-by: Arthit Suriyawongkul --- tests/test_python.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/tests/test_python.py b/tests/test_python.py index 91aec0df..95e94de6 100644 --- a/tests/test_python.py +++ b/tests/test_python.py @@ -2323,11 +2323,6 @@ def test_object_prop_set_coercion(model): assert c.test_class_class_prop is embedded_obj # check if same object -# --------------------------------------------------------------------------- -# Test IDE support -# --------------------------------------------------------------------------- - - def test_introspection(model): """ Tests that defined properties on a class are discoverable @@ -2391,11 +2386,6 @@ def test_version(model): assert model.VERSION == convert_version_string(MODEL_VERSION) -# --------------------------------------------------------------------------- -# Ontology metadata tests -# --------------------------------------------------------------------------- - - def test_ontology(model): classes = list(model.SHACLObject.CLASSES.values()) assert classes @@ -2403,11 +2393,6 @@ def test_ontology(model): assert c.ONTOLOGY is not None -# --------------------------------------------------------------------------- -# Pre-release metadata tests -# --------------------------------------------------------------------------- - - def test_prerelease_warning(model): model.SHACL2CODE_TEST.is_prerelease = True From 25ea783e4e5fc268f11f90cd40ae1a0bbea56995 Mon Sep 17 00:00:00 2001 From: Arthit Suriyawongkul Date: Thu, 27 Aug 2026 09:17:31 +0700 Subject: [PATCH 34/40] Sort keys in extra_env Signed-off-by: Arthit Suriyawongkul --- src/shacl2code/lang/python.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shacl2code/lang/python.py b/src/shacl2code/lang/python.py index e3844456..82c99560 100644 --- a/src/shacl2code/lang/python.py +++ b/src/shacl2code/lang/python.py @@ -222,10 +222,10 @@ def get_file(name): def get_extra_env(self): return { "varname": varname, - "prop_shape": prop_shape, "prop_element_pytype": prop_element_pytype, - "protocols_use_datetime": protocols_use_datetime, + "prop_shape": prop_shape, "protocols_extra_imports": protocols_extra_imports, + "protocols_use_datetime": protocols_use_datetime, "DATATYPE_CLASSES": DATATYPE_CLASSES, "DATATYPE_PYTHON_TYPES": DATATYPE_PYTHON_TYPES, } From d109fab7f3c3a6bb2c398fdf7c86816182c4784b Mon Sep 17 00:00:00 2001 From: Arthit Suriyawongkul Date: Thu, 27 Aug 2026 09:21:55 +0700 Subject: [PATCH 35/40] Clear imports Signed-off-by: Arthit Suriyawongkul --- tests/test_python.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_python.py b/tests/test_python.py index 95e94de6..e8d32707 100644 --- a/tests/test_python.py +++ b/tests/test_python.py @@ -2425,6 +2425,9 @@ def test_pre_release_cli_option(tmp_path_factory, model_context_url): assert m.SHACL2CODE_TEST.is_prerelease is True finally: sys.path.remove(str(tmp_directory)) + for mod in list(sys.modules): + if mod == module_name or mod.startswith(module_name + "."): + del sys.modules[mod] def test_no_pre_release_cli_option(tmp_path, model_context_url): From f0d9cc19efaeff085e54b2ad6acf87ac0312c54a Mon Sep 17 00:00:00 2001 From: Arthit Suriyawongkul Date: Thu, 27 Aug 2026 15:57:39 +0700 Subject: [PATCH 36/40] Fix varname collission Signed-off-by: Arthit Suriyawongkul --- src/shacl2code/lang/python.py | 29 +++++-- .../lang/templates/python/model.py.j2 | 8 +- .../lang/templates/python/model.pyi.j2 | 6 +- .../lang/templates/python/protocols.py.j2 | 2 +- tests/test_python.py | 47 +++++----- tests/test_python_lazy_loading.py | 11 +-- tests/test_python_prerelease.py | 44 +++++----- tests/test_python_protocols.py | 85 +++++++++---------- 8 files changed, 112 insertions(+), 120 deletions(-) diff --git a/src/shacl2code/lang/python.py b/src/shacl2code/lang/python.py index 82c99560..13b4a781 100644 --- a/src/shacl2code/lang/python.py +++ b/src/shacl2code/lang/python.py @@ -3,6 +3,7 @@ # SPDX-License-Identifier: MIT """Python language binding renderer""" +import hashlib import keyword import re from pathlib import Path @@ -103,21 +104,31 @@ def prop_element_pytype(prop, classes): def protocols_use_datetime(classes: Iterable[Class]) -> bool: - """Whether any class has a plain datetime-typed scalar property.""" + """Whether any class has a datetime-typed scalar or list property.""" for cls in classes: for prop in cls.properties: - is_list, has_ref, is_enum = prop_shape(prop) - is_scalar = not (is_list or has_ref or is_enum) - if not is_scalar: + _, has_ref, is_enum = prop_shape(prop) + if has_ref or is_enum: continue - if prop.datatype not in DATATYPE_PYTHON_TYPES: - # Same error as model.py.j2's abort() - raise TemplateRuntimeError("Unknown data type " + prop.datatype) - if DATATYPE_PYTHON_TYPES[prop.datatype] == "datetime": + if prop_element_pytype(prop, classes) == "datetime": return True return False +def protocol_discriminator_name(cls: Class) -> str: + """Stable, collision-resistant name for cls's Protocol discriminator method. + + Keyed by the class IRI (not the --context-compacted class name), so it + matches across generations with different --context flags. varname() + alone can sanitize two distinct IRIs to the same string (e.g. IRIs that + differ only in punctuation runs both collapsing to "_"), so a short hash + of the raw IRI is appended to disambiguate while staying stable across + regenerations of the same class. + """ + digest = hashlib.sha256(cls._id.encode("utf-8")).hexdigest()[:8] + return varname(cls._id, digest) + + def protocols_extra_imports(classes: Iterable[Class]) -> str: """Conditionally-needed stdlib imports for protocols.py.j2. @@ -224,8 +235,8 @@ def get_extra_env(self): "varname": varname, "prop_element_pytype": prop_element_pytype, "prop_shape": prop_shape, + "protocol_discriminator_name": protocol_discriminator_name, "protocols_extra_imports": protocols_extra_imports, - "protocols_use_datetime": protocols_use_datetime, "DATATYPE_CLASSES": DATATYPE_CLASSES, "DATATYPE_PYTHON_TYPES": DATATYPE_PYTHON_TYPES, } diff --git a/src/shacl2code/lang/templates/python/model.py.j2 b/src/shacl2code/lang/templates/python/model.py.j2 index 8ee0c0d3..043966d6 100644 --- a/src/shacl2code/lang/templates/python/model.py.j2 +++ b/src/shacl2code/lang/templates/python/model.py.j2 @@ -3075,14 +3075,14 @@ class {{ varname(*class.clsname) }}( # Discriminator keyed by IRI, not the --context-compacted class name, so # it matches across generations with different --context flags. - def _protocol_{{ varname(class._id) }}(self) -> None: + def _protocol_{{ protocol_discriminator_name(class) }}(self) -> None: pass {%- endif %} {%- if class.properties %} PROPERTIES: ClassVar[List[ClassProp]] = [ {%- for prop in class.properties %} - {%- set is_list = prop_is_list(prop) %} + {%- set is_list, has_ref, is_enum = prop_shape(prop) %} {%- if prop.comment %} {%- for l in prop.comment.split("\n") %} #{{ (" " + l).rstrip() }} @@ -3092,13 +3092,13 @@ class {{ varname(*class.clsname) }}( "{{ varname(prop.varname) }}", lambda: {% if is_list -%}ListProp({% endif %} - {%- if prop.enum_values -%} + {%- if is_enum -%} EnumProp(( {%- for value in prop.enum_values %} ("{{ value }}", "{{ context.compact_vocab(value, prop.path) }}"), {%- endfor %} )) - {%- elif prop.class_id -%} + {%- elif has_ref -%} {%- set ctx = [] %} {%- for value in get_all_named_individuals(classes.get(prop.class_id)) %} {%- if context.compact_vocab(value, prop.path) != value %} diff --git a/src/shacl2code/lang/templates/python/model.pyi.j2 b/src/shacl2code/lang/templates/python/model.pyi.j2 index 0de6824d..cb6c3a75 100644 --- a/src/shacl2code/lang/templates/python/model.pyi.j2 +++ b/src/shacl2code/lang/templates/python/model.pyi.j2 @@ -453,7 +453,7 @@ class {{ varname(*class.clsname) }}( {{ class.id_property }}: Optional[str] = None, {%- endif %} {%- for prop in class.properties %} - {%- set is_list, has_ref, is_enum = prop_shape(prop) %} + {%- set is_list = prop_is_list(prop) %} {%- set ptype = prop_element_pytype(prop, classes) %} {%- if is_list %} {{ varname(prop.varname) }}: Optional[Iterable[{{ ptype }}]] = None, @@ -465,14 +465,14 @@ class {{ varname(*class.clsname) }}( **kwargs: Any ) -> None: ... {%- if include_protocols %} - def _protocol_{{ varname(class._id) }}(self) -> None: ... + def _protocol_{{ protocol_discriminator_name(class) }}(self) -> None: ... {%- endif %} {%- if class.id_property %} {{ class.id_property }}: Optional[str] {%- endif %} {%- for prop in class.properties %} - {%- set is_list, has_ref, is_enum = prop_shape(prop) %} + {%- set is_list = prop_is_list(prop) %} {%- set ptype = prop_element_pytype(prop, classes) %} {%- if is_list %} @property diff --git a/src/shacl2code/lang/templates/python/protocols.py.j2 b/src/shacl2code/lang/templates/python/protocols.py.j2 index b9eb17d2..a3fc07d2 100644 --- a/src/shacl2code/lang/templates/python/protocols.py.j2 +++ b/src/shacl2code/lang/templates/python/protocols.py.j2 @@ -76,7 +76,7 @@ class {{ varname(*class.clsname) }}( , Protocol): {%- if class.comment %}{{ pymacros.class_docstring(class.comment) }}{% endif %} - def _protocol_{{ varname(class._id) }}(self) -> None: ... + def _protocol_{{ protocol_discriminator_name(class) }}(self) -> None: ... {%- if class.id_property %} {{ class.id_property }}: Optional[str] {%- endif %} diff --git a/tests/test_python.py b/tests/test_python.py index e8d32707..4285a21b 100644 --- a/tests/test_python.py +++ b/tests/test_python.py @@ -65,12 +65,7 @@ def shacl2code_generate(args, python_args, outfile): @pytest.fixture(scope="module") -def model_context_url(model_server): - yield model_server + "/test-context.json" - - -@pytest.fixture(scope="module") -def python_model(tmp_path_factory, model_context_url): +def python_model(tmp_path_factory, test_context_url): tmp_directory = tmp_path_factory.mktemp("pythontestcontext") module_name = "pymodel" output_dir = tmp_directory / module_name @@ -79,7 +74,7 @@ def python_model(tmp_path_factory, model_context_url): "--input", TEST_MODEL, "--context", - model_context_url, + test_context_url, "--jss-signature", "signatures", ], @@ -2127,7 +2122,7 @@ def test_varname_reserved_words(tmp_path): del sys.modules[mod] -def test_extensible_properties(model, model_context_url): +def test_extensible_properties(model, test_context_url): class Extension(model.extensible_class): TYPE = "http://example.org/shacl2code-test/extension" @@ -2142,7 +2137,7 @@ class Extension(model.extensible_class): DATA = { "@context": [ - model_context_url, + test_context_url, { "prefix": "http://example.org/shacl2code-test/", }, @@ -2175,7 +2170,7 @@ class Extension(model.extensible_class): assert s.serialize_data(objset, True) == DATA -def test_custom_objset_index(model, model_context_url): +def test_custom_objset_index(model, test_context_url): # Creates a derived objectset that indexes objects based on a property class ObjectSet(model.SHACLObjectSet): def create_index(self): @@ -2191,7 +2186,7 @@ def add_index(self, obj): d = model.JSONLDDeserializer() d.deserialize_data( { - "@context": model_context_url, + "@context": test_context_url, "@graph": [ { "@type": "test-class", @@ -2400,7 +2395,7 @@ def test_prerelease_warning(model): model.test_class() -def test_pre_release_cli_option(tmp_path_factory, model_context_url): +def test_pre_release_cli_option(tmp_path_factory, test_context_url): tmp_directory = tmp_path_factory.mktemp("prerelease_test") module_name = "pymodel_prerelease" output_dir = tmp_directory / module_name @@ -2409,7 +2404,7 @@ def test_pre_release_cli_option(tmp_path_factory, model_context_url): "--input", str(TEST_MODEL), "--context", - model_context_url, + test_context_url, "--pre-release", ], [ @@ -2430,7 +2425,7 @@ def test_pre_release_cli_option(tmp_path_factory, model_context_url): del sys.modules[mod] -def test_no_pre_release_cli_option(tmp_path, model_context_url): +def test_no_pre_release_cli_option(tmp_path, test_context_url): ttl_content = """ @base . @prefix rdf: . @@ -2454,7 +2449,7 @@ def test_no_pre_release_cli_option(tmp_path, model_context_url): "--input", str(ttl_file), "--context", - model_context_url, + test_context_url, ], [ "--version", @@ -2477,7 +2472,7 @@ def test_no_pre_release_cli_option(tmp_path, model_context_url): "--input", str(ttl_file), "--context", - model_context_url, + test_context_url, "--no-pre-release", ], [ @@ -2495,7 +2490,7 @@ def test_no_pre_release_cli_option(tmp_path, model_context_url): sys.path.remove(str(tmp_path)) -def test_pre_release_annotations_cases(tmp_path, model_context_url): +def test_pre_release_annotations_cases(tmp_path, test_context_url): # The number in the comment indicates the precedence of the annotation. # 1 is the highest precedence (force by command line option) cases = [ @@ -2568,7 +2563,7 @@ def test_pre_release_annotations_cases(tmp_path, model_context_url): "--input", str(ttl_file), "--context", - model_context_url, + test_context_url, ], [ "--version", @@ -2587,7 +2582,7 @@ def test_pre_release_annotations_cases(tmp_path, model_context_url): sys.path.remove(str(tmp_path)) -def test_pre_release_precedence(tmp_path, model_context_url): +def test_pre_release_precedence(tmp_path, test_context_url): # Example 1: # 2) sh-to-code:isPreRelease false (False) # 3) adms:status EU SEMIC DEVELOP (True) @@ -2615,7 +2610,7 @@ def test_pre_release_precedence(tmp_path, model_context_url): "--input", str(ttl_file_1), "--context", - model_context_url, + test_context_url, ], [ "--version", @@ -2658,7 +2653,7 @@ def test_pre_release_precedence(tmp_path, model_context_url): "--input", str(ttl_file_2), "--context", - model_context_url, + test_context_url, ], [ "--version", @@ -2701,7 +2696,7 @@ def test_pre_release_precedence(tmp_path, model_context_url): "--input", str(ttl_file_3), "--context", - model_context_url, + test_context_url, ], [ "--version", @@ -2744,7 +2739,7 @@ def test_pre_release_precedence(tmp_path, model_context_url): "--input", str(ttl_file_4), "--context", - model_context_url, + test_context_url, ], [ "--version", @@ -2787,7 +2782,7 @@ def test_pre_release_precedence(tmp_path, model_context_url): "--input", str(ttl_file_5), "--context", - model_context_url, + test_context_url, ], [ "--version", @@ -2804,7 +2799,7 @@ def test_pre_release_precedence(tmp_path, model_context_url): sys.path.remove(str(tmp_path)) -def test_pre_release_multi_valued_annotations(tmp_path, model_context_url): +def test_pre_release_multi_valued_annotations(tmp_path, test_context_url): # Each of these predicates can legally repeat. # A stable-looking value listed first must not hide # a pre-release-indicating value listed after it. @@ -2878,7 +2873,7 @@ def test_pre_release_multi_valued_annotations(tmp_path, model_context_url): "--input", str(ttl_file), "--context", - model_context_url, + test_context_url, ], [ "--version", diff --git a/tests/test_python_lazy_loading.py b/tests/test_python_lazy_loading.py index 1f152c08..30e3bfe8 100644 --- a/tests/test_python_lazy_loading.py +++ b/tests/test_python_lazy_loading.py @@ -8,8 +8,6 @@ import sys from pathlib import Path -import pytest - THIS_FILE = Path(__file__) THIS_DIR = THIS_FILE.parent @@ -41,11 +39,6 @@ def shacl2code_generate(args, python_args, outfile): return p -@pytest.fixture(scope="module") -def model_context_url(model_server): - yield model_server + "/test-context.json" - - class TestModelAll: def test_wildcard_import_is_eager_and_matches_public_names( self, tmp_path: Path @@ -107,13 +100,13 @@ def test_wildcard_import_is_eager_and_matches_public_names( del sys.modules[m] def test_protocols_submodule_import_stays_lazy( - self, tmp_path: Path, model_context_url: str + self, tmp_path: Path, test_context_url: str ) -> None: """Importing the ``protocols`` submodule must not load ``model``.""" module_name = "pymodel_lazy_check" output_dir = tmp_path / module_name shacl2code_generate( - ["--input", TEST_MODEL, "--context", model_context_url], + ["--input", TEST_MODEL, "--context", test_context_url], ["--include-protocols", "yes"], output_dir, ) diff --git a/tests/test_python_prerelease.py b/tests/test_python_prerelease.py index 523f81eb..6de2538c 100644 --- a/tests/test_python_prerelease.py +++ b/tests/test_python_prerelease.py @@ -45,18 +45,26 @@ def shacl2code_generate(args, python_args, outfile): PRERELEASE_MODEL = DATA_DIR / "prerelease.ttl" -def test_is_prerelease_constant(tmp_path: Path) -> None: - """IS_PRERELEASE reflects sh-to-code:isPreRelease without loading model.py.""" - prerelease_dir = tmp_path / "pymodel_prerelease" - shacl2code_generate(["--input", PRERELEASE_MODEL], [], prerelease_dir) +@pytest.fixture(scope="module") +def prerelease_and_stable_modules(tmp_path_factory: pytest.TempPathFactory) -> Path: + """Generate a pre-release and a stable module once, shared by both tests below.""" + tmp_directory = tmp_path_factory.mktemp("prerelease") + shacl2code_generate( + ["--input", PRERELEASE_MODEL], [], tmp_directory / "pymodel_prerelease" + ) + shacl2code_generate(["--input", TEST_MODEL], [], tmp_directory / "pymodel_stable") + return tmp_directory + - stable_dir = tmp_path / "pymodel_stable" - shacl2code_generate(["--input", TEST_MODEL], [], stable_dir) +def test_is_prerelease_constant(prerelease_and_stable_modules: Path) -> None: + """IS_PRERELEASE reflects sh-to-code:isPreRelease without loading model.py.""" + prerelease_dir = prerelease_and_stable_modules / "pymodel_prerelease" + stable_dir = prerelease_and_stable_modules / "pymodel_stable" assert "IS_PRERELEASE = True" in (prerelease_dir / "__init__.py").read_text() assert "IS_PRERELEASE = False" in (stable_dir / "__init__.py").read_text() - sys.path.insert(0, str(tmp_path)) + sys.path.insert(0, str(prerelease_and_stable_modules)) try: with pytest.warns(FutureWarning): pkg = importlib.import_module("pymodel_prerelease") @@ -64,36 +72,30 @@ def test_is_prerelease_constant(tmp_path: Path) -> None: # Reading the constant must not have loaded model.py. assert "pymodel_prerelease.model" not in sys.modules finally: - sys.path.remove(str(tmp_path)) + sys.path.remove(str(prerelease_and_stable_modules)) for m in list(sys.modules): if m == "pymodel_prerelease" or m.startswith("pymodel_prerelease."): del sys.modules[m] -def test_prerelease_import_warning(tmp_path: Path) -> None: +def test_prerelease_import_warning(prerelease_and_stable_modules: Path) -> None: """Pre-release package warns FutureWarning on first import, any form; stable doesn't.""" - prerelease_dir = tmp_path / "pymodel_prerelease_import" - shacl2code_generate(["--input", PRERELEASE_MODEL], [], prerelease_dir) - - stable_dir = tmp_path / "pymodel_stable_import" - shacl2code_generate(["--input", TEST_MODEL], [], stable_dir) - - sys.path.insert(0, str(tmp_path)) + sys.path.insert(0, str(prerelease_and_stable_modules)) try: with pytest.warns(FutureWarning): - import pymodel_prerelease_import # noqa: F401 + import pymodel_prerelease # noqa: F401 # Second import of an already-loaded module must not re-warn. with warnings.catch_warnings(): warnings.simplefilter("error", FutureWarning) - importlib.import_module("pymodel_prerelease_import") + importlib.import_module("pymodel_prerelease") with warnings.catch_warnings(): warnings.simplefilter("error", FutureWarning) - import pymodel_stable_import # noqa: F401 + import pymodel_stable # noqa: F401 finally: - sys.path.remove(str(tmp_path)) - for prefix in ("pymodel_prerelease_import", "pymodel_stable_import"): + sys.path.remove(str(prerelease_and_stable_modules)) + for prefix in ("pymodel_prerelease", "pymodel_stable"): for m in list(sys.modules): if m == prefix or m.startswith(prefix + "."): del sys.modules[m] diff --git a/tests/test_python_protocols.py b/tests/test_python_protocols.py index 83a49470..83a4c50b 100644 --- a/tests/test_python_protocols.py +++ b/tests/test_python_protocols.py @@ -55,11 +55,6 @@ def shacl2code_generate(args, python_args, outfile): return p -@pytest.fixture(scope="module") -def model_context_url(model_server): - yield model_server + "/test-context.json" - - def _env_with_pythonpath(*paths: Path) -> "dict[str, str]": """A copy of the current environment with `paths` appended to PYTHONPATH.""" env = os.environ.copy() @@ -122,7 +117,7 @@ def test_raises_on_unknown_datatype(self) -> None: def _generate_protocols_fixture( tmp_path_factory: pytest.TempPathFactory, - model_context_url: str, + test_context_url: str, model_path: Path, version: str, ) -> Tuple[Path, str]: @@ -131,7 +126,7 @@ def _generate_protocols_fixture( module_name = f"pymodel_{version}" output_dir = tmp_directory / module_name shacl2code_generate( - ["--input", model_path, "--context", model_context_url], + ["--input", model_path, "--context", test_context_url], ["--include-protocols", "yes"], output_dir, ) @@ -141,44 +136,56 @@ def _generate_protocols_fixture( @pytest.fixture(scope="module") def python_model_v1_protocols( - tmp_path_factory: pytest.TempPathFactory, model_context_url: str + tmp_path_factory: pytest.TempPathFactory, test_context_url: str ) -> Tuple[Path, str]: """v1 model generated with --include-protocols yes.""" return _generate_protocols_fixture( - tmp_path_factory, model_context_url, TEST_MODEL, "v1" + tmp_path_factory, test_context_url, TEST_MODEL, "v1" ) @pytest.fixture(scope="module") def python_model_v2_protocols( - tmp_path_factory: pytest.TempPathFactory, model_context_url: str + tmp_path_factory: pytest.TempPathFactory, test_context_url: str ) -> Tuple[Path, str]: """v2 model (backward-compatible extension) generated with --include-protocols yes.""" return _generate_protocols_fixture( - tmp_path_factory, model_context_url, TEST_V2_MODEL, "v2" + tmp_path_factory, test_context_url, TEST_V2_MODEL, "v2" ) @pytest.fixture(scope="module") def python_model_v3_protocols( - tmp_path_factory: pytest.TempPathFactory, model_context_url: str + tmp_path_factory: pytest.TempPathFactory, test_context_url: str ) -> Tuple[Path, str]: """v3 model (backward-compatible extension of v2) with --include-protocols yes.""" return _generate_protocols_fixture( - tmp_path_factory, model_context_url, TEST_V3_MODEL, "v3" + tmp_path_factory, test_context_url, TEST_V3_MODEL, "v3" ) @pytest.fixture(scope="module") def python_model_v4_protocols( - tmp_path_factory: pytest.TempPathFactory, model_context_url: str + tmp_path_factory: pytest.TempPathFactory, test_context_url: str ) -> Tuple[Path, str]: """v4 model (backward-compatible extension of v3) with --include-protocols yes.""" return _generate_protocols_fixture( - tmp_path_factory, model_context_url, TEST_V4_MODEL, "v4" + tmp_path_factory, test_context_url, TEST_V4_MODEL, "v4" ) +@pytest.fixture(scope="module") +def python_model_no_datetime(tmp_path_factory: pytest.TempPathFactory) -> Path: + """--include-protocols yes generated from a model with no datetime property.""" + output_dir = tmp_path_factory.mktemp("no_datetime") / "pymodel" + shacl2code_generate( + ["--input", NO_DATETIME_MODEL], + ["--include-protocols", "yes"], + output_dir, + ) + return output_dir + + class TestProtocolOutput: """ Tests for generated protocols.py - syntax, typing, and flake8. @@ -191,11 +198,11 @@ def test_protocols_file_generated( assert (output_path / module_name / "protocols.py").exists() def test_protocols_file_not_generated_by_default( - self, tmp_path: Path, model_context_url: str + self, tmp_path: Path, test_context_url: str ) -> None: output_dir = tmp_path / "pymodel" shacl2code_generate( - ["--input", TEST_MODEL, "--context", model_context_url], + ["--input", TEST_MODEL, "--context", test_context_url], [], output_dir, ) @@ -239,19 +246,6 @@ def test_mypy(self, python_model_v1_protocols: Tuple[Path, str]) -> None: ["mypy", output_path / module_name], encoding="utf-8", check=True ) - def test_flake8(self, python_model_v1_protocols: Tuple[Path, str]) -> None: - output_path, module_name = python_model_v1_protocols - subprocess.run( - [ - "flake8", - "--config", - TOP_DIR / ".flake8", - output_path / module_name / "protocols.py", - ], - encoding="utf-8", - check=True, - ) - def test_flake8_all_files( self, python_model_v1_protocols: Tuple[Path, str] ) -> None: @@ -268,37 +262,34 @@ def test_flake8_all_files( check=True, ) - def test_flake8_no_datetime_properties(self, tmp_path: Path) -> None: + def test_flake8_no_datetime_properties( + self, python_model_no_datetime: Path + ) -> None: """ protocols.py must not unconditionally import `datetime`. A model with no datetime-typed property must not produce an unused import (F401). """ - output_dir = tmp_path / "pymodel" - shacl2code_generate( - ["--input", NO_DATETIME_MODEL], - ["--include-protocols", "yes"], - output_dir, + assert ( + "import datetime" + not in (python_model_no_datetime / "protocols.py").read_text() ) - assert "import datetime" not in (output_dir / "protocols.py").read_text() subprocess.run( - ["flake8", "--config", TOP_DIR / ".flake8", output_dir / "protocols.py"], + [ + "flake8", + "--config", + TOP_DIR / ".flake8", + python_model_no_datetime / "protocols.py", + ], encoding="utf-8", check=True, ) - def test_mypy_no_datetime_properties(self, tmp_path: Path) -> None: + def test_mypy_no_datetime_properties(self, python_model_no_datetime: Path) -> None: """ The generated package must still type-check when protocols.py omits the `datetime` import. """ - output_dir = tmp_path / "pymodel" - shacl2code_generate( - ["--input", NO_DATETIME_MODEL], - ["--include-protocols", "yes"], - output_dir, - ) - (output_dir / "py.typed").touch() - subprocess.run(["mypy", output_dir], encoding="utf-8", check=True) + subprocess.run(["mypy", python_model_no_datetime], encoding="utf-8", check=True) class TestProtocolConformance: From 8c0ea767537a7dcbb3e4dee837f5a52fdbe9d1b8 Mon Sep 17 00:00:00 2001 From: Arthit Suriyawongkul Date: Thu, 27 Aug 2026 19:27:26 +0700 Subject: [PATCH 37/40] Fix markdown Signed-off-by: Arthit Suriyawongkul --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 09a84db7..df18a5dd 100644 --- a/README.md +++ b/README.md @@ -339,7 +339,7 @@ compatible versions of the same model. Passing `--include-protocols yes` to the Python generator adds a `protocols.py` module with a [`typing.Protocol`][typing-protocol] for every -class in the model. A Protocol accepts an object from *any* generated module +class in the model. A Protocol accepts an object from _any_ generated module whose version is compatible with the one the Protocol came from, so you can write functions and classes that work across model versions instead of being tied to one: From d13f3b27de97154d33672d6b6c651d54b655193b Mon Sep 17 00:00:00 2001 From: Arthit Suriyawongkul Date: Fri, 28 Aug 2026 21:02:33 +0700 Subject: [PATCH 38/40] Fix discriminator IRI Signed-off-by: Arthit Suriyawongkul --- README.md | 33 +- pyproject.toml | 6 + src/shacl2code/lang/python.py | 45 +- .../lang/templates/python/model.py.j2 | 7 +- .../lang/templates/python/model.pyi.j2 | 5 +- .../lang/templates/python/protocols.py.j2 | 31 +- tests/data/spdx/3.0.1/spdx-context.jsonld | 816 +++ .../3.0.1/spdx-json-serialize-annotations.ttl | 10 + tests/data/spdx/3.0.1/spdx-model.ttl | 3333 +++++++++++ tests/data/spdx/3.1-dev/spdx-context.jsonld | 1313 +++++ .../spdx-json-serialize-annotations.ttl | 10 + tests/data/spdx/3.1-dev/spdx-model.ttl | 5027 +++++++++++++++++ tests/data/spdx/README.md | 26 + tests/test_python_lazy_loading.py | 2 +- tests/test_python_protocols.py | 227 +- tests/test_python_spdx_protocols.py | 524 ++ 16 files changed, 11362 insertions(+), 53 deletions(-) create mode 100644 tests/data/spdx/3.0.1/spdx-context.jsonld create mode 100644 tests/data/spdx/3.0.1/spdx-json-serialize-annotations.ttl create mode 100644 tests/data/spdx/3.0.1/spdx-model.ttl create mode 100644 tests/data/spdx/3.1-dev/spdx-context.jsonld create mode 100644 tests/data/spdx/3.1-dev/spdx-json-serialize-annotations.ttl create mode 100644 tests/data/spdx/3.1-dev/spdx-model.ttl create mode 100644 tests/data/spdx/README.md create mode 100644 tests/test_python_spdx_protocols.py diff --git a/README.md b/README.md index df18a5dd..9a31038d 100644 --- a/README.md +++ b/README.md @@ -337,7 +337,7 @@ By default, code written against one generated Python module can't accept objects from another generated module, even if the two were generated from compatible versions of the same model. -Passing `--include-protocols yes` to the Python generator adds a +Passing `--include-protocols iri` to the Python generator adds a `protocols.py` module with a [`typing.Protocol`][typing-protocol] for every class in the model. A Protocol accepts an object from _any_ generated module whose version is compatible with the one the Protocol came from, so you can @@ -345,7 +345,7 @@ write functions and classes that work across model versions instead of being tied to one: ```shell -shacl2code generate -i model.jsonld python --include-protocols yes -o out +shacl2code generate -i model.jsonld python --include-protocols iri -o out ``` ```python @@ -358,10 +358,31 @@ def describe(obj: protocols.MyClass) -> str: `describe()` accepts a `MyClass` instance from `out`, or from any other generated module whose `MyClass` is compatible with `out`'s. -A couple of things to keep in mind: - -- Object-reference properties are typed `Any` on a Protocol; read those - through the concrete module when you need a precise type. +Each class's Protocol carries a hidden marker so structurally-identical but +unrelated classes can't accidentally satisfy each other's Protocol. +`--include-protocols` chooses how that marker is keyed: + +- `iri` (shown above) keys it by the class's full IRI. This stays stable + across regenerations of the *same* model with different `--context` + files, but not across ontology versions that embed their own version + number in every class IRI (e.g. SPDX's + `https://spdx.org/rdf/3.0.1/terms/Core/...` vs + `https://spdx.org/rdf/3.1/terms/Core/...`) -- a newer version's classes + won't satisfy an older version's Protocols. +- `compact-name` keys it by the `--context`-compacted class name instead + (`CreationInfo`, not the full IRI). This is what actually achieves + cross-version compatibility for ontologies like SPDX, which keep their + compact term names stable release to release even as the underlying IRI + changes -- but it's only safe when every generation being compared uses + the ontology's canonical context, since a custom context could compact + the same class to a different name. + +A couple of other things to keep in mind: + +- Object-reference properties are typed precisely on a Protocol for reads + (as the referenced class's own Protocol), but accept anything on write, + since each generated module version has its own distinct concrete class + for the referenced type. - Protocols are for type annotations only -- construct objects using a concrete generated module, not a Protocol. diff --git a/pyproject.toml b/pyproject.toml index a0e01550..ec90c007 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -101,6 +101,12 @@ pythonpath = [ "testfixtures" ] +[tool.hatch.build.targets.sdist] +# Vendored third-party test fixtures carry their own license, separate from +# this project's MIT license -- exclude them from the published sdist. +# They stay in the git repo, so they can be used for testing. +exclude = ["tests/data/spdx/*/*.ttl", "tests/data/spdx/*/*.jsonld"] + [tool.coverage.run] relative_files = true patch = ["subprocess"] diff --git a/src/shacl2code/lang/python.py b/src/shacl2code/lang/python.py index 13b4a781..bd6615f0 100644 --- a/src/shacl2code/lang/python.py +++ b/src/shacl2code/lang/python.py @@ -115,16 +115,37 @@ def protocols_use_datetime(classes: Iterable[Class]) -> bool: return False -def protocol_discriminator_name(cls: Class) -> str: +def protocols_use_object_refs(classes: Iterable[Class]) -> bool: + """Whether any class has an object-reference-typed scalar or list property.""" + for cls in classes: + for prop in cls.properties: + _, has_ref, _ = prop_shape(prop) + if has_ref: + return True + return False + + +def protocol_discriminator_name(cls: Class, key: str) -> str: """Stable, collision-resistant name for cls's Protocol discriminator method. - Keyed by the class IRI (not the --context-compacted class name), so it - matches across generations with different --context flags. varname() + key="iri": keyed by the class's raw IRI, so it matches across + generations of the SAME model with different --context flags. varname() alone can sanitize two distinct IRIs to the same string (e.g. IRIs that differ only in punctuation runs both collapsing to "_"), so a short hash of the raw IRI is appended to disambiguate while staying stable across regenerations of the same class. + + key="compact-name": keyed by the --context-compacted class name + instead -- the exact same name already used for the class itself, so + any collision here would already be a duplicate Python class + definition, independent of this function. Matches across different + VERSIONS of an ontology that keeps its compact term names stable even + as the underlying IRIs change (e.g. SPDX, which embeds its own spec + version in every class IRI). Only safe when every generation being + compared shares a canonical context. """ + if key == "compact-name": + return varname(*cls.clsname) digest = hashlib.sha256(cls._id.encode("utf-8")).hexdigest()[:8] return varname(cls._id, digest) @@ -146,6 +167,8 @@ def protocols_extra_imports(classes: Iterable[Class]) -> str: lines.append("from datetime import datetime # noqa: E402, I100, I202") if any(cls.named_individuals for cls in classes): lines.append("from typing import ClassVar, Dict # noqa: E402, I100, I202") + if protocols_use_object_refs(classes): + lines.append("from typing import Union # noqa: E402, I100, I202") if not lines: lines.append("# No extra imports needed for this model.") return "\n".join(lines) @@ -167,7 +190,8 @@ def __init__(self, args): super().__init__(args) self.__output = args.output self.__include_main = args.include_main == "yes" - self.__include_protocols = args.include_protocols == "yes" + self.__protocol_discriminator_key = args.include_protocols + self.__include_protocols = args.include_protocols != "no" self.__use_slots = args.use_slots self.__version_str = args.version if args.version: @@ -192,11 +216,19 @@ def get_arguments(cls, parser): ) parser.add_argument( "--include-protocols", - choices=("yes", "no"), + choices=("no", "iri", "compact-name"), default="no", help=( "Include a protocols.py module with version-agnostic Protocol " - "types for every class. Default is '%(default)s'" + "types for every class. 'iri' keys each class's cross-version " + "discriminator by its full IRI: stable across regenerations of " + "the same model with different --context files, but differs if " + "the ontology embeds its own version in class IRIs (e.g. SPDX). " + "'compact-name' keys it by the --context-compacted class name " + "instead: stable across ontology versions that keep the same " + "compact term names (e.g. SPDX), but only safe when every " + "generation being compared shares a canonical context. " + "Default is '%(default)s'" ), ) parser.add_argument( @@ -251,6 +283,7 @@ def get_additional_render_args(self, model): return { "include_main": self.__include_main, "include_protocols": self.__include_protocols, + "protocol_discriminator_key": self.__protocol_discriminator_key, "use_slots": use_slots, "version": self.__version, "version_str": self.__version_str, diff --git a/src/shacl2code/lang/templates/python/model.py.j2 b/src/shacl2code/lang/templates/python/model.py.j2 index 043966d6..d24c7e61 100644 --- a/src/shacl2code/lang/templates/python/model.py.j2 +++ b/src/shacl2code/lang/templates/python/model.py.j2 @@ -3073,9 +3073,10 @@ class {{ varname(*class.clsname) }}( {%- endif %} {%- if include_protocols %} - # Discriminator keyed by IRI, not the --context-compacted class name, so - # it matches across generations with different --context flags. - def _protocol_{{ protocol_discriminator_name(class) }}(self) -> None: + # Discriminator keyed by --include-protocols's chosen key (IRI or + # --context-compacted class name), not the class's own Python name, so + # it matches across generations sharing that key. + def _protocol_{{ protocol_discriminator_name(class, protocol_discriminator_key) }}(self) -> None: pass {%- endif %} diff --git a/src/shacl2code/lang/templates/python/model.pyi.j2 b/src/shacl2code/lang/templates/python/model.pyi.j2 index cb6c3a75..6fff2352 100644 --- a/src/shacl2code/lang/templates/python/model.pyi.j2 +++ b/src/shacl2code/lang/templates/python/model.pyi.j2 @@ -465,7 +465,10 @@ class {{ varname(*class.clsname) }}( **kwargs: Any ) -> None: ... {%- if include_protocols %} - def _protocol_{{ protocol_discriminator_name(class) }}(self) -> None: ... + # Discriminator keyed by --include-protocols's chosen key (IRI or + # --context-compacted class name), not the class's own Python name, so + # it matches across generations sharing that key. + def _protocol_{{ protocol_discriminator_name(class, protocol_discriminator_key) }}(self) -> None: ... {%- endif %} {%- if class.id_property %} diff --git a/src/shacl2code/lang/templates/python/protocols.py.j2 b/src/shacl2code/lang/templates/python/protocols.py.j2 index a3fc07d2..8a997d95 100644 --- a/src/shacl2code/lang/templates/python/protocols.py.j2 +++ b/src/shacl2code/lang/templates/python/protocols.py.j2 @@ -7,10 +7,12 @@ Satisfied by the corresponding concrete class of any model version backward-compatible with the baseline these Protocols were generated from. Use them to write functions and classes that work across model versions. -Scalar and list-of-scalar properties are typed precisely. Object-reference -properties are typed ``Any``: structural typing can't resolve the circular -reference to the sibling protocol type they'd otherwise return. Read them -through the concrete version type when a typed value is needed. +Scalar and list-of-scalar properties are typed precisely for reads and +writes. Object-reference properties are typed precisely for reads (as the +referenced class's own Protocol) but accept anything on write: each +generated module version has its own distinct concrete class for the +referenced type, so no single settable type could equal that concrete +attribute's type across every version. Construct objects with a concrete version module; use these Protocols only for annotations. @@ -76,7 +78,10 @@ class {{ varname(*class.clsname) }}( , Protocol): {%- if class.comment %}{{ pymacros.class_docstring(class.comment) }}{% endif %} - def _protocol_{{ protocol_discriminator_name(class) }}(self) -> None: ... + # Discriminator keyed by --include-protocols's chosen key (IRI or + # --context-compacted class name), not the class's own Python name, so + # it matches across generations sharing that key. + def _protocol_{{ protocol_discriminator_name(class, protocol_discriminator_key) }}(self) -> None: ... {%- if class.id_property %} {{ class.id_property }}: Optional[str] {%- endif %} @@ -87,16 +92,16 @@ class {{ varname(*class.clsname) }}( {%- endfor %} {%- endif %} {%- for prop in class.properties %} - {%- set is_list, has_ref, is_enum = prop_shape(prop) %} - {%- if not has_ref %} - {%- set ptype = prop_element_pytype(prop, classes) %} - {%- endif %} + {%- set is_list, has_ref, _ = prop_shape(prop) %} + {%- set ptype = prop_element_pytype(prop, classes) %} + {%- set rtype = "Iterable[" ~ ptype ~ "]" if is_list else "Optional[" ~ ptype ~ "]" %} {%- if has_ref %} - {{ varname(prop.varname) }}: Any - {%- elif is_list %} - {{ varname(prop.varname) }}: Iterable[{{ ptype }}] + @property + def {{ varname(prop.varname) }}(self) -> {{ rtype }}: ... + @{{ varname(prop.varname) }}.setter + def {{ varname(prop.varname) }}(self, value: Any) -> None: ... {%- else %} - {{ varname(prop.varname) }}: Optional[{{ ptype }}] + {{ varname(prop.varname) }}: {{ rtype }} {%- endif %} {%- endfor %} {% endfor %} diff --git a/tests/data/spdx/3.0.1/spdx-context.jsonld b/tests/data/spdx/3.0.1/spdx-context.jsonld new file mode 100644 index 00000000..f692cb99 --- /dev/null +++ b/tests/data/spdx/3.0.1/spdx-context.jsonld @@ -0,0 +1,816 @@ +{ + "@context": { + "Agent": "https://spdx.org/rdf/3.0.1/terms/Core/Agent", + "Annotation": "https://spdx.org/rdf/3.0.1/terms/Core/Annotation", + "AnnotationType": "https://spdx.org/rdf/3.0.1/terms/Core/AnnotationType", + "Artifact": "https://spdx.org/rdf/3.0.1/terms/Core/Artifact", + "Bom": "https://spdx.org/rdf/3.0.1/terms/Core/Bom", + "Bundle": "https://spdx.org/rdf/3.0.1/terms/Core/Bundle", + "CreationInfo": "https://spdx.org/rdf/3.0.1/terms/Core/CreationInfo", + "DictionaryEntry": "https://spdx.org/rdf/3.0.1/terms/Core/DictionaryEntry", + "Element": "https://spdx.org/rdf/3.0.1/terms/Core/Element", + "ElementCollection": "https://spdx.org/rdf/3.0.1/terms/Core/ElementCollection", + "ExternalIdentifier": "https://spdx.org/rdf/3.0.1/terms/Core/ExternalIdentifier", + "ExternalIdentifierType": "https://spdx.org/rdf/3.0.1/terms/Core/ExternalIdentifierType", + "ExternalMap": "https://spdx.org/rdf/3.0.1/terms/Core/ExternalMap", + "ExternalRef": "https://spdx.org/rdf/3.0.1/terms/Core/ExternalRef", + "ExternalRefType": "https://spdx.org/rdf/3.0.1/terms/Core/ExternalRefType", + "Hash": "https://spdx.org/rdf/3.0.1/terms/Core/Hash", + "HashAlgorithm": "https://spdx.org/rdf/3.0.1/terms/Core/HashAlgorithm", + "IndividualElement": "https://spdx.org/rdf/3.0.1/terms/Core/IndividualElement", + "IntegrityMethod": "https://spdx.org/rdf/3.0.1/terms/Core/IntegrityMethod", + "LifecycleScopeType": "https://spdx.org/rdf/3.0.1/terms/Core/LifecycleScopeType", + "LifecycleScopedRelationship": "https://spdx.org/rdf/3.0.1/terms/Core/LifecycleScopedRelationship", + "NamespaceMap": "https://spdx.org/rdf/3.0.1/terms/Core/NamespaceMap", + "NoAssertionElement": "https://spdx.org/rdf/3.0.1/terms/Core/NoAssertionElement", + "NoneElement": "https://spdx.org/rdf/3.0.1/terms/Core/NoneElement", + "Organization": "https://spdx.org/rdf/3.0.1/terms/Core/Organization", + "PackageVerificationCode": "https://spdx.org/rdf/3.0.1/terms/Core/PackageVerificationCode", + "Person": "https://spdx.org/rdf/3.0.1/terms/Core/Person", + "PositiveIntegerRange": "https://spdx.org/rdf/3.0.1/terms/Core/PositiveIntegerRange", + "PresenceType": "https://spdx.org/rdf/3.0.1/terms/Core/PresenceType", + "ProfileIdentifierType": "https://spdx.org/rdf/3.0.1/terms/Core/ProfileIdentifierType", + "Relationship": "https://spdx.org/rdf/3.0.1/terms/Core/Relationship", + "RelationshipCompleteness": "https://spdx.org/rdf/3.0.1/terms/Core/RelationshipCompleteness", + "RelationshipType": "https://spdx.org/rdf/3.0.1/terms/Core/RelationshipType", + "SoftwareAgent": "https://spdx.org/rdf/3.0.1/terms/Core/SoftwareAgent", + "SpdxDocument": "https://spdx.org/rdf/3.0.1/terms/Core/SpdxDocument", + "SpdxOrganization": "https://spdx.org/rdf/3.0.1/terms/Core/SpdxOrganization", + "SupportType": "https://spdx.org/rdf/3.0.1/terms/Core/SupportType", + "Tool": "https://spdx.org/rdf/3.0.1/terms/Core/Tool", + "ai_AIPackage": "https://spdx.org/rdf/3.0.1/terms/AI/AIPackage", + "ai_EnergyConsumption": "https://spdx.org/rdf/3.0.1/terms/AI/EnergyConsumption", + "ai_EnergyConsumptionDescription": "https://spdx.org/rdf/3.0.1/terms/AI/EnergyConsumptionDescription", + "ai_EnergyUnitType": "https://spdx.org/rdf/3.0.1/terms/AI/EnergyUnitType", + "ai_SafetyRiskAssessmentType": "https://spdx.org/rdf/3.0.1/terms/AI/SafetyRiskAssessmentType", + "ai_autonomyType": { + "@context": { + "@vocab": "https://spdx.org/rdf/3.0.1/terms/Core/PresenceType/" + }, + "@id": "https://spdx.org/rdf/3.0.1/terms/AI/autonomyType", + "@type": "@vocab" + }, + "ai_domain": { + "@id": "https://spdx.org/rdf/3.0.1/terms/AI/domain", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "ai_energyConsumption": { + "@id": "https://spdx.org/rdf/3.0.1/terms/AI/energyConsumption", + "@type": "@vocab" + }, + "ai_energyQuantity": { + "@id": "https://spdx.org/rdf/3.0.1/terms/AI/energyQuantity", + "@type": "http://www.w3.org/2001/XMLSchema#decimal" + }, + "ai_energyUnit": { + "@context": { + "@vocab": "https://spdx.org/rdf/3.0.1/terms/AI/EnergyUnitType/" + }, + "@id": "https://spdx.org/rdf/3.0.1/terms/AI/energyUnit", + "@type": "@vocab" + }, + "ai_finetuningEnergyConsumption": { + "@id": "https://spdx.org/rdf/3.0.1/terms/AI/finetuningEnergyConsumption", + "@type": "@vocab" + }, + "ai_hyperparameter": { + "@id": "https://spdx.org/rdf/3.0.1/terms/AI/hyperparameter", + "@type": "@vocab" + }, + "ai_inferenceEnergyConsumption": { + "@id": "https://spdx.org/rdf/3.0.1/terms/AI/inferenceEnergyConsumption", + "@type": "@vocab" + }, + "ai_informationAboutApplication": { + "@id": "https://spdx.org/rdf/3.0.1/terms/AI/informationAboutApplication", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "ai_informationAboutTraining": { + "@id": "https://spdx.org/rdf/3.0.1/terms/AI/informationAboutTraining", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "ai_limitation": { + "@id": "https://spdx.org/rdf/3.0.1/terms/AI/limitation", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "ai_metric": { + "@id": "https://spdx.org/rdf/3.0.1/terms/AI/metric", + "@type": "@vocab" + }, + "ai_metricDecisionThreshold": { + "@id": "https://spdx.org/rdf/3.0.1/terms/AI/metricDecisionThreshold", + "@type": "@vocab" + }, + "ai_modelDataPreprocessing": { + "@id": "https://spdx.org/rdf/3.0.1/terms/AI/modelDataPreprocessing", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "ai_modelExplainability": { + "@id": "https://spdx.org/rdf/3.0.1/terms/AI/modelExplainability", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "ai_safetyRiskAssessment": { + "@context": { + "@vocab": "https://spdx.org/rdf/3.0.1/terms/AI/SafetyRiskAssessmentType/" + }, + "@id": "https://spdx.org/rdf/3.0.1/terms/AI/safetyRiskAssessment", + "@type": "@vocab" + }, + "ai_standardCompliance": { + "@id": "https://spdx.org/rdf/3.0.1/terms/AI/standardCompliance", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "ai_trainingEnergyConsumption": { + "@id": "https://spdx.org/rdf/3.0.1/terms/AI/trainingEnergyConsumption", + "@type": "@vocab" + }, + "ai_typeOfModel": { + "@id": "https://spdx.org/rdf/3.0.1/terms/AI/typeOfModel", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "ai_useSensitivePersonalInformation": { + "@context": { + "@vocab": "https://spdx.org/rdf/3.0.1/terms/Core/PresenceType/" + }, + "@id": "https://spdx.org/rdf/3.0.1/terms/AI/useSensitivePersonalInformation", + "@type": "@vocab" + }, + "algorithm": { + "@context": { + "@vocab": "https://spdx.org/rdf/3.0.1/terms/Core/HashAlgorithm/" + }, + "@id": "https://spdx.org/rdf/3.0.1/terms/Core/algorithm", + "@type": "@vocab" + }, + "annotationType": { + "@context": { + "@vocab": "https://spdx.org/rdf/3.0.1/terms/Core/AnnotationType/" + }, + "@id": "https://spdx.org/rdf/3.0.1/terms/Core/annotationType", + "@type": "@vocab" + }, + "beginIntegerRange": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Core/beginIntegerRange", + "@type": "http://www.w3.org/2001/XMLSchema#positiveInteger" + }, + "build_Build": "https://spdx.org/rdf/3.0.1/terms/Build/Build", + "build_buildEndTime": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Build/buildEndTime", + "@type": "http://www.w3.org/2001/XMLSchema#dateTimeStamp" + }, + "build_buildId": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Build/buildId", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "build_buildStartTime": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Build/buildStartTime", + "@type": "http://www.w3.org/2001/XMLSchema#dateTimeStamp" + }, + "build_buildType": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Build/buildType", + "@type": "http://www.w3.org/2001/XMLSchema#anyURI" + }, + "build_configSourceDigest": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Build/configSourceDigest", + "@type": "@vocab" + }, + "build_configSourceEntrypoint": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Build/configSourceEntrypoint", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "build_configSourceUri": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Build/configSourceUri", + "@type": "http://www.w3.org/2001/XMLSchema#anyURI" + }, + "build_environment": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Build/environment", + "@type": "@vocab" + }, + "build_parameter": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Build/parameter", + "@type": "@vocab" + }, + "builtTime": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Core/builtTime", + "@type": "http://www.w3.org/2001/XMLSchema#dateTimeStamp" + }, + "comment": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Core/comment", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "completeness": { + "@context": { + "@vocab": "https://spdx.org/rdf/3.0.1/terms/Core/RelationshipCompleteness/" + }, + "@id": "https://spdx.org/rdf/3.0.1/terms/Core/completeness", + "@type": "@vocab" + }, + "contentType": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Core/contentType", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "context": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Core/context", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "created": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Core/created", + "@type": "http://www.w3.org/2001/XMLSchema#dateTimeStamp" + }, + "createdBy": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Core/createdBy", + "@type": "@vocab" + }, + "createdUsing": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Core/createdUsing", + "@type": "@vocab" + }, + "creationInfo": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Core/creationInfo", + "@type": "@vocab" + }, + "dataLicense": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Core/dataLicense", + "@type": "@vocab" + }, + "dataset_ConfidentialityLevelType": "https://spdx.org/rdf/3.0.1/terms/Dataset/ConfidentialityLevelType", + "dataset_DatasetAvailabilityType": "https://spdx.org/rdf/3.0.1/terms/Dataset/DatasetAvailabilityType", + "dataset_DatasetPackage": "https://spdx.org/rdf/3.0.1/terms/Dataset/DatasetPackage", + "dataset_DatasetType": "https://spdx.org/rdf/3.0.1/terms/Dataset/DatasetType", + "dataset_anonymizationMethodUsed": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Dataset/anonymizationMethodUsed", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "dataset_confidentialityLevel": { + "@context": { + "@vocab": "https://spdx.org/rdf/3.0.1/terms/Dataset/ConfidentialityLevelType/" + }, + "@id": "https://spdx.org/rdf/3.0.1/terms/Dataset/confidentialityLevel", + "@type": "@vocab" + }, + "dataset_dataCollectionProcess": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Dataset/dataCollectionProcess", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "dataset_dataPreprocessing": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Dataset/dataPreprocessing", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "dataset_datasetAvailability": { + "@context": { + "@vocab": "https://spdx.org/rdf/3.0.1/terms/Dataset/DatasetAvailabilityType/" + }, + "@id": "https://spdx.org/rdf/3.0.1/terms/Dataset/datasetAvailability", + "@type": "@vocab" + }, + "dataset_datasetNoise": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Dataset/datasetNoise", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "dataset_datasetSize": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Dataset/datasetSize", + "@type": "http://www.w3.org/2001/XMLSchema#nonNegativeInteger" + }, + "dataset_datasetType": { + "@context": { + "@vocab": "https://spdx.org/rdf/3.0.1/terms/Dataset/DatasetType/" + }, + "@id": "https://spdx.org/rdf/3.0.1/terms/Dataset/datasetType", + "@type": "@vocab" + }, + "dataset_datasetUpdateMechanism": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Dataset/datasetUpdateMechanism", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "dataset_hasSensitivePersonalInformation": { + "@context": { + "@vocab": "https://spdx.org/rdf/3.0.1/terms/Core/PresenceType/" + }, + "@id": "https://spdx.org/rdf/3.0.1/terms/Dataset/hasSensitivePersonalInformation", + "@type": "@vocab" + }, + "dataset_intendedUse": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Dataset/intendedUse", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "dataset_knownBias": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Dataset/knownBias", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "dataset_sensor": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Dataset/sensor", + "@type": "@vocab" + }, + "definingArtifact": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Core/definingArtifact", + "@type": "@vocab" + }, + "description": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Core/description", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "element": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Core/element", + "@type": "@vocab" + }, + "endIntegerRange": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Core/endIntegerRange", + "@type": "http://www.w3.org/2001/XMLSchema#positiveInteger" + }, + "endTime": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Core/endTime", + "@type": "http://www.w3.org/2001/XMLSchema#dateTimeStamp" + }, + "expandedlicensing_ConjunctiveLicenseSet": "https://spdx.org/rdf/3.0.1/terms/ExpandedLicensing/ConjunctiveLicenseSet", + "expandedlicensing_CustomLicense": "https://spdx.org/rdf/3.0.1/terms/ExpandedLicensing/CustomLicense", + "expandedlicensing_CustomLicenseAddition": "https://spdx.org/rdf/3.0.1/terms/ExpandedLicensing/CustomLicenseAddition", + "expandedlicensing_DisjunctiveLicenseSet": "https://spdx.org/rdf/3.0.1/terms/ExpandedLicensing/DisjunctiveLicenseSet", + "expandedlicensing_ExtendableLicense": "https://spdx.org/rdf/3.0.1/terms/ExpandedLicensing/ExtendableLicense", + "expandedlicensing_IndividualLicensingInfo": "https://spdx.org/rdf/3.0.1/terms/ExpandedLicensing/IndividualLicensingInfo", + "expandedlicensing_License": "https://spdx.org/rdf/3.0.1/terms/ExpandedLicensing/License", + "expandedlicensing_LicenseAddition": "https://spdx.org/rdf/3.0.1/terms/ExpandedLicensing/LicenseAddition", + "expandedlicensing_ListedLicense": "https://spdx.org/rdf/3.0.1/terms/ExpandedLicensing/ListedLicense", + "expandedlicensing_ListedLicenseException": "https://spdx.org/rdf/3.0.1/terms/ExpandedLicensing/ListedLicenseException", + "expandedlicensing_NoAssertionLicense": "https://spdx.org/rdf/3.0.1/terms/ExpandedLicensing/NoAssertionLicense", + "expandedlicensing_NoneLicense": "https://spdx.org/rdf/3.0.1/terms/ExpandedLicensing/NoneLicense", + "expandedlicensing_OrLaterOperator": "https://spdx.org/rdf/3.0.1/terms/ExpandedLicensing/OrLaterOperator", + "expandedlicensing_WithAdditionOperator": "https://spdx.org/rdf/3.0.1/terms/ExpandedLicensing/WithAdditionOperator", + "expandedlicensing_additionText": { + "@id": "https://spdx.org/rdf/3.0.1/terms/ExpandedLicensing/additionText", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "expandedlicensing_deprecatedVersion": { + "@id": "https://spdx.org/rdf/3.0.1/terms/ExpandedLicensing/deprecatedVersion", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "expandedlicensing_isDeprecatedAdditionId": { + "@id": "https://spdx.org/rdf/3.0.1/terms/ExpandedLicensing/isDeprecatedAdditionId", + "@type": "http://www.w3.org/2001/XMLSchema#boolean" + }, + "expandedlicensing_isDeprecatedLicenseId": { + "@id": "https://spdx.org/rdf/3.0.1/terms/ExpandedLicensing/isDeprecatedLicenseId", + "@type": "http://www.w3.org/2001/XMLSchema#boolean" + }, + "expandedlicensing_isFsfLibre": { + "@id": "https://spdx.org/rdf/3.0.1/terms/ExpandedLicensing/isFsfLibre", + "@type": "http://www.w3.org/2001/XMLSchema#boolean" + }, + "expandedlicensing_isOsiApproved": { + "@id": "https://spdx.org/rdf/3.0.1/terms/ExpandedLicensing/isOsiApproved", + "@type": "http://www.w3.org/2001/XMLSchema#boolean" + }, + "expandedlicensing_licenseXml": { + "@id": "https://spdx.org/rdf/3.0.1/terms/ExpandedLicensing/licenseXml", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "expandedlicensing_listVersionAdded": { + "@id": "https://spdx.org/rdf/3.0.1/terms/ExpandedLicensing/listVersionAdded", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "expandedlicensing_member": { + "@id": "https://spdx.org/rdf/3.0.1/terms/ExpandedLicensing/member", + "@type": "@vocab" + }, + "expandedlicensing_obsoletedBy": { + "@id": "https://spdx.org/rdf/3.0.1/terms/ExpandedLicensing/obsoletedBy", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "expandedlicensing_seeAlso": { + "@id": "https://spdx.org/rdf/3.0.1/terms/ExpandedLicensing/seeAlso", + "@type": "http://www.w3.org/2001/XMLSchema#anyURI" + }, + "expandedlicensing_standardAdditionTemplate": { + "@id": "https://spdx.org/rdf/3.0.1/terms/ExpandedLicensing/standardAdditionTemplate", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "expandedlicensing_standardLicenseHeader": { + "@id": "https://spdx.org/rdf/3.0.1/terms/ExpandedLicensing/standardLicenseHeader", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "expandedlicensing_standardLicenseTemplate": { + "@id": "https://spdx.org/rdf/3.0.1/terms/ExpandedLicensing/standardLicenseTemplate", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "expandedlicensing_subjectAddition": { + "@id": "https://spdx.org/rdf/3.0.1/terms/ExpandedLicensing/subjectAddition", + "@type": "@vocab" + }, + "expandedlicensing_subjectExtendableLicense": { + "@id": "https://spdx.org/rdf/3.0.1/terms/ExpandedLicensing/subjectExtendableLicense", + "@type": "@vocab" + }, + "expandedlicensing_subjectLicense": { + "@id": "https://spdx.org/rdf/3.0.1/terms/ExpandedLicensing/subjectLicense", + "@type": "@vocab" + }, + "extension": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Core/extension", + "@type": "@vocab" + }, + "extension_CdxPropertiesExtension": "https://spdx.org/rdf/3.0.1/terms/Extension/CdxPropertiesExtension", + "extension_CdxPropertyEntry": "https://spdx.org/rdf/3.0.1/terms/Extension/CdxPropertyEntry", + "extension_Extension": "https://spdx.org/rdf/3.0.1/terms/Extension/Extension", + "extension_cdxPropName": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Extension/cdxPropName", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "extension_cdxPropValue": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Extension/cdxPropValue", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "extension_cdxProperty": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Extension/cdxProperty", + "@type": "@vocab" + }, + "externalIdentifier": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Core/externalIdentifier", + "@type": "@vocab" + }, + "externalIdentifierType": { + "@context": { + "@vocab": "https://spdx.org/rdf/3.0.1/terms/Core/ExternalIdentifierType/" + }, + "@id": "https://spdx.org/rdf/3.0.1/terms/Core/externalIdentifierType", + "@type": "@vocab" + }, + "externalRef": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Core/externalRef", + "@type": "@vocab" + }, + "externalRefType": { + "@context": { + "@vocab": "https://spdx.org/rdf/3.0.1/terms/Core/ExternalRefType/" + }, + "@id": "https://spdx.org/rdf/3.0.1/terms/Core/externalRefType", + "@type": "@vocab" + }, + "externalSpdxId": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Core/externalSpdxId", + "@type": "http://www.w3.org/2001/XMLSchema#anyURI" + }, + "from": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Core/from", + "@type": "@vocab" + }, + "hashValue": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Core/hashValue", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "identifier": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Core/identifier", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "identifierLocator": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Core/identifierLocator", + "@type": "http://www.w3.org/2001/XMLSchema#anyURI" + }, + "import": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Core/import", + "@type": "@vocab" + }, + "issuingAuthority": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Core/issuingAuthority", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "key": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Core/key", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "locationHint": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Core/locationHint", + "@type": "http://www.w3.org/2001/XMLSchema#anyURI" + }, + "locator": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Core/locator", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "name": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Core/name", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "namespace": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Core/namespace", + "@type": "http://www.w3.org/2001/XMLSchema#anyURI" + }, + "namespaceMap": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Core/namespaceMap", + "@type": "@vocab" + }, + "originatedBy": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Core/originatedBy", + "@type": "@vocab" + }, + "packageVerificationCodeExcludedFile": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Core/packageVerificationCodeExcludedFile", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "prefix": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Core/prefix", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "profileConformance": { + "@context": { + "@vocab": "https://spdx.org/rdf/3.0.1/terms/Core/ProfileIdentifierType/" + }, + "@id": "https://spdx.org/rdf/3.0.1/terms/Core/profileConformance", + "@type": "@vocab" + }, + "relationshipType": { + "@context": { + "@vocab": "https://spdx.org/rdf/3.0.1/terms/Core/RelationshipType/" + }, + "@id": "https://spdx.org/rdf/3.0.1/terms/Core/relationshipType", + "@type": "@vocab" + }, + "releaseTime": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Core/releaseTime", + "@type": "http://www.w3.org/2001/XMLSchema#dateTimeStamp" + }, + "rootElement": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Core/rootElement", + "@type": "@vocab" + }, + "scope": { + "@context": { + "@vocab": "https://spdx.org/rdf/3.0.1/terms/Core/LifecycleScopeType/" + }, + "@id": "https://spdx.org/rdf/3.0.1/terms/Core/scope", + "@type": "@vocab" + }, + "security_CvssSeverityType": "https://spdx.org/rdf/3.0.1/terms/Security/CvssSeverityType", + "security_CvssV2VulnAssessmentRelationship": "https://spdx.org/rdf/3.0.1/terms/Security/CvssV2VulnAssessmentRelationship", + "security_CvssV3VulnAssessmentRelationship": "https://spdx.org/rdf/3.0.1/terms/Security/CvssV3VulnAssessmentRelationship", + "security_CvssV4VulnAssessmentRelationship": "https://spdx.org/rdf/3.0.1/terms/Security/CvssV4VulnAssessmentRelationship", + "security_EpssVulnAssessmentRelationship": "https://spdx.org/rdf/3.0.1/terms/Security/EpssVulnAssessmentRelationship", + "security_ExploitCatalogType": "https://spdx.org/rdf/3.0.1/terms/Security/ExploitCatalogType", + "security_ExploitCatalogVulnAssessmentRelationship": "https://spdx.org/rdf/3.0.1/terms/Security/ExploitCatalogVulnAssessmentRelationship", + "security_SsvcDecisionType": "https://spdx.org/rdf/3.0.1/terms/Security/SsvcDecisionType", + "security_SsvcVulnAssessmentRelationship": "https://spdx.org/rdf/3.0.1/terms/Security/SsvcVulnAssessmentRelationship", + "security_VexAffectedVulnAssessmentRelationship": "https://spdx.org/rdf/3.0.1/terms/Security/VexAffectedVulnAssessmentRelationship", + "security_VexFixedVulnAssessmentRelationship": "https://spdx.org/rdf/3.0.1/terms/Security/VexFixedVulnAssessmentRelationship", + "security_VexJustificationType": "https://spdx.org/rdf/3.0.1/terms/Security/VexJustificationType", + "security_VexNotAffectedVulnAssessmentRelationship": "https://spdx.org/rdf/3.0.1/terms/Security/VexNotAffectedVulnAssessmentRelationship", + "security_VexUnderInvestigationVulnAssessmentRelationship": "https://spdx.org/rdf/3.0.1/terms/Security/VexUnderInvestigationVulnAssessmentRelationship", + "security_VexVulnAssessmentRelationship": "https://spdx.org/rdf/3.0.1/terms/Security/VexVulnAssessmentRelationship", + "security_VulnAssessmentRelationship": "https://spdx.org/rdf/3.0.1/terms/Security/VulnAssessmentRelationship", + "security_Vulnerability": "https://spdx.org/rdf/3.0.1/terms/Security/Vulnerability", + "security_actionStatement": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Security/actionStatement", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "security_actionStatementTime": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Security/actionStatementTime", + "@type": "http://www.w3.org/2001/XMLSchema#dateTimeStamp" + }, + "security_assessedElement": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Security/assessedElement", + "@type": "@vocab" + }, + "security_catalogType": { + "@context": { + "@vocab": "https://spdx.org/rdf/3.0.1/terms/Security/ExploitCatalogType/" + }, + "@id": "https://spdx.org/rdf/3.0.1/terms/Security/catalogType", + "@type": "@vocab" + }, + "security_decisionType": { + "@context": { + "@vocab": "https://spdx.org/rdf/3.0.1/terms/Security/SsvcDecisionType/" + }, + "@id": "https://spdx.org/rdf/3.0.1/terms/Security/decisionType", + "@type": "@vocab" + }, + "security_exploited": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Security/exploited", + "@type": "http://www.w3.org/2001/XMLSchema#boolean" + }, + "security_impactStatement": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Security/impactStatement", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "security_impactStatementTime": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Security/impactStatementTime", + "@type": "http://www.w3.org/2001/XMLSchema#dateTimeStamp" + }, + "security_justificationType": { + "@context": { + "@vocab": "https://spdx.org/rdf/3.0.1/terms/Security/VexJustificationType/" + }, + "@id": "https://spdx.org/rdf/3.0.1/terms/Security/justificationType", + "@type": "@vocab" + }, + "security_locator": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Security/locator", + "@type": "http://www.w3.org/2001/XMLSchema#anyURI" + }, + "security_modifiedTime": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Security/modifiedTime", + "@type": "http://www.w3.org/2001/XMLSchema#dateTimeStamp" + }, + "security_percentile": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Security/percentile", + "@type": "http://www.w3.org/2001/XMLSchema#decimal" + }, + "security_probability": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Security/probability", + "@type": "http://www.w3.org/2001/XMLSchema#decimal" + }, + "security_publishedTime": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Security/publishedTime", + "@type": "http://www.w3.org/2001/XMLSchema#dateTimeStamp" + }, + "security_score": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Security/score", + "@type": "http://www.w3.org/2001/XMLSchema#decimal" + }, + "security_severity": { + "@context": { + "@vocab": "https://spdx.org/rdf/3.0.1/terms/Security/CvssSeverityType/" + }, + "@id": "https://spdx.org/rdf/3.0.1/terms/Security/severity", + "@type": "@vocab" + }, + "security_statusNotes": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Security/statusNotes", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "security_vectorString": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Security/vectorString", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "security_vexVersion": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Security/vexVersion", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "security_withdrawnTime": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Security/withdrawnTime", + "@type": "http://www.w3.org/2001/XMLSchema#dateTimeStamp" + }, + "simplelicensing_AnyLicenseInfo": "https://spdx.org/rdf/3.0.1/terms/SimpleLicensing/AnyLicenseInfo", + "simplelicensing_LicenseExpression": "https://spdx.org/rdf/3.0.1/terms/SimpleLicensing/LicenseExpression", + "simplelicensing_SimpleLicensingText": "https://spdx.org/rdf/3.0.1/terms/SimpleLicensing/SimpleLicensingText", + "simplelicensing_customIdToUri": { + "@id": "https://spdx.org/rdf/3.0.1/terms/SimpleLicensing/customIdToUri", + "@type": "@vocab" + }, + "simplelicensing_licenseExpression": { + "@id": "https://spdx.org/rdf/3.0.1/terms/SimpleLicensing/licenseExpression", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "simplelicensing_licenseListVersion": { + "@id": "https://spdx.org/rdf/3.0.1/terms/SimpleLicensing/licenseListVersion", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "simplelicensing_licenseText": { + "@id": "https://spdx.org/rdf/3.0.1/terms/SimpleLicensing/licenseText", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "software_ContentIdentifier": "https://spdx.org/rdf/3.0.1/terms/Software/ContentIdentifier", + "software_ContentIdentifierType": "https://spdx.org/rdf/3.0.1/terms/Software/ContentIdentifierType", + "software_File": "https://spdx.org/rdf/3.0.1/terms/Software/File", + "software_FileKindType": "https://spdx.org/rdf/3.0.1/terms/Software/FileKindType", + "software_Package": "https://spdx.org/rdf/3.0.1/terms/Software/Package", + "software_Sbom": "https://spdx.org/rdf/3.0.1/terms/Software/Sbom", + "software_SbomType": "https://spdx.org/rdf/3.0.1/terms/Software/SbomType", + "software_Snippet": "https://spdx.org/rdf/3.0.1/terms/Software/Snippet", + "software_SoftwareArtifact": "https://spdx.org/rdf/3.0.1/terms/Software/SoftwareArtifact", + "software_SoftwarePurpose": "https://spdx.org/rdf/3.0.1/terms/Software/SoftwarePurpose", + "software_additionalPurpose": { + "@context": { + "@vocab": "https://spdx.org/rdf/3.0.1/terms/Software/SoftwarePurpose/" + }, + "@id": "https://spdx.org/rdf/3.0.1/terms/Software/additionalPurpose", + "@type": "@vocab" + }, + "software_attributionText": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Software/attributionText", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "software_byteRange": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Software/byteRange", + "@type": "https://spdx.org/rdf/3.0.1/terms/Core/PositiveIntegerRange" + }, + "software_contentIdentifier": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Software/contentIdentifier", + "@type": "https://spdx.org/rdf/3.0.1/terms/Software/ContentIdentifier" + }, + "software_contentIdentifierType": { + "@context": { + "@vocab": "https://spdx.org/rdf/3.0.1/terms/Software/ContentIdentifierType/" + }, + "@id": "https://spdx.org/rdf/3.0.1/terms/Software/contentIdentifierType", + "@type": "@vocab" + }, + "software_contentIdentifierValue": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Software/contentIdentifierValue", + "@type": "http://www.w3.org/2001/XMLSchema#anyURI" + }, + "software_copyrightText": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Software/copyrightText", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "software_downloadLocation": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Software/downloadLocation", + "@type": "http://www.w3.org/2001/XMLSchema#anyURI" + }, + "software_fileKind": { + "@context": { + "@vocab": "https://spdx.org/rdf/3.0.1/terms/Software/FileKindType/" + }, + "@id": "https://spdx.org/rdf/3.0.1/terms/Software/fileKind", + "@type": "@vocab" + }, + "software_homePage": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Software/homePage", + "@type": "http://www.w3.org/2001/XMLSchema#anyURI" + }, + "software_lineRange": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Software/lineRange", + "@type": "https://spdx.org/rdf/3.0.1/terms/Core/PositiveIntegerRange" + }, + "software_packageUrl": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Software/packageUrl", + "@type": "http://www.w3.org/2001/XMLSchema#anyURI" + }, + "software_packageVersion": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Software/packageVersion", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "software_primaryPurpose": { + "@context": { + "@vocab": "https://spdx.org/rdf/3.0.1/terms/Software/SoftwarePurpose/" + }, + "@id": "https://spdx.org/rdf/3.0.1/terms/Software/primaryPurpose", + "@type": "@vocab" + }, + "software_sbomType": { + "@context": { + "@vocab": "https://spdx.org/rdf/3.0.1/terms/Software/SbomType/" + }, + "@id": "https://spdx.org/rdf/3.0.1/terms/Software/sbomType", + "@type": "@vocab" + }, + "software_snippetFromFile": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Software/snippetFromFile", + "@type": "@vocab" + }, + "software_sourceInfo": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Software/sourceInfo", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "spdx": "https://spdx.org/rdf/3.0.1/terms/", + "spdxId": "@id", + "specVersion": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Core/specVersion", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "standardName": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Core/standardName", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "startTime": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Core/startTime", + "@type": "http://www.w3.org/2001/XMLSchema#dateTimeStamp" + }, + "statement": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Core/statement", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "subject": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Core/subject", + "@type": "@vocab" + }, + "summary": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Core/summary", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "suppliedBy": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Core/suppliedBy", + "@type": "@vocab" + }, + "supportLevel": { + "@context": { + "@vocab": "https://spdx.org/rdf/3.0.1/terms/Core/SupportType/" + }, + "@id": "https://spdx.org/rdf/3.0.1/terms/Core/supportLevel", + "@type": "@vocab" + }, + "to": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Core/to", + "@type": "@vocab" + }, + "type": "@type", + "validUntilTime": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Core/validUntilTime", + "@type": "http://www.w3.org/2001/XMLSchema#dateTimeStamp" + }, + "value": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Core/value", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "verifiedUsing": { + "@id": "https://spdx.org/rdf/3.0.1/terms/Core/verifiedUsing", + "@type": "@vocab" + } + } +} \ No newline at end of file diff --git a/tests/data/spdx/3.0.1/spdx-json-serialize-annotations.ttl b/tests/data/spdx/3.0.1/spdx-json-serialize-annotations.ttl new file mode 100644 index 00000000..5e3297df --- /dev/null +++ b/tests/data/spdx/3.0.1/spdx-json-serialize-annotations.ttl @@ -0,0 +1,10 @@ +@base . +@prefix sh-to-code: . + + ; + sh-to-code:idPropertyName "spdxId" + . + + ; + sh-to-code:isExtensible true + . diff --git a/tests/data/spdx/3.0.1/spdx-model.ttl b/tests/data/spdx/3.0.1/spdx-model.ttl new file mode 100644 index 00000000..587eea20 --- /dev/null +++ b/tests/data/spdx/3.0.1/spdx-model.ttl @@ -0,0 +1,3333 @@ +@prefix dcterms: . +@prefix ns1: . +@prefix ns2: . +@prefix ns3: . +@prefix ns4: . +@prefix ns5: . +@prefix ns6: . +@prefix omg-ann: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix sh: . +@prefix spdx: . +@prefix xsd: . + +ns1:NoAssertionElement a owl:NamedIndividual, + ns1:IndividualElement ; + rdfs:comment """An Individual Value for Element representing a set of Elements of unknown +identify or cardinality (number)."""@en ; + ns1:creationInfo . + +ns1:NoneElement a owl:NamedIndividual, + ns1:IndividualElement ; + rdfs:comment """An Individual Value for Element representing a set of Elements with +cardinality (number/count) of zero."""@en ; + ns1:creationInfo . + +ns2:NoAssertionLicense a owl:NamedIndividual, + ns2:IndividualLicensingInfo ; + rdfs:comment """An Individual Value for License when no assertion can be made about its actual +value."""@en ; + owl:sameAs ; + ns1:creationInfo . + +ns2:NoneLicense a owl:NamedIndividual, + ns2:IndividualLicensingInfo ; + rdfs:comment """An Individual Value for License where the SPDX data creator determines that no +license is present."""@en ; + owl:sameAs ; + ns1:creationInfo . + + a owl:Class, + sh:NodeShape ; + rdfs:comment "A type of extension consisting of a list of name value pairs."@en ; + rdfs:subClassOf ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:property [ sh:class ; + sh:minCount 1 ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ] . + + a ns1:CreationInfo ; + rdfs:comment "This individual element was defined by the spec."@en ; + ns1:created "2024-11-22T03:00:01Z"^^xsd:dateTimeStamp ; + ns1:createdBy ns1:SpdxOrganization ; + ns1:specVersion "3.0.1" . + + a ns1:CreationInfo ; + rdfs:comment "This individual element was defined by the spec."@en ; + ns1:created "2024-11-22T03:00:01Z"^^xsd:dateTimeStamp ; + ns1:createdBy ns1:SpdxOrganization ; + ns1:specVersion "3.0.1" . + + a ns1:CreationInfo ; + rdfs:comment "This individual element was defined by the spec."@en ; + ns1:created "2024-11-22T03:00:01Z"^^xsd:dateTimeStamp ; + ns1:createdBy ns1:SpdxOrganization ; + ns1:specVersion "3.0.1" . + + a ns1:CreationInfo ; + rdfs:comment "This individual element was defined by the spec."@en ; + ns1:created "2024-11-22T03:00:01Z"^^xsd:dateTimeStamp ; + ns1:createdBy ns1:SpdxOrganization ; + ns1:specVersion "3.0.1" . + + a ns1:CreationInfo ; + rdfs:comment "This individual element was defined by the spec."@en ; + ns1:created "2024-11-22T03:00:01Z"^^xsd:dateTimeStamp ; + ns1:createdBy ns1:SpdxOrganization ; + ns1:specVersion "3.0.1" . + +spdx: a owl:Ontology ; + rdfs:label "System Package Data Exchange™ (SPDX®) Ontology"@en ; + dcterms:abstract "This ontology defines the terms and relationships used in the SPDX specification to describe system packages"@en ; + dcterms:created "2024-04-05"^^xsd:date ; + dcterms:creator "SPDX Project"@en ; + dcterms:license ; + dcterms:references ; + dcterms:title "System Package Data Exchange (SPDX) Ontology"@en ; + owl:versionIRI spdx: ; + omg-ann:copyright "Copyright (C) 2024 SPDX Project"@en . + +ns4:AIPackage a owl:Class, + sh:NodeShape ; + rdfs:comment "Specifies an AI package and its associated information."@en ; + rdfs:subClassOf ns3:Package ; + sh:nodeKind sh:IRI ; + sh:property [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns4:informationAboutTraining ], + [ sh:datatype xsd:string ; + sh:nodeKind sh:Literal ; + sh:path ns4:modelDataPreprocessing ], + [ sh:datatype xsd:string ; + sh:nodeKind sh:Literal ; + sh:path ns4:typeOfModel ], + [ sh:class ns4:SafetyRiskAssessmentType ; + sh:in ( ) ; + sh:maxCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns4:safetyRiskAssessment ], + [ sh:class ns1:DictionaryEntry ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ns4:metricDecisionThreshold ], + [ sh:class ns1:PresenceType ; + sh:in ( ) ; + sh:maxCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns4:useSensitivePersonalInformation ], + [ sh:class ns4:EnergyConsumption ; + sh:maxCount 1 ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ns4:energyConsumption ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns4:limitation ], + [ sh:class ns1:DictionaryEntry ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ns4:hyperparameter ], + [ sh:class ns1:PresenceType ; + sh:in ( ) ; + sh:maxCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns4:autonomyType ], + [ sh:datatype xsd:string ; + sh:nodeKind sh:Literal ; + sh:path ns4:domain ], + [ sh:datatype xsd:string ; + sh:nodeKind sh:Literal ; + sh:path ns4:modelExplainability ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns4:informationAboutApplication ], + [ sh:class ns1:DictionaryEntry ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ns4:metric ], + [ sh:datatype xsd:string ; + sh:nodeKind sh:Literal ; + sh:path ns4:standardCompliance ] . + + a owl:NamedIndividual, + ns4:EnergyUnitType ; + rdfs:label "kilowattHour" ; + rdfs:comment "Kilowatt-hour."@en . + + a owl:NamedIndividual, + ns4:EnergyUnitType ; + rdfs:label "megajoule" ; + rdfs:comment "Megajoule."@en . + + a owl:NamedIndividual, + ns4:EnergyUnitType ; + rdfs:label "other" ; + rdfs:comment "Any other units of energy measurement."@en . + + a owl:NamedIndividual, + ns4:SafetyRiskAssessmentType ; + rdfs:label "high" ; + rdfs:comment "The second-highest level of risk posed by an AI system."@en . + + a owl:NamedIndividual, + ns4:SafetyRiskAssessmentType ; + rdfs:label "low" ; + rdfs:comment "Low/no risk is posed by an AI system."@en . + + a owl:NamedIndividual, + ns4:SafetyRiskAssessmentType ; + rdfs:label "medium" ; + rdfs:comment "The third-highest level of risk posed by an AI system."@en . + + a owl:NamedIndividual, + ns4:SafetyRiskAssessmentType ; + rdfs:label "serious" ; + rdfs:comment "The highest level of risk posed by an AI system."@en . + +ns4:autonomyType a owl:ObjectProperty ; + rdfs:comment """Indicates whether the system can perform a decision or action without human +involvement or guidance."""@en ; + rdfs:range ns1:PresenceType . + +ns4:domain a owl:DatatypeProperty ; + rdfs:comment "Captures the domain in which the AI package can be used."@en ; + rdfs:range xsd:string . + +ns4:energyConsumption a owl:ObjectProperty ; + rdfs:comment "Indicates the amount of energy consumption incurred by an AI model."@en ; + rdfs:range ns4:EnergyConsumption . + +ns4:energyQuantity a owl:DatatypeProperty ; + rdfs:comment "Represents the energy quantity."@en ; + rdfs:range xsd:decimal . + +ns4:energyUnit a owl:ObjectProperty ; + rdfs:comment "Specifies the unit in which energy is measured."@en ; + rdfs:range ns4:EnergyUnitType . + +ns4:finetuningEnergyConsumption a owl:ObjectProperty ; + rdfs:comment """Specifies the amount of energy consumed when finetuning the AI model that is +being used in the AI system."""@en ; + rdfs:range ns4:EnergyConsumptionDescription . + +ns4:hyperparameter a owl:ObjectProperty ; + rdfs:comment """Records a hyperparameter used to build the AI model contained in the AI +package."""@en ; + rdfs:range ns1:DictionaryEntry . + +ns4:inferenceEnergyConsumption a owl:ObjectProperty ; + rdfs:comment """Specifies the amount of energy consumed during inference time by an AI model +that is being used in the AI system."""@en ; + rdfs:range ns4:EnergyConsumptionDescription . + +ns4:informationAboutApplication a owl:DatatypeProperty ; + rdfs:comment """Provides relevant information about the AI software, not including the model +description."""@en ; + rdfs:range xsd:string . + +ns4:informationAboutTraining a owl:DatatypeProperty ; + rdfs:comment "Describes relevant information about different steps of the training process."@en ; + rdfs:range xsd:string . + +ns4:limitation a owl:DatatypeProperty ; + rdfs:comment "Captures a limitation of the AI software."@en ; + rdfs:range xsd:string . + +ns4:metric a owl:ObjectProperty ; + rdfs:comment "Records the measurement of prediction quality of the AI model."@en ; + rdfs:range ns1:DictionaryEntry . + +ns4:metricDecisionThreshold a owl:ObjectProperty ; + rdfs:comment """Captures the threshold that was used for computation of a metric described in +the metric field."""@en ; + rdfs:range ns1:DictionaryEntry . + +ns4:modelDataPreprocessing a owl:DatatypeProperty ; + rdfs:comment """Describes all the preprocessing steps applied to the training data before the +model training."""@en ; + rdfs:range xsd:string . + +ns4:modelExplainability a owl:DatatypeProperty ; + rdfs:comment "Describes methods that can be used to explain the results from the AI model."@en ; + rdfs:range xsd:string . + +ns4:safetyRiskAssessment a owl:ObjectProperty ; + rdfs:comment "Records the results of general safety risk assessment of the AI system."@en ; + rdfs:range ns4:SafetyRiskAssessmentType . + +ns4:standardCompliance a owl:DatatypeProperty ; + rdfs:comment "Captures a standard that is being complied with."@en ; + rdfs:range xsd:string . + +ns4:trainingEnergyConsumption a owl:ObjectProperty ; + rdfs:comment """Specifies the amount of energy consumed when training the AI model that is +being used in the AI system."""@en ; + rdfs:range ns4:EnergyConsumptionDescription . + +ns4:typeOfModel a owl:DatatypeProperty ; + rdfs:comment "Records the type of the model used in the AI software."@en ; + rdfs:range xsd:string . + +ns4:useSensitivePersonalInformation a owl:ObjectProperty ; + rdfs:comment """Records if sensitive personal information is used during model training or +could be used during the inference."""@en ; + rdfs:range ns1:PresenceType . + + a owl:Class, + sh:NodeShape ; + rdfs:comment "Class that describes a build instance of software/artifacts."@en ; + rdfs:subClassOf ns1:Element ; + sh:nodeKind sh:IRI ; + sh:property [ sh:datatype xsd:anyURI ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ], + [ sh:datatype xsd:dateTimeStamp ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ; + sh:pattern "^\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\dZ$" ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ], + [ sh:class ns1:Hash ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ], + [ sh:datatype xsd:dateTimeStamp ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ; + sh:pattern "^\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\dZ$" ], + [ sh:datatype xsd:anyURI ; + sh:nodeKind sh:Literal ; + sh:path ], + [ sh:class ns1:DictionaryEntry ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ], + [ sh:datatype xsd:string ; + sh:nodeKind sh:Literal ; + sh:path ], + [ sh:class ns1:DictionaryEntry ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ] . + + a owl:DatatypeProperty ; + rdfs:comment "Property that describes the time at which a build stops."@en ; + rdfs:range xsd:dateTimeStamp . + + a owl:DatatypeProperty ; + rdfs:comment """A buildId is a locally unique identifier used by a builder to identify a unique +instance of a build produced by it."""@en ; + rdfs:range xsd:string . + + a owl:DatatypeProperty ; + rdfs:comment "Property describing the start time of a build."@en ; + rdfs:range xsd:dateTimeStamp . + + a owl:DatatypeProperty ; + rdfs:comment """A buildType is a hint that is used to indicate the toolchain, platform, or +infrastructure that the build was invoked on."""@en ; + rdfs:range xsd:anyURI . + + a owl:ObjectProperty ; + rdfs:comment """Property that describes the digest of the build configuration file used to +invoke a build."""@en ; + rdfs:range ns1:Hash . + + a owl:DatatypeProperty ; + rdfs:comment "Property describes the invocation entrypoint of a build."@en ; + rdfs:range xsd:string . + + a owl:DatatypeProperty ; + rdfs:comment "Property that describes the URI of the build configuration source file."@en ; + rdfs:range xsd:anyURI . + + a owl:ObjectProperty ; + rdfs:comment "Property describing the session in which a build is invoked."@en ; + rdfs:range ns1:DictionaryEntry . + + a owl:ObjectProperty ; + rdfs:comment "Property describing a parameter used in an instance of a build."@en ; + rdfs:range ns1:DictionaryEntry . + +ns1:Annotation a owl:Class, + sh:NodeShape ; + rdfs:comment "An assertion made in relation to one or more elements."@en ; + rdfs:subClassOf ns1:Element ; + sh:nodeKind sh:IRI ; + sh:property [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:contentType ; + sh:pattern "^[^\\/]+\\/[^\\/]+$" ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:statement ], + [ sh:class ns1:Element ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns1:subject ], + [ sh:class ns1:AnnotationType ; + sh:in ( ) ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns1:annotationType ] . + + a owl:NamedIndividual, + ns1:AnnotationType ; + rdfs:label "other" ; + rdfs:comment "Used to store extra information about an Element which is not part of a review (e.g. extra information provided during the creation of the Element)."@en . + + a owl:NamedIndividual, + ns1:AnnotationType ; + rdfs:label "review" ; + rdfs:comment "Used when someone reviews the Element."@en . + + a owl:NamedIndividual, + ns1:ExternalIdentifierType ; + rdfs:label "cpe22" ; + rdfs:comment "[Common Platform Enumeration Specification 2.2](https://cpe.mitre.org/files/cpe-specification_2.2.pdf)"@en . + + a owl:NamedIndividual, + ns1:ExternalIdentifierType ; + rdfs:label "cpe23" ; + rdfs:comment "[Common Platform Enumeration: Naming Specification Version 2.3](https://csrc.nist.gov/publications/detail/nistir/7695/final)"@en . + + a owl:NamedIndividual, + ns1:ExternalIdentifierType ; + rdfs:label "cve" ; + rdfs:comment "Common Vulnerabilities and Exposures identifiers, an identifier for a specific software flaw defined within the official CVE Dictionary and that conforms to the [CVE specification](https://csrc.nist.gov/glossary/term/cve_id)."@en . + + a owl:NamedIndividual, + ns1:ExternalIdentifierType ; + rdfs:label "email" ; + rdfs:comment "Email address, as defined in [RFC 3696](https://datatracker.ietf.org/doc/rfc3986/) Section 3."@en . + + a owl:NamedIndividual, + ns1:ExternalIdentifierType ; + rdfs:label "gitoid" ; + rdfs:comment "[Gitoid](https://www.iana.org/assignments/uri-schemes/prov/gitoid), stands for [Git Object ID](https://git-scm.com/book/en/v2/Git-Internals-Git-Objects). A gitoid of type blob is a unique hash of a binary artifact. A gitoid may represent either an [Artifact Identifier](https://github.com/omnibor/spec/blob/eb1ee5c961c16215eb8709b2975d193a2007a35d/spec/SPEC.md#artifact-identifier-types) for the software artifact or an [Input Manifest Identifier](https://github.com/omnibor/spec/blob/eb1ee5c961c16215eb8709b2975d193a2007a35d/spec/SPEC.md#input-manifest-identifier) for the software artifact's associated [Artifact Input Manifest](https://github.com/omnibor/spec/blob/eb1ee5c961c16215eb8709b2975d193a2007a35d/spec/SPEC.md#artifact-input-manifest); this ambiguity exists because the Artifact Input Manifest is itself an artifact, and the gitoid of that artifact is its valid identifier. Gitoids calculated on software artifacts (Snippet, File, or Package Elements) should be recorded in the SPDX 3.0 SoftwareArtifact's contentIdentifier property. Gitoids calculated on the Artifact Input Manifest (Input Manifest Identifier) should be recorded in the SPDX 3.0 Element's externalIdentifier property. See [OmniBOR Specification](https://github.com/omnibor/spec/), a minimalistic specification for describing software [Artifact Dependency Graphs](https://github.com/omnibor/spec/blob/eb1ee5c961c16215eb8709b2975d193a2007a35d/spec/SPEC.md#artifact-dependency-graph-adg)."@en . + + a owl:NamedIndividual, + ns1:ExternalIdentifierType ; + rdfs:label "other" ; + rdfs:comment "Used when the type does not match any of the other options."@en . + + a owl:NamedIndividual, + ns1:ExternalIdentifierType ; + rdfs:label "packageUrl" ; + rdfs:comment "Package URL, as defined in the corresponding [Annex](../../../annexes/pkg-url-specification.md) of this specification."@en . + + a owl:NamedIndividual, + ns1:ExternalIdentifierType ; + rdfs:label "securityOther" ; + rdfs:comment "Used when there is a security related identifier of unspecified type."@en . + + a owl:NamedIndividual, + ns1:ExternalIdentifierType ; + rdfs:label "swhid" ; + rdfs:comment "SoftWare Hash IDentifier, a persistent intrinsic identifier for digital artifacts, such as files, trees (also known as directories or folders), commits, and other objects typically found in version control systems. The format of the identifiers is defined in the [SWHID specification](https://www.swhid.org/specification/v1.1/4.Syntax) (ISO/IEC DIS 18670). They typically look like `swh:1:cnt:94a9ed024d3859793618152ea559a168bbcbb5e2`."@en . + + a owl:NamedIndividual, + ns1:ExternalIdentifierType ; + rdfs:label "swid" ; + rdfs:comment "Concise Software Identification (CoSWID) tag, as defined in [RFC 9393](https://datatracker.ietf.org/doc/rfc9393/) Section 2.3."@en . + + a owl:NamedIndividual, + ns1:ExternalIdentifierType ; + rdfs:label "urlScheme" ; + rdfs:comment "[Uniform Resource Identifier (URI) Schemes](https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml). The scheme used in order to locate a resource."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "altDownloadLocation" ; + rdfs:comment "A reference to an alternative download location."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "altWebPage" ; + rdfs:comment "A reference to an alternative web page."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "binaryArtifact" ; + rdfs:comment "A reference to binary artifacts related to a package."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "bower" ; + rdfs:comment "A reference to a Bower package. The package locator format, looks like `package#version`, is defined in the \"install\" section of [Bower API documentation](https://bower.io/docs/api/#install)."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "buildMeta" ; + rdfs:comment "A reference build metadata related to a published package."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "buildSystem" ; + rdfs:comment "A reference build system used to create or publish the package."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "certificationReport" ; + rdfs:comment "A reference to a certification report for a package from an accredited/independent body."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "chat" ; + rdfs:comment "A reference to the instant messaging system used by the maintainer for a package."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "componentAnalysisReport" ; + rdfs:comment "A reference to a Software Composition Analysis (SCA) report."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "cwe" ; + rdfs:comment "[Common Weakness Enumeration](https://csrc.nist.gov/glossary/term/common_weakness_enumeration). A reference to a source of software flaw defined within the official [CWE List](https://cwe.mitre.org/data/) that conforms to the [CWE specification](https://cwe.mitre.org/)."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "documentation" ; + rdfs:comment "A reference to the documentation for a package."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "dynamicAnalysisReport" ; + rdfs:comment "A reference to a dynamic analysis report for a package."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "eolNotice" ; + rdfs:comment "A reference to the End Of Sale (EOS) and/or End Of Life (EOL) information related to a package."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "exportControlAssessment" ; + rdfs:comment "A reference to a export control assessment for a package."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "funding" ; + rdfs:comment "A reference to funding information related to a package."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "issueTracker" ; + rdfs:comment "A reference to the issue tracker for a package."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "license" ; + rdfs:comment "A reference to additional license information related to an artifact."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "mailingList" ; + rdfs:comment "A reference to the mailing list used by the maintainer for a package."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "mavenCentral" ; + rdfs:comment "A reference to a Maven repository artifact. The artifact locator format is defined in the [Maven documentation](https://maven.apache.org/guides/mini/guide-naming-conventions.html) and looks like `groupId:artifactId[:version]`."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "metrics" ; + rdfs:comment "A reference to metrics related to package such as OpenSSF scorecards."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "npm" ; + rdfs:comment "A reference to an npm package. The package locator format is defined in the [npm documentation](https://docs.npmjs.com/cli/v10/configuring-npm/package-json) and looks like `package@version`."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "nuget" ; + rdfs:comment "A reference to a NuGet package. The package locator format is defined in the [NuGet documentation](https://docs.nuget.org) and looks like `package/version`."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "other" ; + rdfs:comment "Used when the type does not match any of the other options."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "privacyAssessment" ; + rdfs:comment "A reference to a privacy assessment for a package."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "productMetadata" ; + rdfs:comment "A reference to additional product metadata such as reference within organization's product catalog."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "purchaseOrder" ; + rdfs:comment "A reference to a purchase order for a package."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "qualityAssessmentReport" ; + rdfs:comment "A reference to a quality assessment for a package."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "releaseHistory" ; + rdfs:comment "A reference to a published list of releases for a package."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "releaseNotes" ; + rdfs:comment "A reference to the release notes for a package."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "riskAssessment" ; + rdfs:comment "A reference to a risk assessment for a package."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "runtimeAnalysisReport" ; + rdfs:comment "A reference to a runtime analysis report for a package."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "secureSoftwareAttestation" ; + rdfs:comment "A reference to information assuring that the software is developed using security practices as defined by [NIST SP 800-218 Secure Software Development Framework (SSDF) Version 1.1](https://csrc.nist.gov/pubs/sp/800/218/final) or [CISA Secure Software Development Attestation Form](https://www.cisa.gov/resources-tools/resources/secure-software-development-attestation-form)."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "securityAdversaryModel" ; + rdfs:comment "A reference to the security adversary model for a package."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "securityAdvisory" ; + rdfs:comment "A reference to a published security advisory (where advisory as defined per [ISO 29147:2018](https://www.iso.org/standard/72311.html)) that may affect one or more elements, e.g., vendor advisories or specific NVD entries."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "securityFix" ; + rdfs:comment "A reference to the patch or source code that fixes a vulnerability."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "securityOther" ; + rdfs:comment "A reference to related security information of unspecified type."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "securityPenTestReport" ; + rdfs:comment "A reference to a [penetration test](https://en.wikipedia.org/wiki/Penetration_test) report for a package."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "securityPolicy" ; + rdfs:comment "A reference to instructions for reporting newly discovered security vulnerabilities for a package."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "securityThreatModel" ; + rdfs:comment "A reference the [security threat model](https://en.wikipedia.org/wiki/Threat_model) for a package."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "socialMedia" ; + rdfs:comment "A reference to a social media channel for a package."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "sourceArtifact" ; + rdfs:comment "A reference to an artifact containing the sources for a package."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "staticAnalysisReport" ; + rdfs:comment "A reference to a static analysis report for a package."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "support" ; + rdfs:comment "A reference to the software support channel or other support information for a package."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "vcs" ; + rdfs:comment "A reference to a version control system related to a software artifact."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "vulnerabilityDisclosureReport" ; + rdfs:comment "A reference to a Vulnerability Disclosure Report (VDR) which provides the software supplier's analysis and findings describing the impact (or lack of impact) that reported vulnerabilities have on packages or products in the supplier's SBOM as defined in [NIST SP 800-161 Cybersecurity Supply Chain Risk Management Practices for Systems and Organizations](https://csrc.nist.gov/pubs/sp/800/161/r1/final)."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "vulnerabilityExploitabilityAssessment" ; + rdfs:comment "A reference to a Vulnerability Exploitability eXchange (VEX) statement which provides information on whether a product is impacted by a specific vulnerability in an included package and, if affected, whether there are actions recommended to remediate. See also [NTIA VEX one-page summary](https://ntia.gov/files/ntia/publications/vex_one-page_summary.pdf)."@en . + + a owl:NamedIndividual, + ns1:LifecycleScopeType ; + rdfs:label "build" ; + rdfs:comment "A relationship has specific context implications during an element's build phase, during development."@en . + + a owl:NamedIndividual, + ns1:LifecycleScopeType ; + rdfs:label "design" ; + rdfs:comment "A relationship has specific context implications during an element's design."@en . + + a owl:NamedIndividual, + ns1:LifecycleScopeType ; + rdfs:label "development" ; + rdfs:comment "A relationship has specific context implications during development phase of an element."@en . + + a owl:NamedIndividual, + ns1:LifecycleScopeType ; + rdfs:label "other" ; + rdfs:comment "A relationship has other specific context information necessary to capture that the above set of enumerations does not handle."@en . + + a owl:NamedIndividual, + ns1:LifecycleScopeType ; + rdfs:label "runtime" ; + rdfs:comment "A relationship has specific context implications during the execution phase of an element."@en . + + a owl:NamedIndividual, + ns1:LifecycleScopeType ; + rdfs:label "test" ; + rdfs:comment "A relationship has specific context implications during an element's testing phase, during development."@en . + +ns1:LifecycleScopedRelationship a owl:Class, + sh:NodeShape ; + rdfs:comment "Provide context for a relationship that occurs in the lifecycle."@en ; + rdfs:subClassOf ns1:Relationship ; + sh:nodeKind sh:IRI ; + sh:property [ sh:class ns1:LifecycleScopeType ; + sh:in ( ) ; + sh:maxCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns1:scope ] . + +ns1:PackageVerificationCode a owl:Class, + sh:NodeShape ; + rdfs:comment "An SPDX version 2.X compatible verification method for software packages."@en ; + rdfs:subClassOf ns1:IntegrityMethod ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:property [ sh:datatype xsd:string ; + sh:nodeKind sh:Literal ; + sh:path ns1:packageVerificationCodeExcludedFile ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:hashValue ], + [ sh:class ns1:HashAlgorithm ; + sh:in ( ) ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns1:algorithm ] . + +ns1:Person a owl:Class ; + rdfs:comment "An individual human being."@en ; + rdfs:subClassOf ns1:Agent ; + sh:nodeKind sh:IRI . + + a owl:NamedIndividual, + ns1:ProfileIdentifierType ; + rdfs:label "ai" ; + rdfs:comment "the element follows the AI profile specification"@en . + + a owl:NamedIndividual, + ns1:ProfileIdentifierType ; + rdfs:label "build" ; + rdfs:comment "the element follows the Build profile specification"@en . + + a owl:NamedIndividual, + ns1:ProfileIdentifierType ; + rdfs:label "core" ; + rdfs:comment "the element follows the Core profile specification"@en . + + a owl:NamedIndividual, + ns1:ProfileIdentifierType ; + rdfs:label "dataset" ; + rdfs:comment "the element follows the Dataset profile specification"@en . + + a owl:NamedIndividual, + ns1:ProfileIdentifierType ; + rdfs:label "expandedLicensing" ; + rdfs:comment "the element follows the ExpandedLicensing profile specification"@en . + + a owl:NamedIndividual, + ns1:ProfileIdentifierType ; + rdfs:label "extension" ; + rdfs:comment "the element follows the Extension profile specification"@en . + + a owl:NamedIndividual, + ns1:ProfileIdentifierType ; + rdfs:label "lite" ; + rdfs:comment "the element follows the Lite profile specification"@en . + + a owl:NamedIndividual, + ns1:ProfileIdentifierType ; + rdfs:label "security" ; + rdfs:comment "the element follows the Security profile specification"@en . + + a owl:NamedIndividual, + ns1:ProfileIdentifierType ; + rdfs:label "simpleLicensing" ; + rdfs:comment "the element follows the SimpleLicensing profile specification"@en . + + a owl:NamedIndividual, + ns1:ProfileIdentifierType ; + rdfs:label "software" ; + rdfs:comment "the element follows the Software profile specification"@en . + + a owl:NamedIndividual, + ns1:RelationshipCompleteness ; + rdfs:label "complete" ; + rdfs:comment "The relationship is known to be exhaustive."@en . + + a owl:NamedIndividual, + ns1:RelationshipCompleteness ; + rdfs:label "incomplete" ; + rdfs:comment "The relationship is known not to be exhaustive."@en . + + a owl:NamedIndividual, + ns1:RelationshipCompleteness ; + rdfs:label "noAssertion" ; + rdfs:comment "No assertion can be made about the completeness of the relationship."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "affects" ; + rdfs:comment "The `from` Vulnerability affects each `to` Element. The use of the `affects` type is constrained to `VexAffectedVulnAssessmentRelationship` classed relationships."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "amendedBy" ; + rdfs:comment "The `from` Element is amended by each `to` Element."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "ancestorOf" ; + rdfs:comment "The `from` Element is an ancestor of each `to` Element."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "availableFrom" ; + rdfs:comment "The `from` Element is available from the additional supplier described by each `to` Element."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "configures" ; + rdfs:comment "The `from` Element is a configuration applied to each `to` Element, during a LifecycleScopeType period."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "contains" ; + rdfs:comment "The `from` Element contains each `to` Element."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "coordinatedBy" ; + rdfs:comment "The `from` Vulnerability is coordinatedBy the `to` Agent(s) (vendor, researcher, or consumer agent)."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "copiedTo" ; + rdfs:comment "The `from` Element has been copied to each `to` Element."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "delegatedTo" ; + rdfs:comment "The `from` Agent is delegating an action to the Agent of the `to` Relationship (which must be of type invokedBy), during a LifecycleScopeType (e.g. the `to` invokedBy Relationship is being done on behalf of `from`)."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "dependsOn" ; + rdfs:comment "The `from` Element depends on each `to` Element, during a LifecycleScopeType period."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "descendantOf" ; + rdfs:comment "The `from` Element is a descendant of each `to` Element."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "describes" ; + rdfs:comment "The `from` Element describes each `to` Element. To denote the root(s) of a tree of elements in a collection, the rootElement property should be used."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "doesNotAffect" ; + rdfs:comment "The `from` Vulnerability has no impact on each `to` Element. The use of the `doesNotAffect` is constrained to `VexNotAffectedVulnAssessmentRelationship` classed relationships."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "expandsTo" ; + rdfs:comment "The `from` archive expands out as an artifact described by each `to` Element."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "exploitCreatedBy" ; + rdfs:comment "The `from` Vulnerability has had an exploit created against it by each `to` Agent."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "fixedBy" ; + rdfs:comment "Designates a `from` Vulnerability has been fixed by the `to` Agent(s)."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "fixedIn" ; + rdfs:comment "A `from` Vulnerability has been fixed in each `to` Element. The use of the `fixedIn` type is constrained to `VexFixedVulnAssessmentRelationship` classed relationships."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "foundBy" ; + rdfs:comment "Designates a `from` Vulnerability was originally discovered by the `to` Agent(s)."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "generates" ; + rdfs:comment "The `from` Element generates each `to` Element."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "hasAddedFile" ; + rdfs:comment "Every `to` Element is a file added to the `from` Element (`from` hasAddedFile `to`)."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "hasAssessmentFor" ; + rdfs:comment "Relates a `from` Vulnerability and each `to` Element with a security assessment. To be used with `VulnAssessmentRelationship` types."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "hasAssociatedVulnerability" ; + rdfs:comment "Used to associate a `from` Artifact with each `to` Vulnerability."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "hasConcludedLicense" ; + rdfs:comment "The `from` SoftwareArtifact is concluded by the SPDX data creator to be governed by each `to` license."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "hasDataFile" ; + rdfs:comment "The `from` Element treats each `to` Element as a data file. A data file is an artifact that stores data required or optional for the `from` Element's functionality. A data file can be a database file, an index file, a log file, an AI model file, a calibration data file, a temporary file, a backup file, and more. For AI training dataset, test dataset, test artifact, configuration data, build input data, and build output data, please consider using the more specific relationship types: `trainedOn`, `testedOn`, `hasTest`, `configures`, `hasInput`, and `hasOutput`, respectively. This relationship does not imply dependency."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "hasDeclaredLicense" ; + rdfs:comment "The `from` SoftwareArtifact was discovered to actually contain each `to` license, for example as detected by use of automated tooling."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "hasDeletedFile" ; + rdfs:comment "Every `to` Element is a file deleted from the `from` Element (`from` hasDeletedFile `to`)."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "hasDependencyManifest" ; + rdfs:comment "The `from` Element has manifest files that contain dependency information in each `to` Element."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "hasDistributionArtifact" ; + rdfs:comment "The `from` Element is distributed as an artifact in each `to` Element (e.g. an RPM or archive file)."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "hasDocumentation" ; + rdfs:comment "The `from` Element is documented by each `to` Element."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "hasDynamicLink" ; + rdfs:comment "The `from` Element dynamically links in each `to` Element, during a LifecycleScopeType period."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "hasEvidence" ; + rdfs:comment "Every `to` Element is considered as evidence for the `from` Element (`from` hasEvidence `to`)."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "hasExample" ; + rdfs:comment "Every `to` Element is an example for the `from` Element (`from` hasExample `to`)."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "hasHost" ; + rdfs:comment "The `from` Build was run on the `to` Element during a LifecycleScopeType period (e.g. the host that the build runs on)."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "hasInput" ; + rdfs:comment "The `from` Build has each `to` Element as an input, during a LifecycleScopeType period."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "hasMetadata" ; + rdfs:comment "Every `to` Element is metadata about the `from` Element (`from` hasMetadata `to`)."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "hasOptionalComponent" ; + rdfs:comment "Every `to` Element is an optional component of the `from` Element (`from` hasOptionalComponent `to`)."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "hasOptionalDependency" ; + rdfs:comment "The `from` Element optionally depends on each `to` Element, during a LifecycleScopeType period."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "hasOutput" ; + rdfs:comment "The `from` Build element generates each `to` Element as an output, during a LifecycleScopeType period."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "hasPrerequisite" ; + rdfs:comment "The `from` Element has a prerequisite on each `to` Element, during a LifecycleScopeType period."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "hasProvidedDependency" ; + rdfs:comment "The `from` Element has a dependency on each `to` Element, dependency is not in the distributed artifact, but assumed to be provided, during a LifecycleScopeType period."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "hasRequirement" ; + rdfs:comment "The `from` Element has a requirement on each `to` Element, during a LifecycleScopeType period."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "hasSpecification" ; + rdfs:comment "Every `to` Element is a specification for the `from` Element (`from` hasSpecification `to`), during a LifecycleScopeType period."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "hasStaticLink" ; + rdfs:comment "The `from` Element statically links in each `to` Element, during a LifecycleScopeType period."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "hasTest" ; + rdfs:comment "Every `to` Element is a test artifact for the `from` Element (`from` hasTest `to`), during a LifecycleScopeType period."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "hasTestCase" ; + rdfs:comment "Every `to` Element is a test case for the `from` Element (`from` hasTestCase `to`)."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "hasVariant" ; + rdfs:comment "Every `to` Element is a variant the `from` Element (`from` hasVariant `to`)."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "invokedBy" ; + rdfs:comment "The `from` Element was invoked by the `to` Agent, during a LifecycleScopeType period (for example, a Build element that describes a build step)."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "modifiedBy" ; + rdfs:comment "The `from` Element is modified by each `to` Element."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "other" ; + rdfs:comment "Every `to` Element is related to the `from` Element where the relationship type is not described by any of the SPDX relationship types (this relationship is directionless)."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "packagedBy" ; + rdfs:comment "Every `to` Element is a packaged instance of the `from` Element (`from` packagedBy `to`)."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "patchedBy" ; + rdfs:comment "Every `to` Element is a patch for the `from` Element (`from` patchedBy `to`)."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "publishedBy" ; + rdfs:comment "Designates a `from` Vulnerability was made available for public use or reference by each `to` Agent."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "reportedBy" ; + rdfs:comment "Designates a `from` Vulnerability was first reported to a project, vendor, or tracking database for formal identification by each `to` Agent."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "republishedBy" ; + rdfs:comment "Designates a `from` Vulnerability's details were tracked, aggregated, and/or enriched to improve context (i.e. NVD) by each `to` Agent."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "serializedInArtifact" ; + rdfs:comment "The `from` SpdxDocument can be found in a serialized form in each `to` Artifact."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "testedOn" ; + rdfs:comment "The `from` Element has been tested on the `to` Element(s)."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "trainedOn" ; + rdfs:comment "The `from` Element has been trained on the `to` Element(s)."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "underInvestigationFor" ; + rdfs:comment "The `from` Vulnerability impact is being investigated for each `to` Element. The use of the `underInvestigationFor` type is constrained to `VexUnderInvestigationVulnAssessmentRelationship` classed relationships."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "usesTool" ; + rdfs:comment "The `from` Element uses each `to` Element as a tool, during a LifecycleScopeType period."@en . + +ns1:SoftwareAgent a owl:Class ; + rdfs:comment "A software agent."@en ; + rdfs:subClassOf ns1:Agent ; + sh:nodeKind sh:IRI . + +ns1:SpdxDocument a owl:Class, + sh:NodeShape ; + rdfs:comment "A collection of SPDX Elements that could potentially be serialized."@en ; + rdfs:subClassOf ns1:ElementCollection ; + sh:nodeKind sh:IRI ; + sh:property [ sh:class ns1:NamespaceMap ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ns1:namespaceMap ], + [ sh:class ; + sh:maxCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns1:dataLicense ], + [ sh:class ns1:ExternalMap ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ns1:import ] . + + a owl:NamedIndividual, + ns1:SupportType ; + rdfs:label "deployed" ; + rdfs:comment "in addition to being supported by the supplier, the software is known to have been deployed and is in use. For a software as a service provider, this implies the software is now available as a service."@en . + + a owl:NamedIndividual, + ns1:SupportType ; + rdfs:label "development" ; + rdfs:comment "the artifact is in active development and is not considered ready for formal support from the supplier."@en . + + a owl:NamedIndividual, + ns1:SupportType ; + rdfs:label "endOfSupport" ; + rdfs:comment "there is a defined end of support for the artifact from the supplier. This may also be referred to as end of life. There is a validUntilDate that can be used to signal when support ends for the artifact."@en . + + a owl:NamedIndividual, + ns1:SupportType ; + rdfs:label "limitedSupport" ; + rdfs:comment "the artifact has been released, and there is limited support available from the supplier. There is a validUntilDate that can provide additional information about the duration of support."@en . + + a owl:NamedIndividual, + ns1:SupportType ; + rdfs:label "noAssertion" ; + rdfs:comment "no assertion about the type of support is made. This is considered the default if no other support type is used."@en . + + a owl:NamedIndividual, + ns1:SupportType ; + rdfs:label "noSupport" ; + rdfs:comment "there is no support for the artifact from the supplier, consumer assumes any support obligations."@en . + + a owl:NamedIndividual, + ns1:SupportType ; + rdfs:label "support" ; + rdfs:comment "the artifact has been released, and is supported from the supplier. There is a validUntilDate that can provide additional information about the duration of support."@en . + +ns1:annotationType a owl:ObjectProperty ; + rdfs:comment "Describes the type of annotation."@en ; + rdfs:range ns1:AnnotationType . + +ns1:beginIntegerRange a owl:DatatypeProperty ; + rdfs:comment "Defines the beginning of a range."@en ; + rdfs:range xsd:positiveInteger . + +ns1:builtTime a owl:DatatypeProperty ; + rdfs:comment "Specifies the time an artifact was built."@en ; + rdfs:range xsd:dateTimeStamp . + +ns1:completeness a owl:ObjectProperty ; + rdfs:comment "Provides information about the completeness of relationships."@en ; + rdfs:range ns1:RelationshipCompleteness . + +ns1:context a owl:DatatypeProperty ; + rdfs:comment """Gives information about the circumstances or unifying properties +that Elements of the bundle have been assembled under."""@en ; + rdfs:range xsd:string . + +ns1:created a owl:DatatypeProperty ; + rdfs:comment "Identifies when the Element was originally created."@en ; + rdfs:range xsd:dateTimeStamp . + +ns1:createdBy a owl:ObjectProperty ; + rdfs:comment "Identifies who or what created the Element."@en ; + rdfs:range ns1:Agent . + +ns1:createdUsing a owl:ObjectProperty ; + rdfs:comment "Identifies the tooling that was used during the creation of the Element."@en ; + rdfs:range ns1:Tool . + +ns1:creationInfo a owl:ObjectProperty ; + rdfs:comment "Provides information about the creation of the Element."@en ; + rdfs:range ns1:CreationInfo . + +ns1:dataLicense a owl:ObjectProperty ; + rdfs:comment """Provides the license under which the SPDX documentation of the Element can be +used."""@en ; + rdfs:range . + +ns1:definingArtifact a owl:ObjectProperty ; + rdfs:comment """Artifact representing a serialization instance of SPDX data containing the +definition of a particular Element."""@en ; + rdfs:range ns1:Artifact . + +ns1:description a owl:DatatypeProperty ; + rdfs:comment "Provides a detailed description of the Element."@en ; + rdfs:range xsd:string . + +ns1:element a owl:ObjectProperty ; + rdfs:comment "Refers to one or more Elements that are part of an ElementCollection."@en ; + rdfs:range ns1:Element . + +ns1:endIntegerRange a owl:DatatypeProperty ; + rdfs:comment "Defines the end of a range."@en ; + rdfs:range xsd:positiveInteger . + +ns1:endTime a owl:DatatypeProperty ; + rdfs:comment "Specifies the time from which an element is no longer applicable / valid."@en ; + rdfs:range xsd:dateTimeStamp . + +ns1:externalIdentifier a owl:ObjectProperty ; + rdfs:comment """Provides a reference to a resource outside the scope of SPDX-3.0 content +that uniquely identifies an Element."""@en ; + rdfs:range ns1:ExternalIdentifier . + +ns1:externalIdentifierType a owl:ObjectProperty ; + rdfs:comment "Specifies the type of the external identifier."@en ; + rdfs:range ns1:ExternalIdentifierType . + +ns1:externalRef a owl:ObjectProperty ; + rdfs:comment """Points to a resource outside the scope of the SPDX-3.0 content +that provides additional characteristics of an Element."""@en ; + rdfs:range ns1:ExternalRef . + +ns1:externalRefType a owl:ObjectProperty ; + rdfs:comment "Specifies the type of the external reference."@en ; + rdfs:range ns1:ExternalRefType . + +ns1:externalSpdxId a owl:DatatypeProperty ; + rdfs:comment """Identifies an external Element used within an SpdxDocument but defined +external to that SpdxDocument."""@en ; + rdfs:range xsd:anyURI . + +ns1:from a owl:ObjectProperty ; + rdfs:comment "References the Element on the left-hand side of a relationship."@en ; + rdfs:range ns1:Element . + +ns1:identifier a owl:DatatypeProperty ; + rdfs:comment "Uniquely identifies an external element."@en ; + rdfs:range xsd:string . + +ns1:identifierLocator a owl:DatatypeProperty ; + rdfs:comment "Provides the location for more information regarding an external identifier."@en ; + rdfs:range xsd:anyURI . + +ns1:import a owl:ObjectProperty ; + rdfs:comment "Provides an ExternalMap of Element identifiers."@en ; + rdfs:range ns1:ExternalMap . + +ns1:issuingAuthority a owl:DatatypeProperty ; + rdfs:comment "An entity that is authorized to issue identification credentials."@en ; + rdfs:range xsd:string . + +ns1:key a owl:DatatypeProperty ; + rdfs:comment "A key used in a generic key-value pair."@en ; + rdfs:range xsd:string . + +ns1:locationHint a owl:DatatypeProperty ; + rdfs:comment "Provides an indication of where to retrieve an external Element."@en ; + rdfs:range xsd:anyURI . + +ns1:locator a owl:DatatypeProperty ; + rdfs:comment "Provides the location of an external reference."@en ; + rdfs:range xsd:string . + +ns1:name a owl:DatatypeProperty ; + rdfs:comment "Identifies the name of an Element as designated by the creator."@en ; + rdfs:range xsd:string . + +ns1:namespace a owl:DatatypeProperty ; + rdfs:comment """Provides an unambiguous mechanism for conveying a URI fragment portion of an +Element ID."""@en ; + rdfs:range xsd:anyURI . + +ns1:namespaceMap a owl:ObjectProperty ; + rdfs:comment "Provides a NamespaceMap of prefixes and associated namespace partial URIs applicable to an SpdxDocument and independent of any specific serialization format or instance."@en ; + rdfs:range ns1:NamespaceMap . + +ns1:originatedBy a owl:ObjectProperty ; + rdfs:comment "Identifies from where or whom the Element originally came."@en ; + rdfs:range ns1:Agent . + +ns1:packageVerificationCodeExcludedFile a owl:DatatypeProperty ; + rdfs:comment """The relative file name of a file to be excluded from the +`PackageVerificationCode`."""@en ; + rdfs:range xsd:string . + +ns1:prefix a owl:DatatypeProperty ; + rdfs:comment "A substitute for a URI."@en ; + rdfs:range xsd:string . + +ns1:profileConformance a owl:ObjectProperty ; + rdfs:comment """Describes one a profile which the creator of this ElementCollection intends to +conform to."""@en ; + rdfs:range ns1:ProfileIdentifierType . + +ns1:relationshipType a owl:ObjectProperty ; + rdfs:comment "Information about the relationship between two Elements."@en ; + rdfs:range ns1:RelationshipType . + +ns1:releaseTime a owl:DatatypeProperty ; + rdfs:comment "Specifies the time an artifact was released."@en ; + rdfs:range xsd:dateTimeStamp . + +ns1:rootElement a owl:ObjectProperty ; + rdfs:comment "This property is used to denote the root Element(s) of a tree of elements contained in a BOM."@en ; + rdfs:range ns1:Element . + +ns1:scope a owl:ObjectProperty ; + rdfs:comment "Capture the scope of information about a specific relationship between elements."@en ; + rdfs:range ns1:LifecycleScopeType . + +ns1:specVersion a owl:DatatypeProperty ; + rdfs:comment """Provides a reference number that can be used to understand how to parse and +interpret an Element."""@en ; + rdfs:range xsd:string . + +ns1:standardName a owl:DatatypeProperty ; + rdfs:comment "The name of a relevant standard that may apply to an artifact."@en ; + rdfs:range xsd:string . + +ns1:startTime a owl:DatatypeProperty ; + rdfs:comment "Specifies the time from which an element is applicable / valid."@en ; + rdfs:range xsd:dateTimeStamp . + +ns1:statement a owl:DatatypeProperty ; + rdfs:comment "Commentary on an assertion that an annotator has made."@en ; + rdfs:range xsd:string . + +ns1:subject a owl:ObjectProperty ; + rdfs:comment "An Element an annotator has made an assertion about."@en ; + rdfs:range ns1:Element . + +ns1:summary a owl:DatatypeProperty ; + rdfs:comment "A short description of an Element."@en ; + rdfs:range xsd:string . + +ns1:supportLevel a owl:ObjectProperty ; + rdfs:comment "Specifies the level of support associated with an artifact."@en ; + rdfs:range ns1:SupportType . + +ns1:to a owl:ObjectProperty ; + rdfs:comment "References an Element on the right-hand side of a relationship."@en ; + rdfs:range ns1:Element . + +ns1:validUntilTime a owl:DatatypeProperty ; + rdfs:comment """Specifies until when the artifact can be used before its usage needs to be +reassessed."""@en ; + rdfs:range xsd:dateTimeStamp . + +ns1:value a owl:DatatypeProperty ; + rdfs:comment "A value used in a generic key-value pair."@en ; + rdfs:range xsd:string . + + a owl:NamedIndividual, + ns5:ConfidentialityLevelType ; + rdfs:label "amber" ; + rdfs:comment "Data points in the dataset can be shared only with specific organizations and their clients on a need to know basis."@en . + + a owl:NamedIndividual, + ns5:ConfidentialityLevelType ; + rdfs:label "clear" ; + rdfs:comment "Dataset may be distributed freely, without restriction."@en . + + a owl:NamedIndividual, + ns5:ConfidentialityLevelType ; + rdfs:label "green" ; + rdfs:comment "Dataset can be shared within a community of peers and partners."@en . + + a owl:NamedIndividual, + ns5:ConfidentialityLevelType ; + rdfs:label "red" ; + rdfs:comment "Data points in the dataset are highly confidential and can only be shared with named recipients."@en . + + a owl:NamedIndividual, + ns5:DatasetAvailabilityType ; + rdfs:label "clickthrough" ; + rdfs:comment "the dataset is not publicly available and can only be accessed after affirmatively accepting terms on a clickthrough webpage."@en . + + a owl:NamedIndividual, + ns5:DatasetAvailabilityType ; + rdfs:label "directDownload" ; + rdfs:comment "the dataset is publicly available and can be downloaded directly."@en . + + a owl:NamedIndividual, + ns5:DatasetAvailabilityType ; + rdfs:label "query" ; + rdfs:comment "the dataset is publicly available, but not all at once, and can only be accessed through queries which return parts of the dataset."@en . + + a owl:NamedIndividual, + ns5:DatasetAvailabilityType ; + rdfs:label "registration" ; + rdfs:comment "the dataset is not publicly available and an email registration is required before accessing the dataset, although without an affirmative acceptance of terms."@en . + + a owl:NamedIndividual, + ns5:DatasetAvailabilityType ; + rdfs:label "scrapingScript" ; + rdfs:comment "the dataset provider is not making available the underlying data and the dataset must be reassembled, typically using the provided script for scraping the data."@en . + +ns5:DatasetPackage a owl:Class, + sh:NodeShape ; + rdfs:comment "Specifies a data package and its associated information."@en ; + rdfs:subClassOf ns3:Package ; + sh:nodeKind sh:IRI ; + sh:property [ sh:datatype xsd:nonNegativeInteger ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns5:datasetSize ], + [ sh:class ns5:DatasetType ; + sh:in ( ) ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns5:datasetType ], + [ sh:datatype xsd:string ; + sh:nodeKind sh:Literal ; + sh:path ns5:anonymizationMethodUsed ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns5:datasetUpdateMechanism ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns5:dataCollectionProcess ], + [ sh:datatype xsd:string ; + sh:nodeKind sh:Literal ; + sh:path ns5:knownBias ], + [ sh:class ns1:DictionaryEntry ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ns5:sensor ], + [ sh:datatype xsd:string ; + sh:nodeKind sh:Literal ; + sh:path ns5:dataPreprocessing ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns5:intendedUse ], + [ sh:class ns5:ConfidentialityLevelType ; + sh:in ( ) ; + sh:maxCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns5:confidentialityLevel ], + [ sh:class ns5:DatasetAvailabilityType ; + sh:in ( ) ; + sh:maxCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns5:datasetAvailability ], + [ sh:class ns1:PresenceType ; + sh:in ( ) ; + sh:maxCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns5:hasSensitivePersonalInformation ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns5:datasetNoise ] . + + a owl:NamedIndividual, + ns5:DatasetType ; + rdfs:label "audio" ; + rdfs:comment "data is audio based, such as a collection of music from the 80s."@en . + + a owl:NamedIndividual, + ns5:DatasetType ; + rdfs:label "categorical" ; + rdfs:comment "data that is classified into a discrete number of categories, such as the eye color of a population of people."@en . + + a owl:NamedIndividual, + ns5:DatasetType ; + rdfs:label "graph" ; + rdfs:comment "data is in the form of a graph where entries are somehow related to each other through edges, such a social network of friends."@en . + + a owl:NamedIndividual, + ns5:DatasetType ; + rdfs:label "image" ; + rdfs:comment "data is a collection of images such as pictures of animals."@en . + + a owl:NamedIndividual, + ns5:DatasetType ; + rdfs:label "noAssertion" ; + rdfs:comment "data type is not known."@en . + + a owl:NamedIndividual, + ns5:DatasetType ; + rdfs:label "numeric" ; + rdfs:comment "data consists only of numeric entries."@en . + + a owl:NamedIndividual, + ns5:DatasetType ; + rdfs:label "other" ; + rdfs:comment "data is of a type not included in this list."@en . + + a owl:NamedIndividual, + ns5:DatasetType ; + rdfs:label "sensor" ; + rdfs:comment "data is recorded from a physical sensor, such as a thermometer reading or biometric device."@en . + + a owl:NamedIndividual, + ns5:DatasetType ; + rdfs:label "structured" ; + rdfs:comment "data is stored in tabular format or retrieved from a relational database."@en . + + a owl:NamedIndividual, + ns5:DatasetType ; + rdfs:label "syntactic" ; + rdfs:comment "data describes the syntax or semantics of a language or text, such as a parse tree used for natural language processing."@en . + + a owl:NamedIndividual, + ns5:DatasetType ; + rdfs:label "text" ; + rdfs:comment "data consists of unstructured text, such as a book, Wikipedia article (without images), or transcript."@en . + + a owl:NamedIndividual, + ns5:DatasetType ; + rdfs:label "timeseries" ; + rdfs:comment "data is recorded in an ordered sequence of timestamped entries, such as the price of a stock over the course of a day."@en . + + a owl:NamedIndividual, + ns5:DatasetType ; + rdfs:label "timestamp" ; + rdfs:comment "data is recorded with a timestamp for each entry, but not necessarily ordered or at specific intervals, such as when a taxi ride starts and ends."@en . + + a owl:NamedIndividual, + ns5:DatasetType ; + rdfs:label "video" ; + rdfs:comment "data is video based, such as a collection of movie clips featuring Tom Hanks."@en . + +ns5:anonymizationMethodUsed a owl:DatatypeProperty ; + rdfs:comment "Describes the anonymization methods used."@en ; + rdfs:range xsd:string . + +ns5:confidentialityLevel a owl:ObjectProperty ; + rdfs:comment "Describes the confidentiality level of the data points contained in the dataset."@en ; + rdfs:range ns5:ConfidentialityLevelType . + +ns5:dataCollectionProcess a owl:DatatypeProperty ; + rdfs:comment "Describes how the dataset was collected."@en ; + rdfs:range xsd:string . + +ns5:dataPreprocessing a owl:DatatypeProperty ; + rdfs:comment "Describes the preprocessing steps that were applied to the raw data to create the given dataset."@en ; + rdfs:range xsd:string . + +ns5:datasetAvailability a owl:ObjectProperty ; + rdfs:comment "The field describes the availability of a dataset."@en ; + rdfs:range ns5:DatasetAvailabilityType . + +ns5:datasetNoise a owl:DatatypeProperty ; + rdfs:comment "Describes potentially noisy elements of the dataset."@en ; + rdfs:range xsd:string . + +ns5:datasetSize a owl:DatatypeProperty ; + rdfs:comment "Captures the size of the dataset."@en ; + rdfs:range xsd:nonNegativeInteger . + +ns5:datasetType a owl:ObjectProperty ; + rdfs:comment "Describes the type of the given dataset."@en ; + rdfs:range ns5:DatasetType . + +ns5:datasetUpdateMechanism a owl:DatatypeProperty ; + rdfs:comment "Describes a mechanism to update the dataset."@en ; + rdfs:range xsd:string . + +ns5:hasSensitivePersonalInformation a owl:ObjectProperty ; + rdfs:comment "Describes if any sensitive personal information is present in the dataset."@en ; + rdfs:range ns1:PresenceType . + +ns5:intendedUse a owl:DatatypeProperty ; + rdfs:comment "Describes what the given dataset should be used for."@en ; + rdfs:range xsd:string . + +ns5:knownBias a owl:DatatypeProperty ; + rdfs:comment "Records the biases that the dataset is known to encompass."@en ; + rdfs:range xsd:string . + +ns5:sensor a owl:ObjectProperty ; + rdfs:comment "Describes a sensor used for collecting the data."@en ; + rdfs:range ns1:DictionaryEntry . + +ns2:ConjunctiveLicenseSet a owl:Class, + sh:NodeShape ; + rdfs:comment """Portion of an AnyLicenseInfo representing a set of licensing information +where all elements apply."""@en ; + rdfs:subClassOf ; + sh:nodeKind sh:IRI ; + sh:property [ sh:class ; + sh:minCount 2 ; + sh:nodeKind sh:IRI ; + sh:path ns2:member ] . + +ns2:CustomLicense a owl:Class ; + rdfs:comment "A license that is not listed on the SPDX License List."@en ; + rdfs:subClassOf ns2:License ; + sh:nodeKind sh:IRI . + +ns2:CustomLicenseAddition a owl:Class ; + rdfs:comment "A license addition that is not listed on the SPDX Exceptions List."@en ; + rdfs:subClassOf ns2:LicenseAddition ; + sh:nodeKind sh:IRI . + +ns2:DisjunctiveLicenseSet a owl:Class, + sh:NodeShape ; + rdfs:comment """Portion of an AnyLicenseInfo representing a set of licensing information where +only one of the elements applies."""@en ; + rdfs:subClassOf ; + sh:nodeKind sh:IRI ; + sh:property [ sh:class ; + sh:minCount 2 ; + sh:nodeKind sh:IRI ; + sh:path ns2:member ] . + +ns2:ListedLicense a owl:Class, + sh:NodeShape ; + rdfs:comment "A license that is listed on the SPDX License List."@en ; + rdfs:subClassOf ns2:License ; + sh:nodeKind sh:IRI ; + sh:property [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns2:deprecatedVersion ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns2:listVersionAdded ] . + +ns2:ListedLicenseException a owl:Class, + sh:NodeShape ; + rdfs:comment "A license exception that is listed on the SPDX Exceptions list."@en ; + rdfs:subClassOf ns2:LicenseAddition ; + sh:nodeKind sh:IRI ; + sh:property [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns2:listVersionAdded ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns2:deprecatedVersion ] . + +ns2:OrLaterOperator a owl:Class, + sh:NodeShape ; + rdfs:comment """Portion of an AnyLicenseInfo representing this version, or any later version, +of the indicated License."""@en ; + rdfs:subClassOf ns2:ExtendableLicense ; + sh:nodeKind sh:IRI ; + sh:property [ sh:class ns2:License ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns2:subjectLicense ] . + +ns2:WithAdditionOperator a owl:Class, + sh:NodeShape ; + rdfs:comment """Portion of an AnyLicenseInfo representing a License which has additional +text applied to it."""@en ; + rdfs:subClassOf ; + sh:nodeKind sh:IRI ; + sh:property [ sh:class ns2:ExtendableLicense ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns2:subjectExtendableLicense ], + [ sh:class ns2:LicenseAddition ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns2:subjectAddition ] . + +ns2:additionText a owl:DatatypeProperty ; + rdfs:comment "Identifies the full text of a LicenseAddition."@en ; + rdfs:range xsd:string . + +ns2:isDeprecatedAdditionId a owl:DatatypeProperty ; + rdfs:comment "Specifies whether an additional text identifier has been marked as deprecated."@en ; + rdfs:range xsd:boolean . + +ns2:isDeprecatedLicenseId a owl:DatatypeProperty ; + rdfs:comment """Specifies whether a license or additional text identifier has been marked as +deprecated."""@en ; + rdfs:range xsd:boolean . + +ns2:isFsfLibre a owl:DatatypeProperty ; + rdfs:comment """Specifies whether the License is listed as free by the +Free Software Foundation (FSF)."""@en ; + rdfs:range xsd:boolean . + +ns2:isOsiApproved a owl:DatatypeProperty ; + rdfs:comment """Specifies whether the License is listed as approved by the +Open Source Initiative (OSI)."""@en ; + rdfs:range xsd:boolean . + +ns2:standardAdditionTemplate a owl:DatatypeProperty ; + rdfs:comment "Identifies the full text of a LicenseAddition, in SPDX templating format."@en ; + rdfs:range xsd:string . + +ns2:standardLicenseHeader a owl:DatatypeProperty ; + rdfs:comment """Provides a License author's preferred text to indicate that a file is covered +by the License."""@en ; + rdfs:range xsd:string . + +ns2:standardLicenseTemplate a owl:DatatypeProperty ; + rdfs:comment "Identifies the full text of a License, in SPDX templating format."@en ; + rdfs:range xsd:string . + +ns2:subjectAddition a owl:ObjectProperty ; + rdfs:comment "A LicenseAddition participating in a 'with addition' model."@en ; + rdfs:range ns2:LicenseAddition . + +ns2:subjectExtendableLicense a owl:ObjectProperty ; + rdfs:comment "A License participating in a 'with addition' model."@en ; + rdfs:range ns2:ExtendableLicense . + +ns2:subjectLicense a owl:ObjectProperty ; + rdfs:comment "A License participating in an 'or later' model."@en ; + rdfs:range ns2:License . + + a owl:DatatypeProperty ; + rdfs:comment "A name used in a CdxPropertyEntry name-value pair."@en ; + rdfs:range xsd:string . + + a owl:DatatypeProperty ; + rdfs:comment "A value used in a CdxPropertyEntry name-value pair."@en ; + rdfs:range xsd:string . + + a owl:ObjectProperty ; + rdfs:comment "Provides a map of a property names to a values."@en ; + rdfs:range . + +ns6:CvssV2VulnAssessmentRelationship a owl:Class, + sh:NodeShape ; + rdfs:comment "Provides a CVSS version 2.0 assessment for a vulnerability."@en ; + rdfs:subClassOf ns6:VulnAssessmentRelationship ; + sh:nodeKind sh:IRI ; + sh:property [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns6:vectorString ], + [ sh:datatype xsd:decimal ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns6:score ] . + +ns6:CvssV3VulnAssessmentRelationship a owl:Class, + sh:NodeShape ; + rdfs:comment "Provides a CVSS version 3 assessment for a vulnerability."@en ; + rdfs:subClassOf ns6:VulnAssessmentRelationship ; + sh:nodeKind sh:IRI ; + sh:property [ sh:class ns6:CvssSeverityType ; + sh:in ( ) ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns6:severity ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns6:vectorString ], + [ sh:datatype xsd:decimal ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns6:score ] . + +ns6:CvssV4VulnAssessmentRelationship a owl:Class, + sh:NodeShape ; + rdfs:comment "Provides a CVSS version 4 assessment for a vulnerability."@en ; + rdfs:subClassOf ns6:VulnAssessmentRelationship ; + sh:nodeKind sh:IRI ; + sh:property [ sh:class ns6:CvssSeverityType ; + sh:in ( ) ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns6:severity ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns6:vectorString ], + [ sh:datatype xsd:decimal ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns6:score ] . + +ns6:EpssVulnAssessmentRelationship a owl:Class, + sh:NodeShape ; + rdfs:comment "Provides an EPSS assessment for a vulnerability."@en ; + rdfs:subClassOf ns6:VulnAssessmentRelationship ; + sh:nodeKind sh:IRI ; + sh:property [ sh:datatype xsd:decimal ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns6:percentile ], + [ sh:datatype xsd:decimal ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns6:probability ] . + + a owl:NamedIndividual, + ns6:ExploitCatalogType ; + rdfs:label "kev" ; + rdfs:comment "CISA's Known Exploited Vulnerability (KEV) Catalog"@en . + + a owl:NamedIndividual, + ns6:ExploitCatalogType ; + rdfs:label "other" ; + rdfs:comment "Other exploit catalogs"@en . + +ns6:ExploitCatalogVulnAssessmentRelationship a owl:Class, + sh:NodeShape ; + rdfs:comment "Provides an exploit assessment of a vulnerability."@en ; + rdfs:subClassOf ns6:VulnAssessmentRelationship ; + sh:nodeKind sh:IRI ; + sh:property [ sh:datatype xsd:boolean ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns6:exploited ], + [ sh:datatype xsd:anyURI ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns6:locator ], + [ sh:class ns6:ExploitCatalogType ; + sh:in ( ) ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns6:catalogType ] . + + a owl:NamedIndividual, + ns6:SsvcDecisionType ; + rdfs:label "act" ; + rdfs:comment "The vulnerability requires attention from the organization's internal, supervisory-level and leadership-level individuals. Necessary actions include requesting assistance or information about the vulnerability, as well as publishing a notification either internally and/or externally. Typically, internal groups would meet to determine the overall response and then execute agreed upon actions. CISA recommends remediating Act vulnerabilities as soon as possible."@en . + + a owl:NamedIndividual, + ns6:SsvcDecisionType ; + rdfs:label "attend" ; + rdfs:comment "The vulnerability requires attention from the organization's internal, supervisory-level individuals. Necessary actions include requesting assistance or information about the vulnerability, and may involve publishing a notification either internally and/or externally. CISA recommends remediating Attend vulnerabilities sooner than standard update timelines."@en . + + a owl:NamedIndividual, + ns6:SsvcDecisionType ; + rdfs:label "track" ; + rdfs:comment "The vulnerability does not require action at this time. The organization would continue to track the vulnerability and reassess it if new information becomes available. CISA recommends remediating Track vulnerabilities within standard update timelines."@en . + + a owl:NamedIndividual, + ns6:SsvcDecisionType ; + rdfs:label "trackStar" ; + rdfs:comment "(\"Track\\*\" in the SSVC spec) The vulnerability contains specific characteristics that may require closer monitoring for changes. CISA recommends remediating Track\\* vulnerabilities within standard update timelines."@en . + +ns6:SsvcVulnAssessmentRelationship a owl:Class, + sh:NodeShape ; + rdfs:comment "Provides an SSVC assessment for a vulnerability."@en ; + rdfs:subClassOf ns6:VulnAssessmentRelationship ; + sh:nodeKind sh:IRI ; + sh:property [ sh:class ns6:SsvcDecisionType ; + sh:in ( ) ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns6:decisionType ] . + +ns6:VexAffectedVulnAssessmentRelationship a owl:Class, + sh:NodeShape ; + rdfs:comment """Connects a vulnerability and an element designating the element as a product +affected by the vulnerability."""@en ; + rdfs:subClassOf ns6:VexVulnAssessmentRelationship ; + sh:nodeKind sh:IRI ; + sh:property [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns6:actionStatement ], + [ sh:datatype xsd:dateTimeStamp ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns6:actionStatementTime ; + sh:pattern "^\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\dZ$" ] . + +ns6:VexFixedVulnAssessmentRelationship a owl:Class ; + rdfs:comment """Links a vulnerability and elements representing products (in the VEX sense) where +a fix has been applied and are no longer affected."""@en ; + rdfs:subClassOf ns6:VexVulnAssessmentRelationship ; + sh:nodeKind sh:IRI . + + a owl:NamedIndividual, + ns6:VexJustificationType ; + rdfs:label "componentNotPresent" ; + rdfs:comment "The software is not affected because the vulnerable component is not in the product."@en . + + a owl:NamedIndividual, + ns6:VexJustificationType ; + rdfs:label "inlineMitigationsAlreadyExist" ; + rdfs:comment "Built-in inline controls or mitigations prevent an adversary from leveraging the vulnerability."@en . + + a owl:NamedIndividual, + ns6:VexJustificationType ; + rdfs:label "vulnerableCodeCannotBeControlledByAdversary" ; + rdfs:comment "The vulnerable component is present, and the component contains the vulnerable code. However, vulnerable code is used in such a way that an attacker cannot mount any anticipated attack."@en . + + a owl:NamedIndividual, + ns6:VexJustificationType ; + rdfs:label "vulnerableCodeNotInExecutePath" ; + rdfs:comment "The affected code is not reachable through the execution of the code, including non-anticipated states of the product."@en . + + a owl:NamedIndividual, + ns6:VexJustificationType ; + rdfs:label "vulnerableCodeNotPresent" ; + rdfs:comment "The product is not affected because the code underlying the vulnerability is not present in the product."@en . + +ns6:VexNotAffectedVulnAssessmentRelationship a owl:Class, + sh:NodeShape ; + rdfs:comment """Links a vulnerability and one or more elements designating the latter as products +not affected by the vulnerability."""@en ; + rdfs:subClassOf ns6:VexVulnAssessmentRelationship ; + sh:nodeKind sh:IRI ; + sh:property [ sh:datatype xsd:dateTimeStamp ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns6:impactStatementTime ; + sh:pattern "^\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\dZ$" ], + [ sh:class ns6:VexJustificationType ; + sh:in ( ) ; + sh:maxCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns6:justificationType ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns6:impactStatement ] . + +ns6:VexUnderInvestigationVulnAssessmentRelationship a owl:Class ; + rdfs:comment """Designates elements as products where the impact of a vulnerability is being +investigated."""@en ; + rdfs:subClassOf ns6:VexVulnAssessmentRelationship ; + sh:nodeKind sh:IRI . + +ns6:Vulnerability a owl:Class, + sh:NodeShape ; + rdfs:comment "Specifies a vulnerability and its associated information."@en ; + rdfs:subClassOf ns1:Artifact ; + sh:nodeKind sh:IRI ; + sh:property [ sh:datatype xsd:dateTimeStamp ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns6:withdrawnTime ; + sh:pattern "^\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\dZ$" ], + [ sh:datatype xsd:dateTimeStamp ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns6:modifiedTime ; + sh:pattern "^\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\dZ$" ], + [ sh:datatype xsd:dateTimeStamp ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns6:publishedTime ; + sh:pattern "^\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\dZ$" ] . + +ns6:actionStatement a owl:DatatypeProperty ; + rdfs:comment """Provides advise on how to mitigate or remediate a vulnerability when a VEX product +is affected by it."""@en ; + rdfs:range xsd:string . + +ns6:actionStatementTime a owl:DatatypeProperty ; + rdfs:comment """Records the time when a recommended action was communicated in a VEX statement +to mitigate a vulnerability."""@en ; + rdfs:range xsd:dateTimeStamp . + +ns6:assessedElement a owl:ObjectProperty ; + rdfs:comment """Specifies an Element contained in a piece of software where a vulnerability was +found."""@en ; + rdfs:range ns3:SoftwareArtifact . + +ns6:catalogType a owl:ObjectProperty ; + rdfs:comment "Specifies the exploit catalog type."@en ; + rdfs:range ns6:ExploitCatalogType . + +ns6:decisionType a owl:ObjectProperty ; + rdfs:comment """Provide the enumeration of possible decisions in the +[Stakeholder-Specific Vulnerability Categorization (SSVC) decision tree](https://www.cisa.gov/stakeholder-specific-vulnerability-categorization-ssvc)."""@en ; + rdfs:range ns6:SsvcDecisionType . + +ns6:exploited a owl:DatatypeProperty ; + rdfs:comment "Describe that a CVE is known to have an exploit because it's been listed in an exploit catalog."@en ; + rdfs:range xsd:boolean . + +ns6:impactStatement a owl:DatatypeProperty ; + rdfs:comment """Explains why a VEX product is not affected by a vulnerability. It is an +alternative in VexNotAffectedVulnAssessmentRelationship to the machine-readable +justification label."""@en ; + rdfs:range xsd:string . + +ns6:impactStatementTime a owl:DatatypeProperty ; + rdfs:comment "Timestamp of impact statement."@en ; + rdfs:range xsd:dateTimeStamp . + +ns6:justificationType a owl:ObjectProperty ; + rdfs:comment """Impact justification label to be used when linking a vulnerability to an element +representing a VEX product with a VexNotAffectedVulnAssessmentRelationship +relationship."""@en ; + rdfs:range ns6:VexJustificationType . + +ns6:locator a owl:DatatypeProperty ; + rdfs:comment "Provides the location of an exploit catalog."@en ; + rdfs:range xsd:anyURI . + +ns6:percentile a owl:DatatypeProperty ; + rdfs:comment "The percentile of the current probability score."@en ; + rdfs:range xsd:decimal . + +ns6:probability a owl:DatatypeProperty ; + rdfs:comment "A probability score between 0 and 1 of a vulnerability being exploited."@en ; + rdfs:range xsd:decimal . + +ns6:statusNotes a owl:DatatypeProperty ; + rdfs:comment "Conveys information about how VEX status was determined."@en ; + rdfs:range xsd:string . + +ns6:vexVersion a owl:DatatypeProperty ; + rdfs:comment "Specifies the version of a VEX statement."@en ; + rdfs:range xsd:string . + + a owl:Class, + sh:NodeShape ; + rdfs:comment "An SPDX Element containing an SPDX license expression string."@en ; + rdfs:subClassOf ; + sh:nodeKind sh:IRI ; + sh:property [ sh:class ns1:DictionaryEntry ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ; + sh:pattern "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$" ] . + + a owl:Class, + sh:NodeShape ; + rdfs:comment "A license or addition that is not listed on the SPDX License List."@en ; + rdfs:subClassOf ns1:Element ; + sh:nodeKind sh:IRI ; + sh:property [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ] . + + a owl:ObjectProperty ; + rdfs:comment """Maps a LicenseRef or AdditionRef string for a Custom License or a Custom +License Addition to its URI ID."""@en ; + rdfs:range ns1:DictionaryEntry . + + a owl:DatatypeProperty ; + rdfs:comment "A string in the license expression format."@en ; + rdfs:range xsd:string . + + a owl:DatatypeProperty ; + rdfs:comment "The version of the SPDX License List used in the license expression."@en ; + rdfs:range xsd:string . + + a owl:NamedIndividual, + ns3:ContentIdentifierType ; + rdfs:label "gitoid" ; + rdfs:comment "[Gitoid](https://www.iana.org/assignments/uri-schemes/prov/gitoid), stands for [Git Object ID](https://git-scm.com/book/en/v2/Git-Internals-Git-Objects). A gitoid of type blob is a unique hash of a binary artifact. A gitoid may represent either an [Artifact Identifier](https://github.com/omnibor/spec/blob/eb1ee5c961c16215eb8709b2975d193a2007a35d/spec/SPEC.md#artifact-identifier-types) for the software artifact or an [Input Manifest Identifier](https://github.com/omnibor/spec/blob/eb1ee5c961c16215eb8709b2975d193a2007a35d/spec/SPEC.md#input-manifest-identifier) for the software artifact's associated [Artifact Input Manifest](https://github.com/omnibor/spec/blob/eb1ee5c961c16215eb8709b2975d193a2007a35d/spec/SPEC.md#artifact-input-manifest); this ambiguity exists because the Artifact Input Manifest is itself an artifact, and the gitoid of that artifact is its valid identifier. Gitoids calculated on software artifacts (Snippet, File, or Package Elements) should be recorded in the SPDX 3.0 SoftwareArtifact's contentIdentifier property. Gitoids calculated on the Artifact Input Manifest (Input Manifest Identifier) should be recorded in the SPDX 3.0 Element's externalIdentifier property. See [OmniBOR Specification](https://github.com/omnibor/spec/), a minimalistic specification for describing software [Artifact Dependency Graphs](https://github.com/omnibor/spec/blob/eb1ee5c961c16215eb8709b2975d193a2007a35d/spec/SPEC.md#artifact-dependency-graph-adg)."@en . + + a owl:NamedIndividual, + ns3:ContentIdentifierType ; + rdfs:label "swhid" ; + rdfs:comment "SoftWare Hash IDentifier, a persistent intrinsic identifier for digital artifacts, such as files, trees (also known as directories or folders), commits, and other objects typically found in version control systems. The format of the identifiers is defined in the [SWHID specification](https://www.swhid.org/specification/v1.1/4.Syntax) (ISO/IEC DIS 18670). They typically look like `swh:1:cnt:94a9ed024d3859793618152ea559a168bbcbb5e2`."@en . + + a owl:NamedIndividual, + ns3:FileKindType ; + rdfs:label "directory" ; + rdfs:comment "The file represents a directory and all content stored in that directory."@en . + + a owl:NamedIndividual, + ns3:FileKindType ; + rdfs:label "file" ; + rdfs:comment "The file represents a single file (default)."@en . + +ns3:Sbom a owl:Class, + sh:NodeShape ; + rdfs:comment "A collection of SPDX Elements describing a single package."@en ; + rdfs:subClassOf ns1:Bom ; + sh:nodeKind sh:IRI ; + sh:property [ sh:class ns3:SbomType ; + sh:in ( ) ; + sh:nodeKind sh:IRI ; + sh:path ns3:sbomType ] . + + a owl:NamedIndividual, + ns3:SbomType ; + rdfs:label "analyzed" ; + rdfs:comment "SBOM generated through analysis of artifacts (e.g., executables, packages, containers, and virtual machine images) after its build. Such analysis generally requires a variety of heuristics. In some contexts, this may also be referred to as a \"3rd party\" SBOM."@en . + + a owl:NamedIndividual, + ns3:SbomType ; + rdfs:label "build" ; + rdfs:comment "SBOM generated as part of the process of building the software to create a releasable artifact (e.g., executable or package) from data such as source files, dependencies, built components, build process ephemeral data, and other SBOMs."@en . + + a owl:NamedIndividual, + ns3:SbomType ; + rdfs:label "deployed" ; + rdfs:comment "SBOM provides an inventory of software that is present on a system. This may be an assembly of other SBOMs that combines analysis of configuration options, and examination of execution behavior in a (potentially simulated) deployment environment."@en . + + a owl:NamedIndividual, + ns3:SbomType ; + rdfs:label "design" ; + rdfs:comment "SBOM of intended, planned software project or product with included components (some of which may not yet exist) for a new software artifact."@en . + + a owl:NamedIndividual, + ns3:SbomType ; + rdfs:label "runtime" ; + rdfs:comment "SBOM generated through instrumenting the system running the software, to capture only components present in the system, as well as external call-outs or dynamically loaded components. In some contexts, this may also be referred to as an \"Instrumented\" or \"Dynamic\" SBOM."@en . + + a owl:NamedIndividual, + ns3:SbomType ; + rdfs:label "source" ; + rdfs:comment "SBOM created directly from the development environment, source files, and included dependencies used to build an product artifact."@en . + +ns3:Snippet a owl:Class, + sh:NodeShape ; + rdfs:comment "Describes a certain part of a file."@en ; + rdfs:subClassOf ns3:SoftwareArtifact ; + sh:nodeKind sh:IRI ; + sh:property [ sh:class ns1:PositiveIntegerRange ; + sh:maxCount 1 ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ns3:lineRange ], + [ sh:class ns3:File ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns3:snippetFromFile ], + [ sh:class ns1:PositiveIntegerRange ; + sh:maxCount 1 ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ns3:byteRange ] . + +ns3:additionalPurpose a owl:ObjectProperty ; + rdfs:comment "Provides additional purpose information of the software artifact."@en ; + rdfs:range ns3:SoftwarePurpose . + +ns3:attributionText a owl:DatatypeProperty ; + rdfs:comment """Provides a place for the SPDX data creator to record acknowledgement text for +a software Package, File or Snippet."""@en ; + rdfs:range xsd:string . + +ns3:byteRange a owl:DatatypeProperty ; + rdfs:comment """Defines the byte range in the original host file that the snippet information +applies to."""@en ; + rdfs:range ns1:PositiveIntegerRange . + +ns3:contentIdentifier a owl:DatatypeProperty ; + rdfs:comment """A canonical, unique, immutable identifier of the artifact content, that may be +used for verifying its identity and/or integrity."""@en ; + rdfs:range ns3:ContentIdentifier . + +ns3:contentIdentifierType a owl:ObjectProperty ; + rdfs:comment "Specifies the type of the content identifier."@en ; + rdfs:range ns3:ContentIdentifierType . + +ns3:contentIdentifierValue a owl:DatatypeProperty ; + rdfs:comment "Specifies the value of the content identifier."@en ; + rdfs:range xsd:anyURI . + +ns3:copyrightText a owl:DatatypeProperty ; + rdfs:comment """Identifies the text of one or more copyright notices for a software Package, +File or Snippet, if any."""@en ; + rdfs:range xsd:string . + +ns3:downloadLocation a owl:DatatypeProperty ; + rdfs:comment """Identifies the download Uniform Resource Identifier for the package at the time +that the document was created."""@en ; + rdfs:range xsd:anyURI . + +ns3:fileKind a owl:ObjectProperty ; + rdfs:comment "Describes if a given file is a directory or non-directory kind of file."@en ; + rdfs:range ns3:FileKindType . + +ns3:homePage a owl:DatatypeProperty ; + rdfs:comment """A place for the SPDX document creator to record a website that serves as the +package's home page."""@en ; + rdfs:range xsd:anyURI . + +ns3:lineRange a owl:DatatypeProperty ; + rdfs:comment """Defines the line range in the original host file that the snippet information +applies to."""@en ; + rdfs:range ns1:PositiveIntegerRange . + +ns3:packageUrl a owl:DatatypeProperty ; + rdfs:comment """Provides a place for the SPDX data creator to record the package URL string +(in accordance with the Package URL specification) for a software Package."""@en ; + rdfs:range xsd:anyURI . + +ns3:packageVersion a owl:DatatypeProperty ; + rdfs:comment "Identify the version of a package."@en ; + rdfs:range xsd:string . + +ns3:primaryPurpose a owl:ObjectProperty ; + rdfs:comment "Provides information about the primary purpose of the software artifact."@en ; + rdfs:range ns3:SoftwarePurpose . + +ns3:sbomType a owl:ObjectProperty ; + rdfs:comment "Provides information about the type of an SBOM."@en ; + rdfs:range ns3:SbomType . + +ns3:snippetFromFile a owl:ObjectProperty ; + rdfs:comment "Defines the original host file that the snippet information applies to."@en ; + rdfs:range ns3:File . + +ns3:sourceInfo a owl:DatatypeProperty ; + rdfs:comment """Records any relevant background information or additional comments +about the origin of the package."""@en ; + rdfs:range xsd:string . + +ns1:Bom a owl:Class ; + rdfs:comment """A container for a grouping of SPDX-3.0 content characterizing details +(provenence, composition, licensing, etc.) about a product."""@en ; + rdfs:subClassOf ns1:Bundle ; + sh:nodeKind sh:IRI . + +ns1:Bundle a owl:Class, + sh:NodeShape ; + rdfs:comment "A collection of Elements that have a shared context."@en ; + rdfs:subClassOf ns1:ElementCollection ; + sh:nodeKind sh:IRI ; + sh:property [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:context ] . + + a owl:NamedIndividual, + ns1:HashAlgorithm ; + rdfs:label "adler32" ; + rdfs:comment "Adler-32 checksum is part of the widely used zlib compression library as defined in [RFC 1950](https://datatracker.ietf.org/doc/rfc1950/) Section 2.3."@en . + + a owl:NamedIndividual, + ns1:HashAlgorithm ; + rdfs:label "blake2b256" ; + rdfs:comment "BLAKE2b algorithm with a digest size of 256, as defined in [RFC 7693](https://datatracker.ietf.org/doc/rfc7693/) Section 4."@en . + + a owl:NamedIndividual, + ns1:HashAlgorithm ; + rdfs:label "blake2b384" ; + rdfs:comment "BLAKE2b algorithm with a digest size of 384, as defined in [RFC 7693](https://datatracker.ietf.org/doc/rfc7693/) Section 4."@en . + + a owl:NamedIndividual, + ns1:HashAlgorithm ; + rdfs:label "blake2b512" ; + rdfs:comment "BLAKE2b algorithm with a digest size of 512, as defined in [RFC 7693](https://datatracker.ietf.org/doc/rfc7693/) Section 4."@en . + + a owl:NamedIndividual, + ns1:HashAlgorithm ; + rdfs:label "blake3" ; + rdfs:comment "[BLAKE3](https://github.com/BLAKE3-team/BLAKE3-specs/blob/master/blake3.pdf)"@en . + + a owl:NamedIndividual, + ns1:HashAlgorithm ; + rdfs:label "crystalsDilithium" ; + rdfs:comment "[Dilithium](https://pq-crystals.org/dilithium/)"@en . + + a owl:NamedIndividual, + ns1:HashAlgorithm ; + rdfs:label "crystalsKyber" ; + rdfs:comment "[Kyber](https://pq-crystals.org/kyber/)"@en . + + a owl:NamedIndividual, + ns1:HashAlgorithm ; + rdfs:label "falcon" ; + rdfs:comment "[FALCON](https://falcon-sign.info/falcon.pdf)"@en . + + a owl:NamedIndividual, + ns1:HashAlgorithm ; + rdfs:label "md2" ; + rdfs:comment "MD2 message-digest algorithm, as defined in [RFC 1319](https://datatracker.ietf.org/doc/rfc1319/)."@en . + + a owl:NamedIndividual, + ns1:HashAlgorithm ; + rdfs:label "md4" ; + rdfs:comment "MD4 message-digest algorithm, as defined in [RFC 1186](https://datatracker.ietf.org/doc/rfc1186/)."@en . + + a owl:NamedIndividual, + ns1:HashAlgorithm ; + rdfs:label "md5" ; + rdfs:comment "MD5 message-digest algorithm, as defined in [RFC 1321](https://datatracker.ietf.org/doc/rfc1321/)."@en . + + a owl:NamedIndividual, + ns1:HashAlgorithm ; + rdfs:label "md6" ; + rdfs:comment "[MD6 hash function](https://people.csail.mit.edu/rivest/pubs/RABCx08.pdf)"@en . + + a owl:NamedIndividual, + ns1:HashAlgorithm ; + rdfs:label "other" ; + rdfs:comment "any hashing algorithm that does not exist in this list of entries"@en . + + a owl:NamedIndividual, + ns1:HashAlgorithm ; + rdfs:label "sha1" ; + rdfs:comment "SHA-1, a secure hashing algorithm, as defined in [RFC 3174](https://datatracker.ietf.org/doc/rfc3174/)."@en . + + a owl:NamedIndividual, + ns1:HashAlgorithm ; + rdfs:label "sha224" ; + rdfs:comment "SHA-2 with a digest length of 224, as defined in [RFC 3874](https://datatracker.ietf.org/doc/rfc3874/)."@en . + + a owl:NamedIndividual, + ns1:HashAlgorithm ; + rdfs:label "sha256" ; + rdfs:comment "SHA-2 with a digest length of 256, as defined in [RFC 6234](https://datatracker.ietf.org/doc/rfc6234/)."@en . + + a owl:NamedIndividual, + ns1:HashAlgorithm ; + rdfs:label "sha384" ; + rdfs:comment "SHA-2 with a digest length of 384, as defined in [RFC 6234](https://datatracker.ietf.org/doc/rfc6234/)."@en . + + a owl:NamedIndividual, + ns1:HashAlgorithm ; + rdfs:label "sha3_224" ; + rdfs:comment "SHA-3 with a digest length of 224, as defined in [FIPS 202](https://csrc.nist.gov/pubs/fips/202/final)."@en . + + a owl:NamedIndividual, + ns1:HashAlgorithm ; + rdfs:label "sha3_256" ; + rdfs:comment "SHA-3 with a digest length of 256, as defined in [FIPS 202](https://csrc.nist.gov/pubs/fips/202/final)."@en . + + a owl:NamedIndividual, + ns1:HashAlgorithm ; + rdfs:label "sha3_384" ; + rdfs:comment "SHA-3 with a digest length of 384, as defined in [FIPS 202](https://csrc.nist.gov/pubs/fips/202/final)."@en . + + a owl:NamedIndividual, + ns1:HashAlgorithm ; + rdfs:label "sha3_512" ; + rdfs:comment "SHA-3 with a digest length of 512, as defined in [FIPS 202](https://csrc.nist.gov/pubs/fips/202/final)."@en . + + a owl:NamedIndividual, + ns1:HashAlgorithm ; + rdfs:label "sha512" ; + rdfs:comment "SHA-2 with a digest length of 512, as defined in [RFC 6234](https://datatracker.ietf.org/doc/rfc6234/)."@en . + +ns1:Organization a owl:Class ; + rdfs:comment "A group of people who work together in an organized way for a shared purpose."@en ; + rdfs:subClassOf ns1:Agent ; + sh:nodeKind sh:IRI . + +ns1:algorithm a owl:ObjectProperty ; + rdfs:comment "Specifies the algorithm used for calculating the hash value."@en ; + rdfs:range ns1:HashAlgorithm . + +ns1:extension a owl:ObjectProperty ; + rdfs:comment "Specifies an Extension characterization of some aspect of an Element."@en ; + rdfs:range . + +ns1:hashValue a owl:DatatypeProperty ; + rdfs:comment "The result of applying a hash algorithm to an Element."@en ; + rdfs:range xsd:string . + +ns1:suppliedBy a owl:ObjectProperty ; + rdfs:comment """Identifies who or what supplied the artifact or VulnAssessmentRelationship +referenced by the Element."""@en ; + rdfs:range ns1:Agent . + +ns1:verifiedUsing a owl:ObjectProperty ; + rdfs:comment """Provides an IntegrityMethod with which the integrity of an Element can be +asserted."""@en ; + rdfs:range ns1:IntegrityMethod . + +ns2:deprecatedVersion a owl:DatatypeProperty ; + rdfs:comment """Specifies the SPDX License List version in which this license or exception +identifier was deprecated."""@en ; + rdfs:range xsd:string . + +ns2:licenseXml a owl:DatatypeProperty ; + rdfs:comment """Identifies all the text and metadata associated with a license in the license +XML format."""@en ; + rdfs:range xsd:string . + +ns2:listVersionAdded a owl:DatatypeProperty ; + rdfs:comment """Specifies the SPDX License List version in which this ListedLicense or +ListedLicenseException identifier was first added."""@en ; + rdfs:range xsd:string . + +ns2:member a owl:ObjectProperty ; + rdfs:comment "A license expression participating in a license set."@en ; + rdfs:range . + +ns2:obsoletedBy a owl:DatatypeProperty ; + rdfs:comment """Specifies the licenseId that is preferred to be used in place of a deprecated +License or LicenseAddition."""@en ; + rdfs:range xsd:string . + +ns2:seeAlso a owl:DatatypeProperty ; + rdfs:comment "Contains a URL where the License or LicenseAddition can be found in use."@en ; + rdfs:range xsd:anyURI . + + a owl:NamedIndividual, + ns6:CvssSeverityType ; + rdfs:label "critical" ; + rdfs:comment "When a CVSS score is between 9.0 - 10.0"@en . + + a owl:NamedIndividual, + ns6:CvssSeverityType ; + rdfs:label "high" ; + rdfs:comment "When a CVSS score is between 7.0 - 8.9"@en . + + a owl:NamedIndividual, + ns6:CvssSeverityType ; + rdfs:label "low" ; + rdfs:comment "When a CVSS score is between 0.1 - 3.9"@en . + + a owl:NamedIndividual, + ns6:CvssSeverityType ; + rdfs:label "medium" ; + rdfs:comment "When a CVSS score is between 4.0 - 6.9"@en . + + a owl:NamedIndividual, + ns6:CvssSeverityType ; + rdfs:label "none" ; + rdfs:comment "When a CVSS score is 0.0"@en . + +ns6:modifiedTime a owl:DatatypeProperty ; + rdfs:comment "Specifies a time when a vulnerability assessment was modified"@en ; + rdfs:range xsd:dateTimeStamp . + +ns6:publishedTime a owl:DatatypeProperty ; + rdfs:comment "Specifies the time when a vulnerability was published."@en ; + rdfs:range xsd:dateTimeStamp . + +ns6:severity a owl:ObjectProperty ; + rdfs:comment "Specifies the CVSS qualitative severity rating of a vulnerability in relation to a piece of software."@en ; + rdfs:range ns6:CvssSeverityType . + +ns6:withdrawnTime a owl:DatatypeProperty ; + rdfs:comment "Specified the time and date when a vulnerability was withdrawn."@en ; + rdfs:range xsd:dateTimeStamp . + + a owl:DatatypeProperty ; + rdfs:comment "Identifies the full text of a License or Addition."@en ; + rdfs:range xsd:string . + + a owl:NamedIndividual, + ns3:SoftwarePurpose ; + rdfs:label "application" ; + rdfs:comment "The Element is a software application."@en . + + a owl:NamedIndividual, + ns3:SoftwarePurpose ; + rdfs:label "archive" ; + rdfs:comment "The Element is an archived collection of one or more files (.tar, .zip, etc.)."@en . + + a owl:NamedIndividual, + ns3:SoftwarePurpose ; + rdfs:label "bom" ; + rdfs:comment "The Element is a bill of materials."@en . + + a owl:NamedIndividual, + ns3:SoftwarePurpose ; + rdfs:label "configuration" ; + rdfs:comment "The Element is configuration data."@en . + + a owl:NamedIndividual, + ns3:SoftwarePurpose ; + rdfs:label "container" ; + rdfs:comment "The Element is a container image which can be used by a container runtime application."@en . + + a owl:NamedIndividual, + ns3:SoftwarePurpose ; + rdfs:label "data" ; + rdfs:comment "The Element is data."@en . + + a owl:NamedIndividual, + ns3:SoftwarePurpose ; + rdfs:label "device" ; + rdfs:comment "The Element refers to a chipset, processor, or electronic board."@en . + + a owl:NamedIndividual, + ns3:SoftwarePurpose ; + rdfs:label "deviceDriver" ; + rdfs:comment "The Element represents software that controls hardware devices."@en . + + a owl:NamedIndividual, + ns3:SoftwarePurpose ; + rdfs:label "diskImage" ; + rdfs:comment "The Element refers to a disk image that can be written to a disk, booted in a VM, etc. A disk image typically contains most or all of the components necessary to boot, such as bootloaders, kernels, firmware, userspace, etc."@en . + + a owl:NamedIndividual, + ns3:SoftwarePurpose ; + rdfs:label "documentation" ; + rdfs:comment "The Element is documentation."@en . + + a owl:NamedIndividual, + ns3:SoftwarePurpose ; + rdfs:label "evidence" ; + rdfs:comment "The Element is the evidence that a specification or requirement has been fulfilled."@en . + + a owl:NamedIndividual, + ns3:SoftwarePurpose ; + rdfs:label "executable" ; + rdfs:comment "The Element is an Artifact that can be run on a computer."@en . + + a owl:NamedIndividual, + ns3:SoftwarePurpose ; + rdfs:label "file" ; + rdfs:comment "The Element is a single file which can be independently distributed (configuration file, statically linked binary, Kubernetes deployment, etc.)."@en . + + a owl:NamedIndividual, + ns3:SoftwarePurpose ; + rdfs:label "filesystemImage" ; + rdfs:comment "The Element is a file system image that can be written to a disk (or virtual) partition."@en . + + a owl:NamedIndividual, + ns3:SoftwarePurpose ; + rdfs:label "firmware" ; + rdfs:comment "The Element provides low level control over a device's hardware."@en . + + a owl:NamedIndividual, + ns3:SoftwarePurpose ; + rdfs:label "framework" ; + rdfs:comment "The Element is a software framework."@en . + + a owl:NamedIndividual, + ns3:SoftwarePurpose ; + rdfs:label "install" ; + rdfs:comment "The Element is used to install software on disk."@en . + + a owl:NamedIndividual, + ns3:SoftwarePurpose ; + rdfs:label "library" ; + rdfs:comment "The Element is a software library."@en . + + a owl:NamedIndividual, + ns3:SoftwarePurpose ; + rdfs:label "manifest" ; + rdfs:comment "The Element is a software manifest."@en . + + a owl:NamedIndividual, + ns3:SoftwarePurpose ; + rdfs:label "model" ; + rdfs:comment "The Element is a machine learning or artificial intelligence model."@en . + + a owl:NamedIndividual, + ns3:SoftwarePurpose ; + rdfs:label "module" ; + rdfs:comment "The Element is a module of a piece of software."@en . + + a owl:NamedIndividual, + ns3:SoftwarePurpose ; + rdfs:label "operatingSystem" ; + rdfs:comment "The Element is an operating system."@en . + + a owl:NamedIndividual, + ns3:SoftwarePurpose ; + rdfs:label "other" ; + rdfs:comment "The Element doesn't fit into any of the other categories."@en . + + a owl:NamedIndividual, + ns3:SoftwarePurpose ; + rdfs:label "patch" ; + rdfs:comment "The Element contains a set of changes to update, fix, or improve another Element."@en . + + a owl:NamedIndividual, + ns3:SoftwarePurpose ; + rdfs:label "platform" ; + rdfs:comment "The Element represents a runtime environment."@en . + + a owl:NamedIndividual, + ns3:SoftwarePurpose ; + rdfs:label "requirement" ; + rdfs:comment "The Element provides a requirement needed as input for another Element."@en . + + a owl:NamedIndividual, + ns3:SoftwarePurpose ; + rdfs:label "source" ; + rdfs:comment "The Element is a single or a collection of source files."@en . + + a owl:NamedIndividual, + ns3:SoftwarePurpose ; + rdfs:label "specification" ; + rdfs:comment "The Element is a plan, guideline or strategy how to create, perform or analyze an application."@en . + + a owl:NamedIndividual, + ns3:SoftwarePurpose ; + rdfs:label "test" ; + rdfs:comment "The Element is a test used to verify functionality on an software element."@en . + +ns4:EnergyConsumption a owl:Class, + sh:NodeShape ; + rdfs:comment """A class for describing the energy consumption incurred by an AI model in +different stages of its lifecycle."""@en ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:property [ sh:class ns4:EnergyConsumptionDescription ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ns4:finetuningEnergyConsumption ], + [ sh:class ns4:EnergyConsumptionDescription ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ns4:inferenceEnergyConsumption ], + [ sh:class ns4:EnergyConsumptionDescription ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ns4:trainingEnergyConsumption ] . + +ns1:ElementCollection a owl:Class, + sh:NodeShape ; + rdfs:comment "A collection of Elements, not necessarily with unifying context."@en ; + rdfs:subClassOf ns1:Element ; + sh:nodeKind sh:IRI ; + sh:property [ sh:class ns1:Element ; + sh:nodeKind sh:IRI ; + sh:path ns1:element ], + [ sh:class ns1:ProfileIdentifierType ; + sh:in ( ) ; + sh:nodeKind sh:IRI ; + sh:path ns1:profileConformance ], + [ sh:class ns1:Element ; + sh:nodeKind sh:IRI ; + sh:path ns1:rootElement ], + [ sh:message "https://spdx.org/rdf/3.0.1/terms/Core/ElementCollection is an abstract class and should not be instantiated directly. Instantiate a subclass instead."@en ; + sh:not [ sh:hasValue ns1:ElementCollection ] ; + sh:path rdf:type ] . + +ns1:ExternalIdentifier a owl:Class, + sh:NodeShape ; + rdfs:comment "A reference to a resource identifier defined outside the scope of SPDX-3.0 content that uniquely identifies an Element."@en ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:property [ sh:datatype xsd:anyURI ; + sh:nodeKind sh:Literal ; + sh:path ns1:identifierLocator ], + [ sh:class ns1:ExternalIdentifierType ; + sh:in ( ) ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns1:externalIdentifierType ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:issuingAuthority ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:identifier ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:comment ] . + +ns1:ExternalMap a owl:Class, + sh:NodeShape ; + rdfs:comment """A map of Element identifiers that are used within an SpdxDocument but defined +external to that SpdxDocument."""@en ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:property [ sh:class ns1:Artifact ; + sh:maxCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns1:definingArtifact ], + [ sh:datatype xsd:anyURI ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:locationHint ], + [ sh:datatype xsd:anyURI ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:externalSpdxId ], + [ sh:class ns1:IntegrityMethod ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ns1:verifiedUsing ] . + +ns1:ExternalRef a owl:Class, + sh:NodeShape ; + rdfs:comment "A reference to a resource outside the scope of SPDX-3.0 content related to an Element."@en ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:property [ sh:datatype xsd:string ; + sh:nodeKind sh:Literal ; + sh:path ns1:locator ], + [ sh:class ns1:ExternalRefType ; + sh:in ( ) ; + sh:maxCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns1:externalRefType ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:comment ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:contentType ; + sh:pattern "^[^\\/]+\\/[^\\/]+$" ] . + +ns1:Hash a owl:Class, + sh:NodeShape ; + rdfs:comment "A mathematically calculated representation of a grouping of data."@en ; + rdfs:subClassOf ns1:IntegrityMethod ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:property [ sh:class ns1:HashAlgorithm ; + sh:in ( ) ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns1:algorithm ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:hashValue ] . + +ns1:IndividualElement a owl:Class ; + rdfs:comment """A concrete subclass of Element used by Individuals in the +Core profile."""@en ; + rdfs:subClassOf ns1:Element ; + sh:nodeKind sh:IRI . + +ns1:NamespaceMap a owl:Class, + sh:NodeShape ; + rdfs:comment "A mapping between prefixes and namespace partial URIs."@en ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:property [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:prefix ], + [ sh:datatype xsd:anyURI ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:namespace ] . + + a owl:NamedIndividual, + ns1:PresenceType ; + rdfs:label "no" ; + rdfs:comment "Indicates absence of the field."@en . + + a owl:NamedIndividual, + ns1:PresenceType ; + rdfs:label "noAssertion" ; + rdfs:comment "Makes no assertion about the field."@en . + + a owl:NamedIndividual, + ns1:PresenceType ; + rdfs:label "yes" ; + rdfs:comment "Indicates presence of the field."@en . + +ns1:Relationship a owl:Class, + sh:NodeShape ; + rdfs:comment "Describes a relationship between one or more elements."@en ; + rdfs:subClassOf ns1:Element ; + sh:nodeKind sh:IRI ; + sh:property [ sh:class ns1:Element ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns1:to ], + [ sh:class ns1:RelationshipCompleteness ; + sh:in ( ) ; + sh:maxCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns1:completeness ], + [ sh:datatype xsd:dateTimeStamp ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:startTime ; + sh:pattern "^\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\dZ$" ], + [ sh:class ns1:RelationshipType ; + sh:in ( ) ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns1:relationshipType ], + [ sh:class ns1:Element ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns1:from ], + [ sh:datatype xsd:dateTimeStamp ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:endTime ; + sh:pattern "^\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\dZ$" ] . + +ns1:Tool a owl:Class ; + rdfs:comment "An element of hardware and/or software utilized to carry out a particular function."@en ; + rdfs:subClassOf ns1:Element ; + sh:nodeKind sh:IRI . + +ns1:contentType a owl:DatatypeProperty ; + rdfs:comment "Provides information about the content type of an Element or a Property."@en ; + rdfs:range xsd:string . + +ns2:IndividualLicensingInfo a owl:Class ; + rdfs:comment """A concrete subclass of AnyLicenseInfo used by Individuals in the +ExpandedLicensing profile."""@en ; + rdfs:subClassOf ; + sh:nodeKind sh:IRI . + + a owl:Class, + sh:NodeShape ; + rdfs:comment "A property name with an associated value."@en ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:property [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ] . + + a owl:Class ; + rdfs:comment "A characterization of some aspect of an Element that is associated with the Element in a generalized fashion."@en ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:property [ sh:message "https://spdx.org/rdf/3.0.1/terms/Extension/Extension is an abstract class and should not be instantiated directly. Instantiate a subclass instead."@en ; + sh:not [ sh:hasValue ] ; + sh:path rdf:type ] . + +ns6:score a owl:DatatypeProperty ; + rdfs:comment "Provides a numerical (0-10) representation of the severity of a vulnerability."@en ; + rdfs:range xsd:decimal . + +ns6:vectorString a owl:DatatypeProperty ; + rdfs:comment "Specifies the CVSS vector string for a vulnerability."@en ; + rdfs:range xsd:string . + +ns3:ContentIdentifier a owl:Class, + sh:NodeShape ; + rdfs:comment "A canonical, unique, immutable identifier"@en ; + rdfs:subClassOf ns1:IntegrityMethod ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:property [ sh:datatype xsd:anyURI ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns3:contentIdentifierValue ], + [ sh:class ns3:ContentIdentifierType ; + sh:in ( ) ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns3:contentIdentifierType ] . + +ns3:File a owl:Class, + sh:NodeShape ; + rdfs:comment "Refers to any object that stores content on a computer."@en ; + rdfs:subClassOf ns3:SoftwareArtifact ; + sh:nodeKind sh:IRI ; + sh:property [ sh:class ns3:FileKindType ; + sh:in ( ) ; + sh:maxCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns3:fileKind ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:contentType ; + sh:pattern "^[^\\/]+\\/[^\\/]+$" ] . + +ns3:Package a owl:Class, + sh:NodeShape ; + rdfs:comment """Refers to any unit of content that can be associated with a distribution of +software."""@en ; + rdfs:subClassOf ns3:SoftwareArtifact ; + sh:nodeKind sh:IRI ; + sh:property [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns3:sourceInfo ], + [ sh:datatype xsd:anyURI ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns3:homePage ], + [ sh:datatype xsd:anyURI ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns3:downloadLocation ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns3:packageVersion ], + [ sh:datatype xsd:anyURI ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns3:packageUrl ] . + +ns1:AnnotationType a owl:Class ; + rdfs:comment "Specifies the type of an annotation."@en . + +ns6:ExploitCatalogType a owl:Class ; + rdfs:comment "Specifies the exploit catalog type."@en . + +ns3:ContentIdentifierType a owl:Class ; + rdfs:comment "Specifies the type of a content identifier."@en . + +ns3:FileKindType a owl:Class ; + rdfs:comment "Enumeration of the different kinds of SPDX file."@en . + +ns4:EnergyUnitType a owl:Class ; + rdfs:comment "Specifies the unit of energy consumption."@en . + +ns1:Artifact a owl:Class, + sh:NodeShape ; + rdfs:comment "A distinct article or unit within the digital domain."@en ; + rdfs:subClassOf ns1:Element ; + sh:nodeKind sh:IRI ; + sh:property [ sh:datatype xsd:string ; + sh:nodeKind sh:Literal ; + sh:path ns1:standardName ], + [ sh:datatype xsd:dateTimeStamp ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:builtTime ; + sh:pattern "^\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\dZ$" ], + [ sh:datatype xsd:dateTimeStamp ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:validUntilTime ; + sh:pattern "^\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\dZ$" ], + [ sh:message "https://spdx.org/rdf/3.0.1/terms/Core/Artifact is an abstract class and should not be instantiated directly. Instantiate a subclass instead."@en ; + sh:not [ sh:hasValue ns1:Artifact ] ; + sh:path rdf:type ], + [ sh:class ns1:SupportType ; + sh:in ( ) ; + sh:nodeKind sh:IRI ; + sh:path ns1:supportLevel ], + [ sh:class ns1:Agent ; + sh:maxCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns1:suppliedBy ], + [ sh:class ns1:Agent ; + sh:nodeKind sh:IRI ; + sh:path ns1:originatedBy ], + [ sh:datatype xsd:dateTimeStamp ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:releaseTime ; + sh:pattern "^\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\dZ$" ] . + +ns1:PositiveIntegerRange a owl:Class, + sh:NodeShape ; + rdfs:comment "A tuple of two positive integers that define a range."@en ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:property [ sh:datatype xsd:positiveInteger ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:endIntegerRange ], + [ sh:datatype xsd:positiveInteger ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:beginIntegerRange ] . + +ns1:RelationshipCompleteness a owl:Class ; + rdfs:comment "Indicates whether a relationship is known to be complete, incomplete, or if no assertion is made with respect to relationship completeness."@en . + +ns1:SpdxOrganization a owl:NamedIndividual, + ns1:Organization ; + rdfs:comment "An Organization representing the SPDX Project."@en ; + owl:sameAs ; + ns1:creationInfo . + +ns1:comment a owl:DatatypeProperty ; + rdfs:comment """Provide consumers with comments by the creator of the Element about the +Element."""@en ; + rdfs:range xsd:string . + +ns2:ExtendableLicense a owl:Class ; + rdfs:comment "Abstract class representing a License or an OrLaterOperator."@en ; + rdfs:subClassOf ; + sh:nodeKind sh:IRI ; + sh:property [ sh:message "https://spdx.org/rdf/3.0.1/terms/ExpandedLicensing/ExtendableLicense is an abstract class and should not be instantiated directly. Instantiate a subclass instead."@en ; + sh:not [ sh:hasValue ns2:ExtendableLicense ] ; + sh:path rdf:type ] . + +ns2:License a owl:Class, + sh:NodeShape ; + rdfs:comment "Abstract class for the portion of an AnyLicenseInfo representing a license."@en ; + rdfs:subClassOf ns2:ExtendableLicense ; + sh:nodeKind sh:IRI ; + sh:property [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns2:obsoletedBy ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns2:standardLicenseHeader ], + [ sh:datatype xsd:anyURI ; + sh:nodeKind sh:Literal ; + sh:path ns2:seeAlso ], + [ sh:datatype xsd:boolean ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns2:isFsfLibre ], + [ sh:datatype xsd:boolean ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns2:isDeprecatedLicenseId ], + [ sh:datatype xsd:boolean ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns2:isOsiApproved ], + [ sh:message "https://spdx.org/rdf/3.0.1/terms/ExpandedLicensing/License is an abstract class and should not be instantiated directly. Instantiate a subclass instead."@en ; + sh:not [ sh:hasValue ns2:License ] ; + sh:path rdf:type ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns2:licenseXml ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns2:standardLicenseTemplate ] . + +ns2:LicenseAddition a owl:Class, + sh:NodeShape ; + rdfs:comment """Abstract class for additional text intended to be added to a License, but +which is not itself a standalone License."""@en ; + rdfs:subClassOf ns1:Element ; + sh:nodeKind sh:IRI ; + sh:property [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns2:standardAdditionTemplate ], + [ sh:datatype xsd:anyURI ; + sh:nodeKind sh:Literal ; + sh:path ns2:seeAlso ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns2:obsoletedBy ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns2:licenseXml ], + [ sh:datatype xsd:boolean ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns2:isDeprecatedAdditionId ], + [ sh:message "https://spdx.org/rdf/3.0.1/terms/ExpandedLicensing/LicenseAddition is an abstract class and should not be instantiated directly. Instantiate a subclass instead."@en ; + sh:not [ sh:hasValue ns2:LicenseAddition ] ; + sh:path rdf:type ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns2:additionText ] . + +ns6:VexVulnAssessmentRelationship a owl:Class, + sh:NodeShape ; + rdfs:comment "Abstract ancestor class for all VEX relationships"@en ; + rdfs:subClassOf ns6:VulnAssessmentRelationship ; + sh:nodeKind sh:IRI ; + sh:property [ sh:message "https://spdx.org/rdf/3.0.1/terms/Security/VexVulnAssessmentRelationship is an abstract class and should not be instantiated directly. Instantiate a subclass instead."@en ; + sh:not [ sh:hasValue ns6:VexVulnAssessmentRelationship ] ; + sh:path rdf:type ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns6:vexVersion ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns6:statusNotes ] . + +ns4:SafetyRiskAssessmentType a owl:Class ; + rdfs:comment "Specifies the safety risk level."@en . + +ns5:ConfidentialityLevelType a owl:Class ; + rdfs:comment "Categories of confidentiality level."@en . + +ns6:SsvcDecisionType a owl:Class ; + rdfs:comment "Specifies the SSVC decision type."@en . + +ns3:SoftwareArtifact a owl:Class, + sh:NodeShape ; + rdfs:comment "A distinct article or unit related to Software."@en ; + rdfs:subClassOf ns1:Artifact ; + sh:nodeKind sh:IRI ; + sh:property [ sh:datatype xsd:string ; + sh:nodeKind sh:Literal ; + sh:path ns3:attributionText ], + [ sh:class ns3:SoftwarePurpose ; + sh:in ( ) ; + sh:maxCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns3:primaryPurpose ], + [ sh:class ns3:SoftwarePurpose ; + sh:in ( ) ; + sh:nodeKind sh:IRI ; + sh:path ns3:additionalPurpose ], + [ sh:class ns3:ContentIdentifier ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ns3:contentIdentifier ], + [ sh:message "https://spdx.org/rdf/3.0.1/terms/Software/SoftwareArtifact is an abstract class and should not be instantiated directly. Instantiate a subclass instead."@en ; + sh:not [ sh:hasValue ns3:SoftwareArtifact ] ; + sh:path rdf:type ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns3:copyrightText ] . + +ns4:EnergyConsumptionDescription a owl:Class, + sh:NodeShape ; + rdfs:comment """The class that helps note down the quantity of energy consumption and the unit +used for measurement."""@en ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:property [ sh:datatype xsd:decimal ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns4:energyQuantity ], + [ sh:class ns4:EnergyUnitType ; + sh:in ( ) ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns4:energyUnit ] . + +ns1:IntegrityMethod a owl:Class, + sh:NodeShape ; + rdfs:comment "Provides an independently reproducible mechanism that permits verification of a specific Element."@en ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:property [ sh:message "https://spdx.org/rdf/3.0.1/terms/Core/IntegrityMethod is an abstract class and should not be instantiated directly. Instantiate a subclass instead."@en ; + sh:not [ sh:hasValue ns1:IntegrityMethod ] ; + sh:path rdf:type ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:comment ] . + +ns5:DatasetAvailabilityType a owl:Class ; + rdfs:comment "Availability of dataset."@en . + +ns6:VexJustificationType a owl:Class ; + rdfs:comment "Specifies the VEX justification type."@en . + +ns1:CreationInfo a owl:Class, + sh:NodeShape ; + rdfs:comment "Provides information about the creation of the Element."@en ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:property [ sh:class ns1:Agent ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns1:createdBy ], + [ sh:class ns1:Tool ; + sh:nodeKind sh:IRI ; + sh:path ns1:createdUsing ], + [ sh:datatype xsd:dateTimeStamp ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:created ; + sh:pattern "^\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\dZ$" ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:specVersion ; + sh:pattern "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$" ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:comment ] . + +ns1:LifecycleScopeType a owl:Class ; + rdfs:comment "Provide an enumerated set of lifecycle phases that can provide context to relationships."@en . + +ns6:CvssSeverityType a owl:Class ; + rdfs:comment "Specifies the CVSS base, temporal, threat, or environmental severity type."@en . + +ns6:VulnAssessmentRelationship a owl:Class, + sh:NodeShape ; + rdfs:comment "Abstract ancestor class for all vulnerability assessments"@en ; + rdfs:subClassOf ns1:Relationship ; + sh:nodeKind sh:IRI ; + sh:property [ sh:datatype xsd:dateTimeStamp ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns6:withdrawnTime ; + sh:pattern "^\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\dZ$" ], + [ sh:datatype xsd:dateTimeStamp ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns6:publishedTime ; + sh:pattern "^\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\dZ$" ], + [ sh:message "https://spdx.org/rdf/3.0.1/terms/Security/VulnAssessmentRelationship is an abstract class and should not be instantiated directly. Instantiate a subclass instead."@en ; + sh:not [ sh:hasValue ns6:VulnAssessmentRelationship ] ; + sh:path rdf:type ], + [ sh:class ns3:SoftwareArtifact ; + sh:maxCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns6:assessedElement ], + [ sh:class ns1:Agent ; + sh:maxCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns1:suppliedBy ], + [ sh:datatype xsd:dateTimeStamp ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns6:modifiedTime ; + sh:pattern "^\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\dZ$" ] . + +ns3:SbomType a owl:Class ; + rdfs:comment """Provides a set of values to be used to describe the common types of SBOMs that +tools may create."""@en . + +ns1:PresenceType a owl:Class ; + rdfs:comment "Categories of presence or absence."@en . + +ns1:SupportType a owl:Class ; + rdfs:comment "Indicates the type of support that is associated with an artifact."@en . + +ns1:Agent a owl:Class ; + rdfs:comment "Agent represents anything with the potential to act on a system."@en ; + rdfs:subClassOf ns1:Element ; + sh:nodeKind sh:IRI . + +ns1:ProfileIdentifierType a owl:Class ; + rdfs:comment "Enumeration of the valid profiles."@en . + + a owl:Class ; + rdfs:comment "Abstract class representing a license combination consisting of one or more licenses."@en ; + rdfs:subClassOf ns1:Element ; + sh:nodeKind sh:IRI ; + sh:property [ sh:message "https://spdx.org/rdf/3.0.1/terms/SimpleLicensing/AnyLicenseInfo is an abstract class and should not be instantiated directly. Instantiate a subclass instead."@en ; + sh:not [ sh:hasValue ] ; + sh:path rdf:type ] . + +ns1:ExternalIdentifierType a owl:Class ; + rdfs:comment "Specifies the type of an external identifier."@en . + +ns1:DictionaryEntry a owl:Class, + sh:NodeShape ; + rdfs:comment "A key with an associated value."@en ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:property [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:value ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:key ] . + +ns5:DatasetType a owl:Class ; + rdfs:comment "Enumeration of dataset types."@en . + +ns1:Element a owl:Class, + sh:NodeShape ; + rdfs:comment "Base domain class from which all other SPDX-3.0 domain classes derive."@en ; + sh:nodeKind sh:IRI ; + sh:property [ sh:class ns1:ExternalIdentifier ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ns1:externalIdentifier ], + [ sh:message "Class is known to not derive from Extension and cannot be used"@en ; + sh:not [ sh:or ( [ sh:class ns6:SsvcVulnAssessmentRelationship ] [ sh:class ns6:CvssV2VulnAssessmentRelationship ] [ sh:class ns6:ExploitCatalogVulnAssessmentRelationship ] [ sh:class ns6:CvssV4VulnAssessmentRelationship ] [ sh:class ns6:VexAffectedVulnAssessmentRelationship ] [ sh:class ns6:VexNotAffectedVulnAssessmentRelationship ] [ sh:class ns6:CvssV3VulnAssessmentRelationship ] [ sh:class ns6:Vulnerability ] [ sh:class ns6:VexUnderInvestigationVulnAssessmentRelationship ] [ sh:class ns6:EpssVulnAssessmentRelationship ] [ sh:class ns6:VexFixedVulnAssessmentRelationship ] [ sh:class ns1:NamespaceMap ] [ sh:class ns1:LifecycleScopedRelationship ] [ sh:class ns1:Hash ] [ sh:class ns1:Agent ] [ sh:class ns1:CreationInfo ] [ sh:class ns1:ExternalRef ] [ sh:class ns1:Bom ] [ sh:class ns1:IndividualElement ] [ sh:class ns1:Relationship ] [ sh:class ns1:PositiveIntegerRange ] [ sh:class ns1:DictionaryEntry ] [ sh:class ns1:ExternalMap ] [ sh:class ns1:Annotation ] [ sh:class ns1:SpdxDocument ] [ sh:class ns1:Person ] [ sh:class ns1:Organization ] [ sh:class ns1:Bundle ] [ sh:class ns1:Tool ] [ sh:class ns1:ExternalIdentifier ] [ sh:class ns1:SoftwareAgent ] [ sh:class ns1:PackageVerificationCode ] [ sh:class ns4:AIPackage ] [ sh:class ns4:EnergyConsumptionDescription ] [ sh:class ns4:EnergyConsumption ] [ sh:class ] [ sh:class ns5:DatasetPackage ] [ sh:class ns2:CustomLicense ] [ sh:class ns2:OrLaterOperator ] [ sh:class ns2:ListedLicense ] [ sh:class ns2:DisjunctiveLicenseSet ] [ sh:class ns2:ListedLicenseException ] [ sh:class ns2:WithAdditionOperator ] [ sh:class ns2:IndividualLicensingInfo ] [ sh:class ns2:CustomLicenseAddition ] [ sh:class ns2:ConjunctiveLicenseSet ] [ sh:class ] [ sh:class ] [ sh:class ] [ sh:class ns3:Package ] [ sh:class ns3:File ] [ sh:class ns3:Sbom ] [ sh:class ns3:Snippet ] [ sh:class ns3:ContentIdentifier ] ) ] ; + sh:path ns1:extension ], + [ sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ns1:extension ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:summary ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:description ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:comment ], + [ sh:class ns1:IntegrityMethod ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ns1:verifiedUsing ], + [ sh:class ns1:ExternalRef ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ns1:externalRef ], + [ sh:message "https://spdx.org/rdf/3.0.1/terms/Core/Element is an abstract class and should not be instantiated directly. Instantiate a subclass instead."@en ; + sh:not [ sh:hasValue ns1:Element ] ; + sh:path rdf:type ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:name ], + [ sh:class ns1:CreationInfo ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ns1:creationInfo ] . + +ns1:HashAlgorithm a owl:Class ; + rdfs:comment "A mathematical algorithm that maps data of arbitrary size to a bit string."@en . + +ns3:SoftwarePurpose a owl:Class ; + rdfs:comment "Provides information about the primary purpose of an Element."@en . + +ns1:ExternalRefType a owl:Class ; + rdfs:comment "Specifies the type of an external reference."@en . + +ns1:RelationshipType a owl:Class ; + rdfs:comment "Information about the relationship between two Elements."@en . + diff --git a/tests/data/spdx/3.1-dev/spdx-context.jsonld b/tests/data/spdx/3.1-dev/spdx-context.jsonld new file mode 100644 index 00000000..c05fe5bb --- /dev/null +++ b/tests/data/spdx/3.1-dev/spdx-context.jsonld @@ -0,0 +1,1313 @@ +{ + "@context": { + "Action": "https://spdx.org/rdf/3.1/terms/Core/Action", + "Agent": "https://spdx.org/rdf/3.1/terms/Core/Agent", + "Annotation": "https://spdx.org/rdf/3.1/terms/Core/Annotation", + "AnnotationType": "https://spdx.org/rdf/3.1/terms/Core/AnnotationType", + "Artifact": "https://spdx.org/rdf/3.1/terms/Core/Artifact", + "Bom": "https://spdx.org/rdf/3.1/terms/Core/Bom", + "Bundle": "https://spdx.org/rdf/3.1/terms/Core/Bundle", + "ContactPointRelationship": "https://spdx.org/rdf/3.1/terms/Core/ContactPointRelationship", + "ContactPointRelationshipType": "https://spdx.org/rdf/3.1/terms/Core/ContactPointRelationshipType", + "CreationInfo": "https://spdx.org/rdf/3.1/terms/Core/CreationInfo", + "DefinedProcess": "https://spdx.org/rdf/3.1/terms/Core/DefinedProcess", + "DefinedType": "https://spdx.org/rdf/3.1/terms/Core/DefinedType", + "DictionaryEntry": "https://spdx.org/rdf/3.1/terms/Core/DictionaryEntry", + "Element": "https://spdx.org/rdf/3.1/terms/Core/Element", + "ElementCollection": "https://spdx.org/rdf/3.1/terms/Core/ElementCollection", + "ElementMap": "https://spdx.org/rdf/3.1/terms/Core/ElementMap", + "ExternalIdentifier": "https://spdx.org/rdf/3.1/terms/Core/ExternalIdentifier", + "ExternalIdentifierType": "https://spdx.org/rdf/3.1/terms/Core/ExternalIdentifierType", + "ExternalMap": "https://spdx.org/rdf/3.1/terms/Core/ExternalMap", + "ExternalRef": "https://spdx.org/rdf/3.1/terms/Core/ExternalRef", + "ExternalRefType": "https://spdx.org/rdf/3.1/terms/Core/ExternalRefType", + "Hash": "https://spdx.org/rdf/3.1/terms/Core/Hash", + "HashAlgorithm": "https://spdx.org/rdf/3.1/terms/Core/HashAlgorithm", + "IndividualElement": "https://spdx.org/rdf/3.1/terms/Core/IndividualElement", + "IntegrityMethod": "https://spdx.org/rdf/3.1/terms/Core/IntegrityMethod", + "IsoAutomationLevel": "https://spdx.org/rdf/3.1/terms/Core/IsoAutomationLevel", + "LifecycleScopeType": "https://spdx.org/rdf/3.1/terms/Core/LifecycleScopeType", + "LifecycleScopedRelationship": "https://spdx.org/rdf/3.1/terms/Core/LifecycleScopedRelationship", + "Location": "https://spdx.org/rdf/3.1/terms/Core/Location", + "MeasureOfLength": "https://spdx.org/rdf/3.1/terms/Core/MeasureOfLength", + "MeasureOfMass": "https://spdx.org/rdf/3.1/terms/Core/MeasureOfMass", + "NamespaceMap": "https://spdx.org/rdf/3.1/terms/Core/NamespaceMap", + "NoAssertionElement": "https://spdx.org/rdf/3.1/terms/Core/NoAssertionElement", + "NoneElement": "https://spdx.org/rdf/3.1/terms/Core/NoneElement", + "Organization": "https://spdx.org/rdf/3.1/terms/Core/Organization", + "PackageVerificationCode": "https://spdx.org/rdf/3.1/terms/Core/PackageVerificationCode", + "Person": "https://spdx.org/rdf/3.1/terms/Core/Person", + "PhysicalLocation": "https://spdx.org/rdf/3.1/terms/Core/PhysicalLocation", + "PositiveIntegerRange": "https://spdx.org/rdf/3.1/terms/Core/PositiveIntegerRange", + "PresenceType": "https://spdx.org/rdf/3.1/terms/Core/PresenceType", + "ProcessReadinessType": "https://spdx.org/rdf/3.1/terms/Core/ProcessReadinessType", + "ProfileIdentifierType": "https://spdx.org/rdf/3.1/terms/Core/ProfileIdentifierType", + "Regulation": "https://spdx.org/rdf/3.1/terms/Core/Regulation", + "Relationship": "https://spdx.org/rdf/3.1/terms/Core/Relationship", + "RelationshipCompleteness": "https://spdx.org/rdf/3.1/terms/Core/RelationshipCompleteness", + "RelationshipType": "https://spdx.org/rdf/3.1/terms/Core/RelationshipType", + "Requirement": "https://spdx.org/rdf/3.1/terms/Core/Requirement", + "SoftwareAgent": "https://spdx.org/rdf/3.1/terms/Core/SoftwareAgent", + "SpdxDocument": "https://spdx.org/rdf/3.1/terms/Core/SpdxDocument", + "SpdxOrganization": "https://spdx.org/rdf/3.1/terms/Core/SpdxOrganization", + "Specification": "https://spdx.org/rdf/3.1/terms/Core/Specification", + "SpecificationType": "https://spdx.org/rdf/3.1/terms/Core/SpecificationType", + "SupportRelationship": "https://spdx.org/rdf/3.1/terms/Core/SupportRelationship", + "SupportType": "https://spdx.org/rdf/3.1/terms/Core/SupportType", + "Tool": "https://spdx.org/rdf/3.1/terms/Core/Tool", + "UnitOfMeasure": "https://spdx.org/rdf/3.1/terms/Core/UnitOfMeasure", + "actionEndTime": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/actionEndTime", + "@type": "http://www.w3.org/2001/XMLSchema#dateTimeStamp" + }, + "actionLocation": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/actionLocation", + "@type": "@vocab" + }, + "actionStartTime": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/actionStartTime", + "@type": "http://www.w3.org/2001/XMLSchema#dateTimeStamp" + }, + "additionalInformation": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/additionalInformation", + "@type": "@vocab" + }, + "ai_AIPackage": "https://spdx.org/rdf/3.1/terms/AI/AIPackage", + "ai_EnergyConsumption": "https://spdx.org/rdf/3.1/terms/AI/EnergyConsumption", + "ai_EnergyConsumptionDescription": "https://spdx.org/rdf/3.1/terms/AI/EnergyConsumptionDescription", + "ai_EnergyUnitType": "https://spdx.org/rdf/3.1/terms/AI/EnergyUnitType", + "ai_SafetyRiskAssessmentType": "https://spdx.org/rdf/3.1/terms/AI/SafetyRiskAssessmentType", + "ai_autonomyType": { + "@context": { + "@vocab": "https://spdx.org/rdf/3.1/terms/Core/PresenceType/" + }, + "@id": "https://spdx.org/rdf/3.1/terms/AI/autonomyType", + "@type": "@vocab" + }, + "ai_domain": { + "@id": "https://spdx.org/rdf/3.1/terms/AI/domain", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "ai_energyConsumption": { + "@id": "https://spdx.org/rdf/3.1/terms/AI/energyConsumption", + "@type": "@vocab" + }, + "ai_energyQuantity": { + "@id": "https://spdx.org/rdf/3.1/terms/AI/energyQuantity", + "@type": "http://www.w3.org/2001/XMLSchema#decimal" + }, + "ai_energyUnit": { + "@context": { + "@vocab": "https://spdx.org/rdf/3.1/terms/AI/EnergyUnitType/" + }, + "@id": "https://spdx.org/rdf/3.1/terms/AI/energyUnit", + "@type": "@vocab" + }, + "ai_finetuningEnergyConsumption": { + "@id": "https://spdx.org/rdf/3.1/terms/AI/finetuningEnergyConsumption", + "@type": "@vocab" + }, + "ai_hyperparameter": { + "@id": "https://spdx.org/rdf/3.1/terms/AI/hyperparameter", + "@type": "@vocab" + }, + "ai_inferenceEnergyConsumption": { + "@id": "https://spdx.org/rdf/3.1/terms/AI/inferenceEnergyConsumption", + "@type": "@vocab" + }, + "ai_informationAboutApplication": { + "@id": "https://spdx.org/rdf/3.1/terms/AI/informationAboutApplication", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "ai_informationAboutTraining": { + "@id": "https://spdx.org/rdf/3.1/terms/AI/informationAboutTraining", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "ai_limitation": { + "@id": "https://spdx.org/rdf/3.1/terms/AI/limitation", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "ai_metric": { + "@id": "https://spdx.org/rdf/3.1/terms/AI/metric", + "@type": "@vocab" + }, + "ai_metricDecisionThreshold": { + "@id": "https://spdx.org/rdf/3.1/terms/AI/metricDecisionThreshold", + "@type": "@vocab" + }, + "ai_modelDataPreprocessing": { + "@id": "https://spdx.org/rdf/3.1/terms/AI/modelDataPreprocessing", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "ai_modelExplainability": { + "@id": "https://spdx.org/rdf/3.1/terms/AI/modelExplainability", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "ai_safetyRiskAssessment": { + "@context": { + "@vocab": "https://spdx.org/rdf/3.1/terms/AI/SafetyRiskAssessmentType/" + }, + "@id": "https://spdx.org/rdf/3.1/terms/AI/safetyRiskAssessment", + "@type": "@vocab" + }, + "ai_standardCompliance": { + "@id": "https://spdx.org/rdf/3.1/terms/AI/standardCompliance", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "ai_trainingEnergyConsumption": { + "@id": "https://spdx.org/rdf/3.1/terms/AI/trainingEnergyConsumption", + "@type": "@vocab" + }, + "ai_typeOfModel": { + "@id": "https://spdx.org/rdf/3.1/terms/AI/typeOfModel", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "ai_useSensitivePersonalInformation": { + "@context": { + "@vocab": "https://spdx.org/rdf/3.1/terms/Core/PresenceType/" + }, + "@id": "https://spdx.org/rdf/3.1/terms/AI/useSensitivePersonalInformation", + "@type": "@vocab" + }, + "algorithm": { + "@context": { + "@vocab": "https://spdx.org/rdf/3.1/terms/Core/HashAlgorithm/" + }, + "@id": "https://spdx.org/rdf/3.1/terms/Core/algorithm", + "@type": "@vocab" + }, + "annotationType": { + "@context": { + "@vocab": "https://spdx.org/rdf/3.1/terms/Core/AnnotationType/" + }, + "@id": "https://spdx.org/rdf/3.1/terms/Core/annotationType", + "@type": "@vocab" + }, + "beginIntegerRange": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/beginIntegerRange", + "@type": "http://www.w3.org/2001/XMLSchema#positiveInteger" + }, + "build_Build": "https://spdx.org/rdf/3.1/terms/Build/Build", + "build_buildEndTime": { + "@id": "https://spdx.org/rdf/3.1/terms/Build/buildEndTime", + "@type": "http://www.w3.org/2001/XMLSchema#dateTimeStamp" + }, + "build_buildId": { + "@id": "https://spdx.org/rdf/3.1/terms/Build/buildId", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "build_buildStartTime": { + "@id": "https://spdx.org/rdf/3.1/terms/Build/buildStartTime", + "@type": "http://www.w3.org/2001/XMLSchema#dateTimeStamp" + }, + "build_buildType": { + "@id": "https://spdx.org/rdf/3.1/terms/Build/buildType", + "@type": "http://www.w3.org/2001/XMLSchema#anyURI" + }, + "build_configSourceDigest": { + "@id": "https://spdx.org/rdf/3.1/terms/Build/configSourceDigest", + "@type": "@vocab" + }, + "build_configSourceEntrypoint": { + "@id": "https://spdx.org/rdf/3.1/terms/Build/configSourceEntrypoint", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "build_configSourceUri": { + "@id": "https://spdx.org/rdf/3.1/terms/Build/configSourceUri", + "@type": "http://www.w3.org/2001/XMLSchema#anyURI" + }, + "build_environment": { + "@id": "https://spdx.org/rdf/3.1/terms/Build/environment", + "@type": "@vocab" + }, + "build_parameter": { + "@id": "https://spdx.org/rdf/3.1/terms/Build/parameter", + "@type": "@vocab" + }, + "builtTime": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/builtTime", + "@type": "http://www.w3.org/2001/XMLSchema#dateTimeStamp" + }, + "city": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/city", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "comment": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/comment", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "completeness": { + "@context": { + "@vocab": "https://spdx.org/rdf/3.1/terms/Core/RelationshipCompleteness/" + }, + "@id": "https://spdx.org/rdf/3.1/terms/Core/completeness", + "@type": "@vocab" + }, + "contactType": { + "@context": { + "@vocab": "https://spdx.org/rdf/3.1/terms/Core/ContactPointRelationshipType/" + }, + "@id": "https://spdx.org/rdf/3.1/terms/Core/contactType", + "@type": "@vocab" + }, + "contentType": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/contentType", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "context": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/context", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "country": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/country", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "countyCode": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/countyCode", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "created": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/created", + "@type": "http://www.w3.org/2001/XMLSchema#dateTimeStamp" + }, + "createdBy": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/createdBy", + "@type": "@vocab" + }, + "createdUsing": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/createdUsing", + "@type": "@vocab" + }, + "creationInfo": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/creationInfo", + "@type": "@vocab" + }, + "dataLicense": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/dataLicense", + "@type": "@vocab" + }, + "dataset_ConfidentialityLevelType": "https://spdx.org/rdf/3.1/terms/Dataset/ConfidentialityLevelType", + "dataset_DatasetAvailabilityType": "https://spdx.org/rdf/3.1/terms/Dataset/DatasetAvailabilityType", + "dataset_DatasetPackage": "https://spdx.org/rdf/3.1/terms/Dataset/DatasetPackage", + "dataset_DatasetType": "https://spdx.org/rdf/3.1/terms/Dataset/DatasetType", + "dataset_anonymizationMethodUsed": { + "@id": "https://spdx.org/rdf/3.1/terms/Dataset/anonymizationMethodUsed", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "dataset_confidentialityLevel": { + "@context": { + "@vocab": "https://spdx.org/rdf/3.1/terms/Dataset/ConfidentialityLevelType/" + }, + "@id": "https://spdx.org/rdf/3.1/terms/Dataset/confidentialityLevel", + "@type": "@vocab" + }, + "dataset_dataCollectionProcess": { + "@id": "https://spdx.org/rdf/3.1/terms/Dataset/dataCollectionProcess", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "dataset_dataPreprocessing": { + "@id": "https://spdx.org/rdf/3.1/terms/Dataset/dataPreprocessing", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "dataset_datasetAvailability": { + "@context": { + "@vocab": "https://spdx.org/rdf/3.1/terms/Dataset/DatasetAvailabilityType/" + }, + "@id": "https://spdx.org/rdf/3.1/terms/Dataset/datasetAvailability", + "@type": "@vocab" + }, + "dataset_datasetNoise": { + "@id": "https://spdx.org/rdf/3.1/terms/Dataset/datasetNoise", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "dataset_datasetSize": { + "@id": "https://spdx.org/rdf/3.1/terms/Dataset/datasetSize", + "@type": "http://www.w3.org/2001/XMLSchema#nonNegativeInteger" + }, + "dataset_datasetType": { + "@context": { + "@vocab": "https://spdx.org/rdf/3.1/terms/Dataset/DatasetType/" + }, + "@id": "https://spdx.org/rdf/3.1/terms/Dataset/datasetType", + "@type": "@vocab" + }, + "dataset_datasetUpdateMechanism": { + "@id": "https://spdx.org/rdf/3.1/terms/Dataset/datasetUpdateMechanism", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "dataset_hasSensitivePersonalInformation": { + "@context": { + "@vocab": "https://spdx.org/rdf/3.1/terms/Core/PresenceType/" + }, + "@id": "https://spdx.org/rdf/3.1/terms/Dataset/hasSensitivePersonalInformation", + "@type": "@vocab" + }, + "dataset_intendedUse": { + "@id": "https://spdx.org/rdf/3.1/terms/Dataset/intendedUse", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "dataset_knownBias": { + "@id": "https://spdx.org/rdf/3.1/terms/Dataset/knownBias", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "dataset_sensor": { + "@id": "https://spdx.org/rdf/3.1/terms/Dataset/sensor", + "@type": "@vocab" + }, + "definingArtifact": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/definingArtifact", + "@type": "@vocab" + }, + "definitionSource": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/definitionSource", + "@type": "@vocab" + }, + "description": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/description", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "devLifecycleStage": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/devLifecycleStage", + "@type": "https://spdx.org/rdf/3.1/terms/Core/LifecycleScopeType" + }, + "element": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/element", + "@type": "@vocab" + }, + "elementValue": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/elementValue", + "@type": "@vocab" + }, + "endIntegerRange": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/endIntegerRange", + "@type": "http://www.w3.org/2001/XMLSchema#positiveInteger" + }, + "endTime": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/endTime", + "@type": "http://www.w3.org/2001/XMLSchema#dateTimeStamp" + }, + "expandedlicensing_ConjunctiveLicenseSet": "https://spdx.org/rdf/3.1/terms/ExpandedLicensing/ConjunctiveLicenseSet", + "expandedlicensing_CustomLicense": "https://spdx.org/rdf/3.1/terms/ExpandedLicensing/CustomLicense", + "expandedlicensing_CustomLicenseAddition": "https://spdx.org/rdf/3.1/terms/ExpandedLicensing/CustomLicenseAddition", + "expandedlicensing_DisjunctiveLicenseSet": "https://spdx.org/rdf/3.1/terms/ExpandedLicensing/DisjunctiveLicenseSet", + "expandedlicensing_ExtendableLicense": "https://spdx.org/rdf/3.1/terms/ExpandedLicensing/ExtendableLicense", + "expandedlicensing_IndividualLicensingInfo": "https://spdx.org/rdf/3.1/terms/ExpandedLicensing/IndividualLicensingInfo", + "expandedlicensing_License": "https://spdx.org/rdf/3.1/terms/ExpandedLicensing/License", + "expandedlicensing_LicenseAddition": "https://spdx.org/rdf/3.1/terms/ExpandedLicensing/LicenseAddition", + "expandedlicensing_ListedLicense": "https://spdx.org/rdf/3.1/terms/ExpandedLicensing/ListedLicense", + "expandedlicensing_ListedLicenseException": "https://spdx.org/rdf/3.1/terms/ExpandedLicensing/ListedLicenseException", + "expandedlicensing_NoAssertionLicense": "https://spdx.org/rdf/3.1/terms/ExpandedLicensing/NoAssertionLicense", + "expandedlicensing_NoneLicense": "https://spdx.org/rdf/3.1/terms/ExpandedLicensing/NoneLicense", + "expandedlicensing_OrLaterOperator": "https://spdx.org/rdf/3.1/terms/ExpandedLicensing/OrLaterOperator", + "expandedlicensing_WithAdditionOperator": "https://spdx.org/rdf/3.1/terms/ExpandedLicensing/WithAdditionOperator", + "expandedlicensing_additionText": { + "@id": "https://spdx.org/rdf/3.1/terms/ExpandedLicensing/additionText", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "expandedlicensing_deprecatedVersion": { + "@id": "https://spdx.org/rdf/3.1/terms/ExpandedLicensing/deprecatedVersion", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "expandedlicensing_isDeprecatedAdditionId": { + "@id": "https://spdx.org/rdf/3.1/terms/ExpandedLicensing/isDeprecatedAdditionId", + "@type": "http://www.w3.org/2001/XMLSchema#boolean" + }, + "expandedlicensing_isDeprecatedLicenseId": { + "@id": "https://spdx.org/rdf/3.1/terms/ExpandedLicensing/isDeprecatedLicenseId", + "@type": "http://www.w3.org/2001/XMLSchema#boolean" + }, + "expandedlicensing_isFsfLibre": { + "@id": "https://spdx.org/rdf/3.1/terms/ExpandedLicensing/isFsfLibre", + "@type": "http://www.w3.org/2001/XMLSchema#boolean" + }, + "expandedlicensing_isOsiApproved": { + "@id": "https://spdx.org/rdf/3.1/terms/ExpandedLicensing/isOsiApproved", + "@type": "http://www.w3.org/2001/XMLSchema#boolean" + }, + "expandedlicensing_licenseXml": { + "@id": "https://spdx.org/rdf/3.1/terms/ExpandedLicensing/licenseXml", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "expandedlicensing_listVersionAdded": { + "@id": "https://spdx.org/rdf/3.1/terms/ExpandedLicensing/listVersionAdded", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "expandedlicensing_member": { + "@id": "https://spdx.org/rdf/3.1/terms/ExpandedLicensing/member", + "@type": "@vocab" + }, + "expandedlicensing_obsoletedBy": { + "@id": "https://spdx.org/rdf/3.1/terms/ExpandedLicensing/obsoletedBy", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "expandedlicensing_seeAlso": { + "@id": "https://spdx.org/rdf/3.1/terms/ExpandedLicensing/seeAlso", + "@type": "http://www.w3.org/2001/XMLSchema#anyURI" + }, + "expandedlicensing_standardAdditionTemplate": { + "@id": "https://spdx.org/rdf/3.1/terms/ExpandedLicensing/standardAdditionTemplate", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "expandedlicensing_standardLicenseHeader": { + "@id": "https://spdx.org/rdf/3.1/terms/ExpandedLicensing/standardLicenseHeader", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "expandedlicensing_standardLicenseTemplate": { + "@id": "https://spdx.org/rdf/3.1/terms/ExpandedLicensing/standardLicenseTemplate", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "expandedlicensing_subjectAddition": { + "@id": "https://spdx.org/rdf/3.1/terms/ExpandedLicensing/subjectAddition", + "@type": "@vocab" + }, + "expandedlicensing_subjectExtendableLicense": { + "@id": "https://spdx.org/rdf/3.1/terms/ExpandedLicensing/subjectExtendableLicense", + "@type": "@vocab" + }, + "expandedlicensing_subjectLicense": { + "@id": "https://spdx.org/rdf/3.1/terms/ExpandedLicensing/subjectLicense", + "@type": "@vocab" + }, + "extension": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/extension", + "@type": "@vocab" + }, + "extension_CdxPropertiesExtension": "https://spdx.org/rdf/3.1/terms/Extension/CdxPropertiesExtension", + "extension_CdxPropertyEntry": "https://spdx.org/rdf/3.1/terms/Extension/CdxPropertyEntry", + "extension_Extension": "https://spdx.org/rdf/3.1/terms/Extension/Extension", + "extension_cdxPropName": { + "@id": "https://spdx.org/rdf/3.1/terms/Extension/cdxPropName", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "extension_cdxPropValue": { + "@id": "https://spdx.org/rdf/3.1/terms/Extension/cdxPropValue", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "extension_cdxProperty": { + "@id": "https://spdx.org/rdf/3.1/terms/Extension/cdxProperty", + "@type": "@vocab" + }, + "externalIdentifier": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/externalIdentifier", + "@type": "@vocab" + }, + "externalIdentifierType": { + "@context": { + "@vocab": "https://spdx.org/rdf/3.1/terms/Core/ExternalIdentifierType/" + }, + "@id": "https://spdx.org/rdf/3.1/terms/Core/externalIdentifierType", + "@type": "@vocab" + }, + "externalRef": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/externalRef", + "@type": "@vocab" + }, + "externalRefType": { + "@context": { + "@vocab": "https://spdx.org/rdf/3.1/terms/Core/ExternalRefType/" + }, + "@id": "https://spdx.org/rdf/3.1/terms/Core/externalRefType", + "@type": "@vocab" + }, + "externalSpdxId": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/externalSpdxId", + "@type": "http://www.w3.org/2001/XMLSchema#anyURI" + }, + "from": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/from", + "@type": "@vocab" + }, + "functionalsafety_EvaluationResult": "https://spdx.org/rdf/3.1/terms/FunctionalSafety/EvaluationResult", + "functionalsafety_EvaluationResultType": "https://spdx.org/rdf/3.1/terms/FunctionalSafety/EvaluationResultType", + "functionalsafety_EvidenceRelationship": "https://spdx.org/rdf/3.1/terms/FunctionalSafety/EvidenceRelationship", + "functionalsafety_EvidenceType": "https://spdx.org/rdf/3.1/terms/FunctionalSafety/EvidenceType", + "functionalsafety_RequirementVerification": "https://spdx.org/rdf/3.1/terms/FunctionalSafety/RequirementVerification", + "functionalsafety_VerificationType": "https://spdx.org/rdf/3.1/terms/FunctionalSafety/VerificationType", + "functionalsafety_evaluation": { + "@id": "https://spdx.org/rdf/3.1/terms/FunctionalSafety/evaluation", + "@type": "https://spdx.org/rdf/3.1/terms/FunctionalSafety/EvaluationResultType" + }, + "functionalsafety_evaluationBasedOn": { + "@id": "https://spdx.org/rdf/3.1/terms/FunctionalSafety/evaluationBasedOn", + "@type": "@vocab" + }, + "functionalsafety_evaluationRationale": { + "@id": "https://spdx.org/rdf/3.1/terms/FunctionalSafety/evaluationRationale", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "functionalsafety_evidenceCategory": { + "@context": { + "@vocab": "https://spdx.org/rdf/3.1/terms/FunctionalSafety/EvidenceType/" + }, + "@id": "https://spdx.org/rdf/3.1/terms/FunctionalSafety/evidenceCategory", + "@type": "@vocab" + }, + "functionalsafety_evidenceUUID": { + "@id": "https://spdx.org/rdf/3.1/terms/FunctionalSafety/evidenceUUID", + "@type": "@vocab" + }, + "functionalsafety_verificationMethod": { + "@id": "https://spdx.org/rdf/3.1/terms/FunctionalSafety/verificationMethod", + "@type": "https://spdx.org/rdf/3.1/terms/FunctionalSafety/VerificationType" + }, + "functionalsafety_verificationPostcondition": { + "@id": "https://spdx.org/rdf/3.1/terms/FunctionalSafety/verificationPostcondition", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "functionalsafety_verificationPrecondition": { + "@id": "https://spdx.org/rdf/3.1/terms/FunctionalSafety/verificationPrecondition", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "functionalsafety_verificationRationale": { + "@id": "https://spdx.org/rdf/3.1/terms/FunctionalSafety/verificationRationale", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "functionalsafety_verificationUUID": { + "@id": "https://spdx.org/rdf/3.1/terms/FunctionalSafety/verificationUUID", + "@type": "@vocab" + }, + "geographicPointLocation": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/geographicPointLocation", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "hardware_BulkHardware": "https://spdx.org/rdf/3.1/terms/Hardware/BulkHardware", + "hardware_Dimensions": "https://spdx.org/rdf/3.1/terms/Hardware/Dimensions", + "hardware_Hardware": "https://spdx.org/rdf/3.1/terms/Hardware/Hardware", + "hardware_PhysicalHardware": "https://spdx.org/rdf/3.1/terms/Hardware/PhysicalHardware", + "hardware_ProductSpecification": "https://spdx.org/rdf/3.1/terms/Hardware/ProductSpecification", + "hardware_VirtualHardware": "https://spdx.org/rdf/3.1/terms/Hardware/VirtualHardware", + "hardware_VirtualHardwareModelType": "https://spdx.org/rdf/3.1/terms/Hardware/VirtualHardwareModelType", + "hardware_additionalInformation": { + "@id": "https://spdx.org/rdf/3.1/terms/Hardware/additionalInformation", + "@type": "@vocab" + }, + "hardware_additionalInformationSpecification": { + "@id": "https://spdx.org/rdf/3.1/terms/Hardware/additionalInformationSpecification", + "@type": "@vocab" + }, + "hardware_batchNumber": { + "@id": "https://spdx.org/rdf/3.1/terms/Hardware/batchNumber", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "hardware_bulkQuantity": { + "@id": "https://spdx.org/rdf/3.1/terms/Hardware/bulkQuantity", + "@type": "@vocab" + }, + "hardware_category": { + "@id": "https://spdx.org/rdf/3.1/terms/Hardware/category", + "@type": "@vocab" + }, + "hardware_centerOfMass": { + "@id": "https://spdx.org/rdf/3.1/terms/Hardware/centerOfMass", + "@type": "@vocab" + }, + "hardware_dimensions": { + "@id": "https://spdx.org/rdf/3.1/terms/Hardware/dimensions", + "@type": "@vocab" + }, + "hardware_hardwareVersion": { + "@id": "https://spdx.org/rdf/3.1/terms/Hardware/hardwareVersion", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "hardware_hazard": { + "@id": "https://spdx.org/rdf/3.1/terms/Hardware/hazard", + "@type": "@vocab" + }, + "hardware_itemVersion": { + "@id": "https://spdx.org/rdf/3.1/terms/Hardware/itemVersion", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "hardware_mass": { + "@id": "https://spdx.org/rdf/3.1/terms/Hardware/mass", + "@type": "http://www.w3.org/2001/XMLSchema#decimal" + }, + "hardware_massOfHardware": { + "@id": "https://spdx.org/rdf/3.1/terms/Hardware/massOfHardware", + "@type": "https://spdx.org/rdf/3.1/terms/Core/MeasureOfMass" + }, + "hardware_partNumber": { + "@id": "https://spdx.org/rdf/3.1/terms/Hardware/partNumber", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "hardware_productAgent": { + "@id": "https://spdx.org/rdf/3.1/terms/Hardware/productAgent", + "@type": "@vocab" + }, + "hardware_releaseDate": "https://spdx.org/rdf/3.1/terms/Hardware/releaseDate", + "hardware_serialNumber": { + "@id": "https://spdx.org/rdf/3.1/terms/Hardware/serialNumber", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "hardware_virtualHardwareModel": { + "@id": "https://spdx.org/rdf/3.1/terms/Hardware/virtualHardwareModel", + "@type": "https://spdx.org/rdf/3.1/terms/Hardware/VirtualHardwareModelType" + }, + "hardware_xAxisLength": { + "@id": "https://spdx.org/rdf/3.1/terms/Hardware/xAxisLength", + "@type": "@vocab" + }, + "hardware_yAxisLength": { + "@id": "https://spdx.org/rdf/3.1/terms/Hardware/yAxisLength", + "@type": "@vocab" + }, + "hardware_zAxisLength": { + "@id": "https://spdx.org/rdf/3.1/terms/Hardware/zAxisLength", + "@type": "@vocab" + }, + "hashValue": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/hashValue", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "headquartersLocation": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/headquartersLocation", + "@type": "@vocab" + }, + "identifier": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/identifier", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "identifierLocator": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/identifierLocator", + "@type": "http://www.w3.org/2001/XMLSchema#anyURI" + }, + "import": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/import", + "@type": "@vocab" + }, + "inLanguage": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/inLanguage", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "intendedUse": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/intendedUse", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "isoAutomationLevel": { + "@context": { + "@vocab": "https://spdx.org/rdf/3.1/terms/Core/IsoAutomationLevel/" + }, + "@id": "https://spdx.org/rdf/3.1/terms/Core/isoAutomationLevel", + "@type": "@vocab" + }, + "issuingAuthority": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/issuingAuthority", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "key": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/key", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "locationHint": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/locationHint", + "@type": "http://www.w3.org/2001/XMLSchema#anyURI" + }, + "locationTime": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/locationTime", + "@type": "http://www.w3.org/2001/XMLSchema#dateTimeStamp" + }, + "locator": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/locator", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "name": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/name", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "namespace": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/namespace", + "@type": "http://www.w3.org/2001/XMLSchema#anyURI" + }, + "namespaceMap": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/namespaceMap", + "@type": "@vocab" + }, + "operations_ExportControlClassification": "https://spdx.org/rdf/3.1/terms/Operations/ExportControlClassification", + "operations_ExportControlClassificationAssessment": "https://spdx.org/rdf/3.1/terms/Operations/ExportControlClassificationAssessment", + "operations_Project": "https://spdx.org/rdf/3.1/terms/Operations/Project", + "operations_assessedElement": { + "@id": "https://spdx.org/rdf/3.1/terms/Operations/assessedElement", + "@type": "https://spdx.org/rdf/3.1/terms/Core/Element" + }, + "operations_assessmentContext": { + "@id": "https://spdx.org/rdf/3.1/terms/Operations/assessmentContext", + "@type": "https://spdx.org/rdf/3.1/terms/Operations/Project" + }, + "operations_assessmentResult": { + "@id": "https://spdx.org/rdf/3.1/terms/Operations/assessmentResult", + "@type": "https://spdx.org/rdf/3.1/terms/Operations/ExportControlClassification" + }, + "operations_assessmentTimestamp": { + "@id": "https://spdx.org/rdf/3.1/terms/Operations/assessmentTimestamp", + "@type": "http://www.w3.org/2001/XMLSchema#dateTimeStamp" + }, + "operations_assessor": { + "@id": "https://spdx.org/rdf/3.1/terms/Operations/assessor", + "@type": "https://spdx.org/rdf/3.1/terms/Core/Agent" + }, + "operations_exportClassification": { + "@id": "https://spdx.org/rdf/3.1/terms/Operations/exportClassification", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "operations_exportControlClassificationResult": { + "@id": "https://spdx.org/rdf/3.1/terms/Operations/exportControlClassificationResult", + "@type": "https://spdx.org/rdf/3.1/terms/Operations/ExportControlClassification" + }, + "operations_exportControlSpecification": { + "@id": "https://spdx.org/rdf/3.1/terms/Operations/exportControlSpecification", + "@type": "https://spdx.org/rdf/3.1/terms/Core/Specification" + }, + "operations_exportingCountry": { + "@id": "https://spdx.org/rdf/3.1/terms/Operations/exportingCountry", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "operations_projectContract": { + "@id": "https://spdx.org/rdf/3.1/terms/Operations/projectContract", + "@type": "http://www.w3.org/2001/XMLSchema#anyURI" + }, + "operations_projectEndTime": { + "@id": "https://spdx.org/rdf/3.1/terms/Operations/projectEndTime", + "@type": "http://www.w3.org/2001/XMLSchema#dateTimeStamp" + }, + "operations_projectOwner": { + "@id": "https://spdx.org/rdf/3.1/terms/Operations/projectOwner", + "@type": "https://spdx.org/rdf/3.1/terms/Core/Agent" + }, + "operations_projectSponsor": { + "@id": "https://spdx.org/rdf/3.1/terms/Operations/projectSponsor", + "@type": "https://spdx.org/rdf/3.1/terms/Core/Agent" + }, + "operations_projectStartTime": { + "@id": "https://spdx.org/rdf/3.1/terms/Operations/projectStartTime", + "@type": "http://www.w3.org/2001/XMLSchema#dateTimeStamp" + }, + "operations_projectTitle": { + "@id": "https://spdx.org/rdf/3.1/terms/Operations/projectTitle", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "operations_weight": "https://spdx.org/rdf/3.1/terms/Operations/weight", + "originatedBy": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/originatedBy", + "@type": "@vocab" + }, + "packageVerificationCodeExcludedFile": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/packageVerificationCodeExcludedFile", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "postOfficeBoxNumber": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/postOfficeBoxNumber", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "postalCode": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/postalCode", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "postalName": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/postalName", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "prefix": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/prefix", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "processRationale": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/processRationale", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "processReadiness": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/processReadiness", + "@type": "https://spdx.org/rdf/3.1/terms/Core/ProcessReadinessType" + }, + "processVersion": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/processVersion", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "profileConformance": { + "@context": { + "@vocab": "https://spdx.org/rdf/3.1/terms/Core/ProfileIdentifierType/" + }, + "@id": "https://spdx.org/rdf/3.1/terms/Core/profileConformance", + "@type": "@vocab" + }, + "provinceStateCode": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/provinceStateCode", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "quantity": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/quantity", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "relationshipType": { + "@context": { + "@vocab": "https://spdx.org/rdf/3.1/terms/Core/RelationshipType/" + }, + "@id": "https://spdx.org/rdf/3.1/terms/Core/relationshipType", + "@type": "@vocab" + }, + "releaseTime": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/releaseTime", + "@type": "http://www.w3.org/2001/XMLSchema#dateTimeStamp" + }, + "requirementRationale": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/requirementRationale", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "requirementStatement": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/requirementStatement", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "requirementUUID": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/requirementUUID", + "@type": "https://spdx.org/rdf/3.1/terms/Core/ExternalIdentifier" + }, + "rootElement": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/rootElement", + "@type": "@vocab" + }, + "scope": { + "@context": { + "@vocab": "https://spdx.org/rdf/3.1/terms/Core/LifecycleScopeType/" + }, + "@id": "https://spdx.org/rdf/3.1/terms/Core/scope", + "@type": "@vocab" + }, + "security_CvssSeverityType": "https://spdx.org/rdf/3.1/terms/Security/CvssSeverityType", + "security_CvssV2VulnAssessmentRelationship": "https://spdx.org/rdf/3.1/terms/Security/CvssV2VulnAssessmentRelationship", + "security_CvssV3VulnAssessmentRelationship": "https://spdx.org/rdf/3.1/terms/Security/CvssV3VulnAssessmentRelationship", + "security_CvssV4VulnAssessmentRelationship": "https://spdx.org/rdf/3.1/terms/Security/CvssV4VulnAssessmentRelationship", + "security_EpssVulnAssessmentRelationship": "https://spdx.org/rdf/3.1/terms/Security/EpssVulnAssessmentRelationship", + "security_ExploitCatalogType": "https://spdx.org/rdf/3.1/terms/Security/ExploitCatalogType", + "security_ExploitCatalogVulnAssessmentRelationship": "https://spdx.org/rdf/3.1/terms/Security/ExploitCatalogVulnAssessmentRelationship", + "security_SsvcDecisionType": "https://spdx.org/rdf/3.1/terms/Security/SsvcDecisionType", + "security_SsvcVulnAssessmentRelationship": "https://spdx.org/rdf/3.1/terms/Security/SsvcVulnAssessmentRelationship", + "security_VexAffectedVulnAssessmentRelationship": "https://spdx.org/rdf/3.1/terms/Security/VexAffectedVulnAssessmentRelationship", + "security_VexFixedVulnAssessmentRelationship": "https://spdx.org/rdf/3.1/terms/Security/VexFixedVulnAssessmentRelationship", + "security_VexJustificationType": "https://spdx.org/rdf/3.1/terms/Security/VexJustificationType", + "security_VexNotAffectedVulnAssessmentRelationship": "https://spdx.org/rdf/3.1/terms/Security/VexNotAffectedVulnAssessmentRelationship", + "security_VexUnderInvestigationVulnAssessmentRelationship": "https://spdx.org/rdf/3.1/terms/Security/VexUnderInvestigationVulnAssessmentRelationship", + "security_VexVulnAssessmentRelationship": "https://spdx.org/rdf/3.1/terms/Security/VexVulnAssessmentRelationship", + "security_VulnAssessmentRelationship": "https://spdx.org/rdf/3.1/terms/Security/VulnAssessmentRelationship", + "security_Vulnerability": "https://spdx.org/rdf/3.1/terms/Security/Vulnerability", + "security_actionStatement": { + "@id": "https://spdx.org/rdf/3.1/terms/Security/actionStatement", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "security_actionStatementTime": { + "@id": "https://spdx.org/rdf/3.1/terms/Security/actionStatementTime", + "@type": "http://www.w3.org/2001/XMLSchema#dateTimeStamp" + }, + "security_assessedElement": { + "@id": "https://spdx.org/rdf/3.1/terms/Security/assessedElement", + "@type": "@vocab" + }, + "security_catalogType": { + "@context": { + "@vocab": "https://spdx.org/rdf/3.1/terms/Security/ExploitCatalogType/" + }, + "@id": "https://spdx.org/rdf/3.1/terms/Security/catalogType", + "@type": "@vocab" + }, + "security_decisionType": { + "@context": { + "@vocab": "https://spdx.org/rdf/3.1/terms/Security/SsvcDecisionType/" + }, + "@id": "https://spdx.org/rdf/3.1/terms/Security/decisionType", + "@type": "@vocab" + }, + "security_exploited": { + "@id": "https://spdx.org/rdf/3.1/terms/Security/exploited", + "@type": "http://www.w3.org/2001/XMLSchema#boolean" + }, + "security_impactStatement": { + "@id": "https://spdx.org/rdf/3.1/terms/Security/impactStatement", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "security_impactStatementTime": { + "@id": "https://spdx.org/rdf/3.1/terms/Security/impactStatementTime", + "@type": "http://www.w3.org/2001/XMLSchema#dateTimeStamp" + }, + "security_justificationType": { + "@context": { + "@vocab": "https://spdx.org/rdf/3.1/terms/Security/VexJustificationType/" + }, + "@id": "https://spdx.org/rdf/3.1/terms/Security/justificationType", + "@type": "@vocab" + }, + "security_locator": { + "@id": "https://spdx.org/rdf/3.1/terms/Security/locator", + "@type": "http://www.w3.org/2001/XMLSchema#anyURI" + }, + "security_modifiedTime": { + "@id": "https://spdx.org/rdf/3.1/terms/Security/modifiedTime", + "@type": "http://www.w3.org/2001/XMLSchema#dateTimeStamp" + }, + "security_percentile": { + "@id": "https://spdx.org/rdf/3.1/terms/Security/percentile", + "@type": "http://www.w3.org/2001/XMLSchema#decimal" + }, + "security_probability": { + "@id": "https://spdx.org/rdf/3.1/terms/Security/probability", + "@type": "http://www.w3.org/2001/XMLSchema#decimal" + }, + "security_publishedTime": { + "@id": "https://spdx.org/rdf/3.1/terms/Security/publishedTime", + "@type": "http://www.w3.org/2001/XMLSchema#dateTimeStamp" + }, + "security_score": { + "@id": "https://spdx.org/rdf/3.1/terms/Security/score", + "@type": "http://www.w3.org/2001/XMLSchema#decimal" + }, + "security_severity": { + "@context": { + "@vocab": "https://spdx.org/rdf/3.1/terms/Security/CvssSeverityType/" + }, + "@id": "https://spdx.org/rdf/3.1/terms/Security/severity", + "@type": "@vocab" + }, + "security_statusNotes": { + "@id": "https://spdx.org/rdf/3.1/terms/Security/statusNotes", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "security_vectorString": { + "@id": "https://spdx.org/rdf/3.1/terms/Security/vectorString", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "security_vexVersion": { + "@id": "https://spdx.org/rdf/3.1/terms/Security/vexVersion", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "security_withdrawnTime": { + "@id": "https://spdx.org/rdf/3.1/terms/Security/withdrawnTime", + "@type": "http://www.w3.org/2001/XMLSchema#dateTimeStamp" + }, + "service_AuthenticationProtocolType": "https://spdx.org/rdf/3.1/terms/Service/AuthenticationProtocolType", + "service_SoftwareService": "https://spdx.org/rdf/3.1/terms/Service/SoftwareService", + "service_provider": { + "@id": "https://spdx.org/rdf/3.1/terms/Service/provider", + "@type": "@vocab" + }, + "service_serverAuthenticationProtocol": { + "@context": { + "@vocab": "https://spdx.org/rdf/3.1/terms/Service/AuthenticationProtocolType/" + }, + "@id": "https://spdx.org/rdf/3.1/terms/Service/serverAuthenticationProtocol", + "@type": "@vocab" + }, + "service_serviceHostingCountry": { + "@id": "https://spdx.org/rdf/3.1/terms/Service/serviceHostingCountry", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "simplelicensing_AnyLicenseInfo": "https://spdx.org/rdf/3.1/terms/SimpleLicensing/AnyLicenseInfo", + "simplelicensing_LicenseExpression": "https://spdx.org/rdf/3.1/terms/SimpleLicensing/LicenseExpression", + "simplelicensing_SimpleLicensingText": "https://spdx.org/rdf/3.1/terms/SimpleLicensing/SimpleLicensingText", + "simplelicensing_customIdToLicense": { + "@id": "https://spdx.org/rdf/3.1/terms/SimpleLicensing/customIdToLicense", + "@type": "@vocab" + }, + "simplelicensing_customIdToUri": { + "@id": "https://spdx.org/rdf/3.1/terms/SimpleLicensing/customIdToUri", + "@type": "@vocab" + }, + "simplelicensing_licenseExpression": { + "@id": "https://spdx.org/rdf/3.1/terms/SimpleLicensing/licenseExpression", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "simplelicensing_licenseListVersion": { + "@id": "https://spdx.org/rdf/3.1/terms/SimpleLicensing/licenseListVersion", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "simplelicensing_licenseText": { + "@id": "https://spdx.org/rdf/3.1/terms/SimpleLicensing/licenseText", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "software_ContentIdentifier": "https://spdx.org/rdf/3.1/terms/Software/ContentIdentifier", + "software_ContentIdentifierType": "https://spdx.org/rdf/3.1/terms/Software/ContentIdentifierType", + "software_File": "https://spdx.org/rdf/3.1/terms/Software/File", + "software_FileKindType": "https://spdx.org/rdf/3.1/terms/Software/FileKindType", + "software_Package": "https://spdx.org/rdf/3.1/terms/Software/Package", + "software_Sbom": "https://spdx.org/rdf/3.1/terms/Software/Sbom", + "software_SbomType": "https://spdx.org/rdf/3.1/terms/Software/SbomType", + "software_Snippet": "https://spdx.org/rdf/3.1/terms/Software/Snippet", + "software_SoftwareArtifact": "https://spdx.org/rdf/3.1/terms/Software/SoftwareArtifact", + "software_SoftwarePurpose": "https://spdx.org/rdf/3.1/terms/Software/SoftwarePurpose", + "software_additionalPurpose": { + "@context": { + "@vocab": "https://spdx.org/rdf/3.1/terms/Software/SoftwarePurpose/" + }, + "@id": "https://spdx.org/rdf/3.1/terms/Software/additionalPurpose", + "@type": "@vocab" + }, + "software_artifactSize": { + "@id": "https://spdx.org/rdf/3.1/terms/Software/artifactSize", + "@type": "http://www.w3.org/2001/XMLSchema#nonNegativeInteger" + }, + "software_attributionText": { + "@id": "https://spdx.org/rdf/3.1/terms/Software/attributionText", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "software_byteRange": { + "@id": "https://spdx.org/rdf/3.1/terms/Software/byteRange", + "@type": "https://spdx.org/rdf/3.1/terms/Core/PositiveIntegerRange" + }, + "software_contentIdentifier": { + "@id": "https://spdx.org/rdf/3.1/terms/Software/contentIdentifier", + "@type": "https://spdx.org/rdf/3.1/terms/Software/ContentIdentifier" + }, + "software_contentIdentifierType": { + "@context": { + "@vocab": "https://spdx.org/rdf/3.1/terms/Software/ContentIdentifierType/" + }, + "@id": "https://spdx.org/rdf/3.1/terms/Software/contentIdentifierType", + "@type": "@vocab" + }, + "software_contentIdentifierValue": { + "@id": "https://spdx.org/rdf/3.1/terms/Software/contentIdentifierValue", + "@type": "http://www.w3.org/2001/XMLSchema#anyURI" + }, + "software_copyrightText": { + "@id": "https://spdx.org/rdf/3.1/terms/Software/copyrightText", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "software_downloadLocation": { + "@id": "https://spdx.org/rdf/3.1/terms/Software/downloadLocation", + "@type": "http://www.w3.org/2001/XMLSchema#anyURI" + }, + "software_fileKind": { + "@context": { + "@vocab": "https://spdx.org/rdf/3.1/terms/Software/FileKindType/" + }, + "@id": "https://spdx.org/rdf/3.1/terms/Software/fileKind", + "@type": "@vocab" + }, + "software_homePage": { + "@id": "https://spdx.org/rdf/3.1/terms/Software/homePage", + "@type": "http://www.w3.org/2001/XMLSchema#anyURI" + }, + "software_lineRange": { + "@id": "https://spdx.org/rdf/3.1/terms/Software/lineRange", + "@type": "https://spdx.org/rdf/3.1/terms/Core/PositiveIntegerRange" + }, + "software_packageUrl": { + "@id": "https://spdx.org/rdf/3.1/terms/Software/packageUrl", + "@type": "http://www.w3.org/2001/XMLSchema#anyURI" + }, + "software_packageVersion": { + "@id": "https://spdx.org/rdf/3.1/terms/Software/packageVersion", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "software_primaryPurpose": { + "@context": { + "@vocab": "https://spdx.org/rdf/3.1/terms/Software/SoftwarePurpose/" + }, + "@id": "https://spdx.org/rdf/3.1/terms/Software/primaryPurpose", + "@type": "@vocab" + }, + "software_sbomType": { + "@context": { + "@vocab": "https://spdx.org/rdf/3.1/terms/Software/SbomType/" + }, + "@id": "https://spdx.org/rdf/3.1/terms/Software/sbomType", + "@type": "@vocab" + }, + "software_snippetFromFile": { + "@id": "https://spdx.org/rdf/3.1/terms/Software/snippetFromFile", + "@type": "@vocab" + }, + "software_sourceInfo": { + "@id": "https://spdx.org/rdf/3.1/terms/Software/sourceInfo", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "spdx": "https://spdx.org/rdf/3.1/terms/", + "spdxId": "@id", + "specType": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/specType", + "@type": "https://spdx.org/rdf/3.1/terms/Core/SpecificationType" + }, + "specVersion": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/specVersion", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "standardName": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/standardName", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "startTime": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/startTime", + "@type": "http://www.w3.org/2001/XMLSchema#dateTimeStamp" + }, + "statement": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/statement", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "streetAddress": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/streetAddress", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "subject": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/subject", + "@type": "@vocab" + }, + "summary": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/summary", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "suppliedBy": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/suppliedBy", + "@type": "@vocab" + }, + "supplychain_AssemblyAction": "https://spdx.org/rdf/3.1/terms/SupplyChain/AssemblyAction", + "supplychain_AssemblyProcess": "https://spdx.org/rdf/3.1/terms/SupplyChain/AssemblyProcess", + "supplychain_BoundaryCrossingAction": "https://spdx.org/rdf/3.1/terms/SupplyChain/BoundaryCrossingAction", + "supplychain_BoundaryDefinitionAction": "https://spdx.org/rdf/3.1/terms/SupplyChain/BoundaryDefinitionAction", + "supplychain_BoundaryDefinitionProcess": "https://spdx.org/rdf/3.1/terms/SupplyChain/BoundaryDefinitionProcess", + "supplychain_ChangeAction": "https://spdx.org/rdf/3.1/terms/SupplyChain/ChangeAction", + "supplychain_ChangeProcess": "https://spdx.org/rdf/3.1/terms/SupplyChain/ChangeProcess", + "supplychain_CreateAction": "https://spdx.org/rdf/3.1/terms/SupplyChain/CreateAction", + "supplychain_CreateProcess": "https://spdx.org/rdf/3.1/terms/SupplyChain/CreateProcess", + "supplychain_DefinedStateProcess": "https://spdx.org/rdf/3.1/terms/SupplyChain/DefinedStateProcess", + "supplychain_DestroyAction": "https://spdx.org/rdf/3.1/terms/SupplyChain/DestroyAction", + "supplychain_DestroyProcess": "https://spdx.org/rdf/3.1/terms/SupplyChain/DestroyProcess", + "supplychain_HarvestAction": "https://spdx.org/rdf/3.1/terms/SupplyChain/HarvestAction", + "supplychain_HarvestProcess": "https://spdx.org/rdf/3.1/terms/SupplyChain/HarvestProcess", + "supplychain_InspectionAction": "https://spdx.org/rdf/3.1/terms/SupplyChain/InspectionAction", + "supplychain_InspectionProcess": "https://spdx.org/rdf/3.1/terms/SupplyChain/InspectionProcess", + "supplychain_InstantiateVirtualHardwareProcess": "https://spdx.org/rdf/3.1/terms/SupplyChain/InstantiateVirtualHardwareProcess", + "supplychain_ManufactureAction": "https://spdx.org/rdf/3.1/terms/SupplyChain/ManufactureAction", + "supplychain_ManufactureProcess": "https://spdx.org/rdf/3.1/terms/SupplyChain/ManufactureProcess", + "supplychain_ModifyAction": "https://spdx.org/rdf/3.1/terms/SupplyChain/ModifyAction", + "supplychain_ModifyProcess": "https://spdx.org/rdf/3.1/terms/SupplyChain/ModifyProcess", + "supplychain_OutOfSpecAction": "https://spdx.org/rdf/3.1/terms/SupplyChain/OutOfSpecAction", + "supplychain_PlanAction": "https://spdx.org/rdf/3.1/terms/SupplyChain/PlanAction", + "supplychain_PlanProcess": "https://spdx.org/rdf/3.1/terms/SupplyChain/PlanProcess", + "supplychain_ReproduceAction": "https://spdx.org/rdf/3.1/terms/SupplyChain/ReproduceAction", + "supplychain_ReproduceProcess": "https://spdx.org/rdf/3.1/terms/SupplyChain/ReproduceProcess", + "supplychain_ResolutionAction": "https://spdx.org/rdf/3.1/terms/SupplyChain/ResolutionAction", + "supplychain_ResponsibilityChangeAction": "https://spdx.org/rdf/3.1/terms/SupplyChain/ResponsibilityChangeAction", + "supplychain_ResponsibilityChangeProcess": "https://spdx.org/rdf/3.1/terms/SupplyChain/ResponsibilityChangeProcess", + "supplychain_ResponsibilityType": "https://spdx.org/rdf/3.1/terms/SupplyChain/ResponsibilityType", + "supplychain_State": "https://spdx.org/rdf/3.1/terms/SupplyChain/State", + "supplychain_StateAction": "https://spdx.org/rdf/3.1/terms/SupplyChain/StateAction", + "supplychain_StorageAction": "https://spdx.org/rdf/3.1/terms/SupplyChain/StorageAction", + "supplychain_StorageProcess": "https://spdx.org/rdf/3.1/terms/SupplyChain/StorageProcess", + "supplychain_TestAction": "https://spdx.org/rdf/3.1/terms/SupplyChain/TestAction", + "supplychain_TestProcess": "https://spdx.org/rdf/3.1/terms/SupplyChain/TestProcess", + "supplychain_TransportAction": "https://spdx.org/rdf/3.1/terms/SupplyChain/TransportAction", + "supplychain_TransportProcess": "https://spdx.org/rdf/3.1/terms/SupplyChain/TransportProcess", + "supplychain_UseAction": "https://spdx.org/rdf/3.1/terms/SupplyChain/UseAction", + "supplychain_UseProcess": "https://spdx.org/rdf/3.1/terms/SupplyChain/UseProcess", + "supplychain_boundaryParameter": { + "@id": "https://spdx.org/rdf/3.1/terms/SupplyChain/boundaryParameter", + "@type": "@vocab" + }, + "supplychain_current": { + "@id": "https://spdx.org/rdf/3.1/terms/SupplyChain/current", + "@type": "@vocab" + }, + "supplychain_currentState": { + "@id": "https://spdx.org/rdf/3.1/terms/SupplyChain/currentState", + "@type": "@vocab" + }, + "supplychain_decisionProcess": { + "@id": "https://spdx.org/rdf/3.1/terms/SupplyChain/decisionProcess", + "@type": "@vocab" + }, + "supplychain_destructionPerformedBy": { + "@id": "https://spdx.org/rdf/3.1/terms/SupplyChain/destructionPerformedBy", + "@type": "@vocab" + }, + "supplychain_dropoffLocation": { + "@id": "https://spdx.org/rdf/3.1/terms/SupplyChain/dropoffLocation", + "@type": "@vocab" + }, + "supplychain_forDropoffLocation": { + "@id": "https://spdx.org/rdf/3.1/terms/SupplyChain/forDropoffLocation", + "@type": "@vocab" + }, + "supplychain_forPickupLocation": { + "@id": "https://spdx.org/rdf/3.1/terms/SupplyChain/forPickupLocation", + "@type": "@vocab" + }, + "supplychain_pickupLocation": { + "@id": "https://spdx.org/rdf/3.1/terms/SupplyChain/pickupLocation", + "@type": "@vocab" + }, + "supplychain_plannedCurrent": { + "@id": "https://spdx.org/rdf/3.1/terms/SupplyChain/plannedCurrent", + "@type": "@vocab" + }, + "supplychain_plannedInspectionLocation": { + "@id": "https://spdx.org/rdf/3.1/terms/SupplyChain/plannedInspectionLocation", + "@type": "@vocab" + }, + "supplychain_plannedPrevious": { + "@id": "https://spdx.org/rdf/3.1/terms/SupplyChain/plannedPrevious", + "@type": "@vocab" + }, + "supplychain_plannedProductOfResponsibilityChange": { + "@id": "https://spdx.org/rdf/3.1/terms/SupplyChain/plannedProductOfResponsibilityChange", + "@type": "@vocab" + }, + "supplychain_plannedStorageLocation": { + "@id": "https://spdx.org/rdf/3.1/terms/SupplyChain/plannedStorageLocation", + "@type": "@vocab" + }, + "supplychain_plannedTransportRoutes": { + "@id": "https://spdx.org/rdf/3.1/terms/SupplyChain/plannedTransportRoutes", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "supplychain_previous": { + "@id": "https://spdx.org/rdf/3.1/terms/SupplyChain/previous", + "@type": "@vocab" + }, + "supplychain_responsibilityCategory": { + "@context": { + "@vocab": "https://spdx.org/rdf/3.1/terms/SupplyChain/ResponsibilityType/" + }, + "@id": "https://spdx.org/rdf/3.1/terms/SupplyChain/responsibilityCategory", + "@type": "@vocab" + }, + "supplychain_responsibilityChangedOn": { + "@id": "https://spdx.org/rdf/3.1/terms/SupplyChain/responsibilityChangedOn", + "@type": "@vocab" + }, + "supplychain_transportRoute": { + "@id": "https://spdx.org/rdf/3.1/terms/SupplyChain/transportRoute", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "supplychain_validState": { + "@id": "https://spdx.org/rdf/3.1/terms/SupplyChain/validState", + "@type": "@vocab" + }, + "supportLevel": { + "@context": { + "@vocab": "https://spdx.org/rdf/3.1/terms/Core/SupportType/" + }, + "@id": "https://spdx.org/rdf/3.1/terms/Core/supportLevel", + "@type": "@vocab" + }, + "to": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/to", + "@type": "@vocab" + }, + "type": "@type", + "typeFromSource": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/typeFromSource", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "unitQUDT": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/unitQUDT", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "validUntilTime": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/validUntilTime", + "@type": "http://www.w3.org/2001/XMLSchema#dateTimeStamp" + }, + "value": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/value", + "@type": "http://www.w3.org/2001/XMLSchema#string" + }, + "verifiedUsing": { + "@id": "https://spdx.org/rdf/3.1/terms/Core/verifiedUsing", + "@type": "@vocab" + } + } +} \ No newline at end of file diff --git a/tests/data/spdx/3.1-dev/spdx-json-serialize-annotations.ttl b/tests/data/spdx/3.1-dev/spdx-json-serialize-annotations.ttl new file mode 100644 index 00000000..311a4e18 --- /dev/null +++ b/tests/data/spdx/3.1-dev/spdx-json-serialize-annotations.ttl @@ -0,0 +1,10 @@ +@base . +@prefix sh-to-code: . + + ; + sh-to-code:idPropertyName "spdxId" + . + + ; + sh-to-code:isExtensible true + . \ No newline at end of file diff --git a/tests/data/spdx/3.1-dev/spdx-model.ttl b/tests/data/spdx/3.1-dev/spdx-model.ttl new file mode 100644 index 00000000..ed542030 --- /dev/null +++ b/tests/data/spdx/3.1-dev/spdx-model.ttl @@ -0,0 +1,5027 @@ +@prefix dcterms: . +@prefix ns1: . +@prefix ns10: . +@prefix ns2: . +@prefix ns3: . +@prefix ns4: . +@prefix ns5: . +@prefix ns6: . +@prefix ns7: . +@prefix ns8: . +@prefix ns9: . +@prefix omg-ann: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix sh: . +@prefix spdx: . +@prefix xsd: . + +ns1:NoAssertionElement a owl:NamedIndividual, + ns1:IndividualElement ; + rdfs:comment """An Individual Value for Element representing a set of Elements of unknown +identity or cardinality (number)."""@en ; + ns1:creationInfo . + +ns1:NoneElement a owl:NamedIndividual, + ns1:IndividualElement ; + rdfs:comment """An Individual Value for Element representing a set of Elements with +cardinality (number/count) of zero."""@en ; + ns1:creationInfo . + +ns9:NoAssertionLicense a owl:NamedIndividual, + ns9:IndividualLicensingInfo ; + rdfs:comment """An Individual Value for License when no assertion can be made about its actual +value."""@en ; + owl:sameAs ; + ns1:creationInfo . + +ns9:NoneLicense a owl:NamedIndividual, + ns9:IndividualLicensingInfo ; + rdfs:comment """An Individual Value for License where the SPDX data creator determines that no +license is present."""@en ; + owl:sameAs ; + ns1:creationInfo . + + a owl:Class, + sh:NodeShape ; + rdfs:comment "A type of extension consisting of a list of name value pairs."@en ; + rdfs:subClassOf ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:property [ sh:class ; + sh:minCount 1 ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ] . + +ns10:mass a owl:DatatypeProperty ; + rdfs:comment "Information related to physical hardware."@en ; + rdfs:range xsd:decimal . + + a owl:DatatypeProperty ; + rdfs:comment "Provides the result of an export control assessment."@en ; + rdfs:range . + + a ns1:CreationInfo ; + rdfs:comment "This individual element was defined by the spec."@en ; + ns1:created "2026-01-23T03:01:00Z"^^xsd:dateTimeStamp ; + ns1:createdBy ns1:SpdxOrganization ; + ns1:specVersion "3.1" . + + a ns1:CreationInfo ; + rdfs:comment "This individual element was defined by the spec."@en ; + ns1:created "2026-01-23T03:01:00Z"^^xsd:dateTimeStamp ; + ns1:createdBy ns1:SpdxOrganization ; + ns1:specVersion "3.1" . + + a ns1:CreationInfo ; + rdfs:comment "This individual element was defined by the spec."@en ; + ns1:created "2026-01-23T03:01:00Z"^^xsd:dateTimeStamp ; + ns1:createdBy ns1:SpdxOrganization ; + ns1:specVersion "3.1" . + + a ns1:CreationInfo ; + rdfs:comment "This individual element was defined by the spec."@en ; + ns1:created "2026-01-23T03:01:00Z"^^xsd:dateTimeStamp ; + ns1:createdBy ns1:SpdxOrganization ; + ns1:specVersion "3.1" . + + a ns1:CreationInfo ; + rdfs:comment "This individual element was defined by the spec."@en ; + ns1:created "2026-01-23T03:01:00Z"^^xsd:dateTimeStamp ; + ns1:createdBy ns1:SpdxOrganization ; + ns1:specVersion "3.1" . + +spdx: a owl:Ontology ; + rdfs:label "System Package Data Exchange™ (SPDX®) Ontology"@en ; + dcterms:abstract "This ontology defines the terms and relationships used in the SPDX specification to describe system packages"@en ; + dcterms:created "2026-01-23"^^xsd:date ; + dcterms:creator "SPDX Project"@en ; + dcterms:license ; + dcterms:references ; + dcterms:title "System Package Data Exchange (SPDX) Ontology"@en ; + owl:versionIRI spdx: ; + omg-ann:copyright "Copyright (C) 2026 SPDX Project"@en . + +ns5:AIPackage a owl:Class, + sh:NodeShape ; + rdfs:comment "A Package that contains AI software or an AI model."@en ; + rdfs:subClassOf ns6:Package ; + sh:nodeKind sh:IRI ; + sh:property [ sh:class ns5:SafetyRiskAssessmentType ; + sh:in ( ) ; + sh:maxCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns5:safetyRiskAssessment ], + [ sh:datatype xsd:string ; + sh:nodeKind sh:Literal ; + sh:path ns5:modelDataPreprocessing ], + [ sh:datatype xsd:string ; + sh:nodeKind sh:Literal ; + sh:path ns5:typeOfModel ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns5:informationAboutApplication ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns5:informationAboutTraining ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns5:limitation ], + [ sh:class ns1:PresenceType ; + sh:in ( ) ; + sh:maxCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns5:useSensitivePersonalInformation ], + [ sh:class ns1:DictionaryEntry ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ns5:metricDecisionThreshold ], + [ sh:class ns1:IsoAutomationLevel ; + sh:in ( ) ; + sh:maxCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns1:isoAutomationLevel ], + [ sh:class ns1:DictionaryEntry ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ns5:metric ], + [ sh:class ns1:PresenceType ; + sh:in ( ) ; + sh:maxCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns5:autonomyType ], + [ sh:class ns1:DictionaryEntry ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ns5:hyperparameter ], + [ sh:class ns5:EnergyConsumption ; + sh:maxCount 1 ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ns5:energyConsumption ], + [ sh:datatype xsd:string ; + sh:nodeKind sh:Literal ; + sh:path ns5:standardCompliance ], + [ sh:datatype xsd:string ; + sh:nodeKind sh:Literal ; + sh:path ns5:modelExplainability ], + [ sh:datatype xsd:string ; + sh:nodeKind sh:Literal ; + sh:path ns5:domain ] . + + a owl:NamedIndividual, + ns5:EnergyUnitType ; + rdfs:label "kilowattHour" ; + rdfs:comment "Kilowatt-hour."@en . + + a owl:NamedIndividual, + ns5:EnergyUnitType ; + rdfs:label "megajoule" ; + rdfs:comment "Megajoule."@en . + + a owl:NamedIndividual, + ns5:EnergyUnitType ; + rdfs:label "other" ; + rdfs:comment "Any other units of energy measurement."@en . + + a owl:NamedIndividual, + ns5:SafetyRiskAssessmentType ; + rdfs:label "high" ; + rdfs:comment "The second-highest level of risk posed by an AI system."@en . + + a owl:NamedIndividual, + ns5:SafetyRiskAssessmentType ; + rdfs:label "low" ; + rdfs:comment "Low/no risk is posed by an AI system."@en . + + a owl:NamedIndividual, + ns5:SafetyRiskAssessmentType ; + rdfs:label "medium" ; + rdfs:comment "The third-highest level of risk posed by an AI system."@en . + + a owl:NamedIndividual, + ns5:SafetyRiskAssessmentType ; + rdfs:label "serious" ; + rdfs:comment "The highest level of risk posed by an AI system."@en . + +ns5:autonomyType a owl:ObjectProperty ; + rdfs:comment """**DEPRECATED in SPDX 3.1.** +Use [/Core/isoAutomationLevel](../../Core/Properties/isoAutomationLevel.md) +instead. + +Indicates whether the system can perform a decision or action without human +involvement or guidance."""@en ; + rdfs:range ns1:PresenceType . + +ns5:domain a owl:DatatypeProperty ; + rdfs:comment "Domain in which the AI package can be used."@en ; + rdfs:range xsd:string . + +ns5:energyConsumption a owl:ObjectProperty ; + rdfs:comment "Energy consumption incurred by an AI model."@en ; + rdfs:range ns5:EnergyConsumption . + +ns5:energyQuantity a owl:DatatypeProperty ; + rdfs:comment "Energy quantity."@en ; + rdfs:range xsd:decimal . + +ns5:energyUnit a owl:ObjectProperty ; + rdfs:comment "Unit in which energy is measured."@en ; + rdfs:range ns5:EnergyUnitType . + +ns5:finetuningEnergyConsumption a owl:ObjectProperty ; + rdfs:comment """Energy consumed when finetuning the AI model that is +being used in the AI system."""@en ; + rdfs:range ns5:EnergyConsumptionDescription . + +ns5:hyperparameter a owl:ObjectProperty ; + rdfs:comment "Hyperparameter used to build the AI model contained in the AI package."@en ; + rdfs:range ns1:DictionaryEntry . + +ns5:inferenceEnergyConsumption a owl:ObjectProperty ; + rdfs:comment """Energy consumed during inference time by an AI model +that is being used in the AI system."""@en ; + rdfs:range ns5:EnergyConsumptionDescription . + +ns5:informationAboutApplication a owl:DatatypeProperty ; + rdfs:comment "Information about the AI software, not including the model description."@en ; + rdfs:range xsd:string . + +ns5:informationAboutTraining a owl:DatatypeProperty ; + rdfs:comment "Information about different steps of the training process."@en ; + rdfs:range xsd:string . + +ns5:limitation a owl:DatatypeProperty ; + rdfs:comment "Limitation of the AI software."@en ; + rdfs:range xsd:string . + +ns5:metric a owl:ObjectProperty ; + rdfs:comment "Metric used to evaluate the AI model."@en ; + rdfs:range ns1:DictionaryEntry . + +ns5:metricDecisionThreshold a owl:ObjectProperty ; + rdfs:comment """Threshold that was used for computation of a metric described in +the metric field."""@en ; + rdfs:range ns1:DictionaryEntry . + +ns5:modelDataPreprocessing a owl:DatatypeProperty ; + rdfs:comment "Preprocessing steps applied to the training data before the model training."@en ; + rdfs:range xsd:string . + +ns5:modelExplainability a owl:DatatypeProperty ; + rdfs:comment "Methods that can be used to explain the results from the AI model."@en ; + rdfs:range xsd:string . + +ns5:safetyRiskAssessment a owl:ObjectProperty ; + rdfs:comment "Results of general safety risk assessment of the AI system."@en ; + rdfs:range ns5:SafetyRiskAssessmentType . + +ns5:standardCompliance a owl:DatatypeProperty ; + rdfs:comment "Standard that an artifact is being complied with."@en ; + rdfs:range xsd:string . + +ns5:trainingEnergyConsumption a owl:ObjectProperty ; + rdfs:comment """Energy consumed when training the AI model that is +being used in the AI system."""@en ; + rdfs:range ns5:EnergyConsumptionDescription . + +ns5:typeOfModel a owl:DatatypeProperty ; + rdfs:comment "Type of the model used in the AI software."@en ; + rdfs:range xsd:string . + +ns5:useSensitivePersonalInformation a owl:ObjectProperty ; + rdfs:comment """Records if sensitive personal information is used during model training or +could be used during the inference."""@en ; + rdfs:range ns1:PresenceType . + + a owl:Class, + sh:NodeShape ; + rdfs:comment "Class that describes a build instance of software/artifacts."@en ; + rdfs:subClassOf ns1:Element ; + sh:nodeKind sh:IRI ; + sh:property [ sh:class ns1:DictionaryEntry ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ], + [ sh:class ns1:DictionaryEntry ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ], + [ sh:datatype xsd:string ; + sh:nodeKind sh:Literal ; + sh:path ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ], + [ sh:datatype xsd:anyURI ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ], + [ sh:datatype xsd:anyURI ; + sh:nodeKind sh:Literal ; + sh:path ], + [ sh:datatype xsd:dateTimeStamp ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ; + sh:pattern "^\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\dZ$" ], + [ sh:class ns1:Hash ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ], + [ sh:datatype xsd:dateTimeStamp ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ; + sh:pattern "^\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\dZ$" ] . + + a owl:DatatypeProperty ; + rdfs:comment "Property that describes the time at which a build stops."@en ; + rdfs:range xsd:dateTimeStamp . + + a owl:DatatypeProperty ; + rdfs:comment """A buildId is a locally unique identifier used by a builder to identify a unique +instance of a build produced by it."""@en ; + rdfs:range xsd:string . + + a owl:DatatypeProperty ; + rdfs:comment "Property describing the start time of a build."@en ; + rdfs:range xsd:dateTimeStamp . + + a owl:DatatypeProperty ; + rdfs:comment """A buildType is a hint that is used to indicate the toolchain, platform, or +infrastructure that the build was invoked on."""@en ; + rdfs:range xsd:anyURI . + + a owl:ObjectProperty ; + rdfs:comment """Property that describes the digest of the build configuration file used to +invoke a build."""@en ; + rdfs:range ns1:Hash . + + a owl:DatatypeProperty ; + rdfs:comment "Property describes the invocation entrypoint of a build."@en ; + rdfs:range xsd:string . + + a owl:DatatypeProperty ; + rdfs:comment "Property that describes the URI of the build configuration source file."@en ; + rdfs:range xsd:anyURI . + + a owl:ObjectProperty ; + rdfs:comment "Property describing the session in which a build is invoked."@en ; + rdfs:range ns1:DictionaryEntry . + + a owl:ObjectProperty ; + rdfs:comment "Property describing a parameter used in an instance of a build."@en ; + rdfs:range ns1:DictionaryEntry . + +ns1:Annotation a owl:Class, + sh:NodeShape ; + rdfs:comment "An assertion made in relation to one or more elements."@en ; + rdfs:subClassOf ns1:Element ; + sh:nodeKind sh:IRI ; + sh:property [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:statement ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:contentType ; + sh:pattern "^[^\\/]+\\/[^\\/]+$" ], + [ sh:class ns1:AnnotationType ; + sh:in ( ) ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns1:annotationType ], + [ sh:class ns1:Element ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns1:subject ] . + + a owl:NamedIndividual, + ns1:AnnotationType ; + rdfs:label "other" ; + rdfs:comment "Used to store extra information about an Element which is not part of a review (e.g. extra information provided during the creation of the Element)."@en . + + a owl:NamedIndividual, + ns1:AnnotationType ; + rdfs:label "review" ; + rdfs:comment "Used when someone reviews the Element."@en . + +ns1:ContactPointRelationship a owl:Class, + sh:NodeShape ; + rdfs:comment "A contact point from an Artifact to an Agent."@en ; + rdfs:subClassOf ns1:Relationship ; + sh:nodeKind sh:IRI ; + sh:property [ sh:class ns1:ContactPointRelationshipType ; + sh:in ( ) ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns1:contactType ] . + + a owl:NamedIndividual, + ns1:ContactPointRelationshipType ; + rdfs:label "compliance" ; + rdfs:comment "A contact point for compliance (i.e. export control, licensing)."@en . + + a owl:NamedIndividual, + ns1:ContactPointRelationshipType ; + rdfs:label "other" ; + rdfs:comment "A generic contact point to be used when the contact type does not match any of the other options."@en . + + a owl:NamedIndividual, + ns1:ContactPointRelationshipType ; + rdfs:label "securityVulnerability" ; + rdfs:comment "A contact for reporting security vulnerabilities."@en . + + a owl:NamedIndividual, + ns1:ContactPointRelationshipType ; + rdfs:label "support" ; + rdfs:comment "A contact point for support."@en . + + a owl:NamedIndividual, + ns1:ExternalIdentifierType ; + rdfs:label "cpe22" ; + rdfs:comment "[Common Platform Enumeration Specification 2.2](https://cpe.mitre.org/files/cpe-specification_2.2.pdf)."@en . + + a owl:NamedIndividual, + ns1:ExternalIdentifierType ; + rdfs:label "cpe23" ; + rdfs:comment "[Common Platform Enumeration: Naming Specification Version 2.3](https://csrc.nist.gov/publications/detail/nistir/7695/final)."@en . + + a owl:NamedIndividual, + ns1:ExternalIdentifierType ; + rdfs:label "cve" ; + rdfs:comment "Common Vulnerabilities and Exposures identifiers, an identifier for a specific software flaw defined within the official CVE Dictionary and that conforms to the [CVE specification](https://csrc.nist.gov/glossary/term/cve_id)."@en . + + a owl:NamedIndividual, + ns1:ExternalIdentifierType ; + rdfs:label "duns" ; + rdfs:comment "[Data Universal Numbering System (D-U-N-S) Number](https://www.dnb.com/en-us/smb/duns.html) is a unique nine-digit identifier, issued by Dun & Bradstreet, that identifies a business entity, often on a location-specific basis."@en . + + a owl:NamedIndividual, + ns1:ExternalIdentifierType ; + rdfs:label "email" ; + rdfs:comment "Email address, as defined in [RFC 3696](https://datatracker.ietf.org/doc/rfc3696/) Section 3."@en . + + a owl:NamedIndividual, + ns1:ExternalIdentifierType ; + rdfs:label "evidenceUUID" ; + rdfs:comment "The UUID used by a reporting management system or any other lifecycle management tool to uniquely identify an evidence relationship item. UUID, or universally unique ID, is a standard term to refer to evidence items."@en . + + a owl:NamedIndividual, + ns1:ExternalIdentifierType ; + rdfs:label "gitoid" ; + rdfs:comment "[Gitoid](https://www.iana.org/assignments/uri-schemes/prov/gitoid), stands for [Git Object ID](https://git-scm.com/book/en/v2/Git-Internals-Git-Objects). A gitoid of type blob is a unique hash of a binary artifact. A gitoid may represent either an [Artifact Identifier](https://github.com/omnibor/spec/blob/eb1ee5c961c16215eb8709b2975d193a2007a35d/spec/SPEC.md#artifact-identifier-types) for the software artifact or an [Input Manifest Identifier](https://github.com/omnibor/spec/blob/eb1ee5c961c16215eb8709b2975d193a2007a35d/spec/SPEC.md#input-manifest-identifier) for the software artifact's associated [Artifact Input Manifest](https://github.com/omnibor/spec/blob/eb1ee5c961c16215eb8709b2975d193a2007a35d/spec/SPEC.md#artifact-input-manifest); this ambiguity exists because the Artifact Input Manifest is itself an artifact, and the gitoid of that artifact is its valid identifier. Gitoids calculated on software artifacts (Snippet, File, or Package Elements) should be recorded in the SPDX 3 SoftwareArtifact's contentIdentifier property. Gitoids calculated on the Artifact Input Manifest (Input Manifest Identifier) should be recorded in the SPDX 3 Element's externalIdentifier property. See [OmniBOR Specification](https://github.com/omnibor/spec/), a minimalistic specification for describing software [Artifact Dependency Graphs](https://github.com/omnibor/spec/blob/eb1ee5c961c16215eb8709b2975d193a2007a35d/spec/SPEC.md#artifact-dependency-graph-adg)."@en . + + a owl:NamedIndividual, + ns1:ExternalIdentifierType ; + rdfs:label "gln" ; + rdfs:comment "[Global Location Number (GLN)](https://www.gs1.org/standards/id-keys/gln) is a 13-digit number, assigned by GS1, that uniquely identifies a legal entity (e.g., a company or customer), a function within a legal entity, a physical location (e.g., a warehouse or a specific shelf in a store), or a digital location (e.g., an Electronic Data Interchange (EDI) gateway)."@en . + + a owl:NamedIndividual, + ns1:ExternalIdentifierType ; + rdfs:label "glue" ; + rdfs:comment "[GLobal Unique Enterprise (GLUE) Identifiers](https://datatracker.ietf.org/doc/draft-ietf-spice-glue-id/), as defined by the IETF Internet-Draft, is expressed as a GLUE URI, a Uniform Resource Identifier that standardizes the representation of existing organizational entity identifiers."@en . + + a owl:NamedIndividual, + ns1:ExternalIdentifierType ; + rdfs:label "gtin" ; + rdfs:comment "[Global Trade Item Number (GTIN)](https://www.gs1.org/standards/id-keys/gtin) is a number, assigned by GS1, that uniquely identifies a trade item (product or service)."@en . + + a owl:NamedIndividual, + ns1:ExternalIdentifierType ; + rdfs:label "hsCodes" ; + rdfs:comment "The [Harmonized System (HS)](https://www.wcoomd.org/en/topics/nomenclature/overview/what-is-the-harmonized-system.aspx) of tariff nomenclature is an internationally standardized system of names and numbers, defined by the World Customs Organization, used to classify traded products."@en . + + a owl:NamedIndividual, + ns1:ExternalIdentifierType ; + rdfs:label "lei" ; + rdfs:comment "The [Legal Entity Identifier (LEI)](https://www.gleif.org/en/organizational-identity/introducing-the-legal-entity-identifier-lei) is a 20-character, alphanumeric code based on the [ISO 17442](https://www.iso.org/standard/78829.html) standard developed by the International Organization for Standardization."@en . + + a owl:NamedIndividual, + ns1:ExternalIdentifierType ; + rdfs:label "other" ; + rdfs:comment "Used when the type does not match any of the other options."@en . + + a owl:NamedIndividual, + ns1:ExternalIdentifierType ; + rdfs:label "packageUrl" ; + rdfs:comment "Package URL, as defined in the corresponding [Annex](../../../annexes/pkg-url-specification.md) of this document."@en . + + a owl:NamedIndividual, + ns1:ExternalIdentifierType ; + rdfs:label "phoneNumber" ; + rdfs:comment "Phone number; A string of decimal digits that uniquely indicates the network termination point defined in [RFC 3966](https://datatracker.ietf.org/doc/rfc3966/) Section 5."@en . + + a owl:NamedIndividual, + ns1:ExternalIdentifierType ; + rdfs:label "requirementUUID" ; + rdfs:comment "The UUID used by a requirements management or any other lifecycle management tool to uniquely identify a requirement item. UUID, or universally unique ID, is a standard term in requirements engineering."@en . + + a owl:NamedIndividual, + ns1:ExternalIdentifierType ; + rdfs:label "securityOther" ; + rdfs:comment "Used when there is a security related identifier of unspecified type."@en . + + a owl:NamedIndividual, + ns1:ExternalIdentifierType ; + rdfs:label "swhid" ; + rdfs:comment "SoftWare Hash IDentifier, a persistent intrinsic identifier for digital artifacts, such as files, trees (also known as directories or folders), commits, and other objects typically found in version control systems. The format of the identifiers is defined in the [SWHID specification](https://www.swhid.org/swhid-specification/v1.2/) ([ISO/IEC 18670](https://www.iso.org/standard/89985.html)). They typically look like `swh:1:cnt:94a9ed024d3859793618152ea559a168bbcbb5e2`."@en . + + a owl:NamedIndividual, + ns1:ExternalIdentifierType ; + rdfs:label "swid" ; + rdfs:comment "Concise Software Identification (CoSWID) tag, as defined in [RFC 9393](https://datatracker.ietf.org/doc/rfc9393/) Section 2.3."@en . + + a owl:NamedIndividual, + ns1:ExternalIdentifierType ; + rdfs:label "urlScheme" ; + rdfs:comment "[Uniform Resource Identifier (URI) Schemes](https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml). The scheme used in order to locate a resource."@en . + + a owl:NamedIndividual, + ns1:ExternalIdentifierType ; + rdfs:label "verificationUUID" ; + rdfs:comment "The UUID used by a verification management system or any other lifecycle management tool to uniquely identify a verification item. UUID, or universally unique ID, is a standard term to refer to verification items."@en . + + a owl:NamedIndividual, + ns1:ExternalIdentifierType ; + rdfs:label "webpage" ; + rdfs:comment "Absolute URL that can be used to locate a resource, as defined in [RFC 7230](https://datatracker.ietf.org/doc/rfc7230/) Section 2.7.1 or Section 2.7.2."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "altDownloadLocation" ; + rdfs:comment "A reference to an alternative download location."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "altWebPage" ; + rdfs:comment "A reference to an alternative web page."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "binaryArtifact" ; + rdfs:comment "A reference to binary artifacts related to a package."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "bower" ; + rdfs:comment "A reference to a Bower package. The package locator format, looks like `package#version`, is defined in the \"install\" section of [Bower API documentation](https://bower.io/docs/api/#install)."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "buildMeta" ; + rdfs:comment "A reference build metadata related to a published package."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "buildSystem" ; + rdfs:comment "A reference build system used to create or publish the package."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "certificationReport" ; + rdfs:comment "A reference to a certification report for a package from an accredited/independent body."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "chat" ; + rdfs:comment "A reference to the instant messaging system used by the maintainer for a package."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "componentAnalysisReport" ; + rdfs:comment "A reference to a Software Composition Analysis (SCA) report."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "cwe" ; + rdfs:comment "[Common Weakness Enumeration](https://csrc.nist.gov/glossary/term/common_weakness_enumeration). A reference to a source of software flaw defined within the official [CWE List](https://cwe.mitre.org/data/) that conforms to the [CWE specification](https://cwe.mitre.org/)."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "documentation" ; + rdfs:comment "A reference to the documentation for a package."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "dynamicAnalysisReport" ; + rdfs:comment "A reference to a dynamic analysis report for a package."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "eolNotice" ; + rdfs:comment "A reference to the End Of Sale (EOS) and/or End Of Life (EOL) information related to a package."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "exportControlAssessment" ; + rdfs:comment "A reference to an export control assessment for a package."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "funding" ; + rdfs:comment "A reference to funding information related to a package."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "issueTracker" ; + rdfs:comment "A reference to the issue tracker for a package."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "license" ; + rdfs:comment "A reference to additional license information related to an artifact."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "mailingList" ; + rdfs:comment "A reference to the mailing list used by the maintainer for a package."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "mavenCentral" ; + rdfs:comment "A reference to a Maven repository artifact. The artifact locator format is defined in the [Maven documentation](https://maven.apache.org/guides/mini/guide-naming-conventions.html) and looks like `groupId:artifactId[:version]`."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "metrics" ; + rdfs:comment "A reference to metrics related to package such as OpenSSF scorecards."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "npm" ; + rdfs:comment "A reference to an npm package. The package locator format is defined in the [npm documentation](https://docs.npmjs.com/cli/v10/configuring-npm/package-json) and looks like `package@version`."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "nuget" ; + rdfs:comment "A reference to a NuGet package. The package locator format is defined in the [NuGet documentation](https://docs.nuget.org) and looks like `package/version`."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "other" ; + rdfs:comment "Used when the type does not match any of the other options."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "privacyAssessment" ; + rdfs:comment "A reference to a privacy assessment for a package."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "productMetadata" ; + rdfs:comment "A reference to additional product metadata such as reference within organization's product catalog."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "purchaseOrder" ; + rdfs:comment "A reference to a purchase order for a package."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "qualityAssessmentReport" ; + rdfs:comment "A reference to a quality assessment for a package."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "releaseHistory" ; + rdfs:comment "A reference to a published list of releases for a package."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "releaseNotes" ; + rdfs:comment "A reference to the release notes for a package."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "riskAssessment" ; + rdfs:comment "A reference to a risk assessment for a package."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "runtimeAnalysisReport" ; + rdfs:comment "A reference to a runtime analysis report for a package."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "secureSoftwareAttestation" ; + rdfs:comment "A reference to information assuring that the software is developed using security practices as defined by [NIST SP 800-218 Secure Software Development Framework (SSDF) Version 1.1](https://csrc.nist.gov/pubs/sp/800/218/final) or [CISA Secure Software Development Attestation Form](https://www.cisa.gov/resources-tools/resources/secure-software-development-attestation-form)."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "securityAdversaryModel" ; + rdfs:comment "A reference to the security adversary model for a package."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "securityAdvisory" ; + rdfs:comment "A reference to a published security advisory (where advisory as defined per [ISO 29147:2018](https://www.iso.org/standard/72311.html)) that may affect one or more elements, e.g., vendor advisories or specific NVD entries."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "securityFix" ; + rdfs:comment "A reference to the patch or source code that fixes a vulnerability."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "securityOther" ; + rdfs:comment "A reference to related security information of unspecified type."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "securityPenTestReport" ; + rdfs:comment "A reference to a [penetration test](https://en.wikipedia.org/wiki/Penetration_test) report for a package."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "securityPolicy" ; + rdfs:comment "A reference to instructions for reporting newly discovered security vulnerabilities for a package."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "securityThreatModel" ; + rdfs:comment "A reference the [security threat model](https://en.wikipedia.org/wiki/Threat_model) for a package."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "socialMedia" ; + rdfs:comment "A reference to a social media channel for a package."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "sourceArtifact" ; + rdfs:comment "A reference to an artifact containing the sources for a package."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "staticAnalysisReport" ; + rdfs:comment "A reference to a static analysis report for a package."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "support" ; + rdfs:comment "A reference to the software support channel or other support information for a package."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "vcs" ; + rdfs:comment "A reference to a version control system related to a software artifact."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "vulnerabilityDisclosureReport" ; + rdfs:comment "A reference to a Vulnerability Disclosure Report (VDR) which provides the software supplier's analysis and findings describing the impact (or lack of impact) that reported vulnerabilities have on packages or products in the supplier's SBOM as defined in [NIST SP 800-161 Cybersecurity Supply Chain Risk Management Practices for Systems and Organizations](https://csrc.nist.gov/pubs/sp/800/161/r1/final)."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "vulnerabilityExploitabilityAssessment" ; + rdfs:comment "A reference to a Vulnerability Exploitability eXchange (VEX) statement which provides information on whether a product is impacted by a specific vulnerability in an included package and, if affected, whether there are actions recommended to remediate. See also [NTIA VEX one-page summary](https://ntia.gov/files/ntia/publications/vex_one-page_summary.pdf)."@en . + + a owl:NamedIndividual, + ns1:ExternalRefType ; + rdfs:label "x509Cert" ; + rdfs:comment "A reference to an X.509 certificate as defined in [RFC 1422](https://datatracker.ietf.org/doc/rfc1422/). The media type shall be one of application/x-x509-ca-cert or application/x-x509-user-cert."@en . + + a owl:NamedIndividual, + ns1:IsoAutomationLevel ; + rdfs:label "assistiveAutomation" ; + rdfs:comment "Level 1 - Assistive automation. The system assists an operator."@en . + + a owl:NamedIndividual, + ns1:IsoAutomationLevel ; + rdfs:label "autonomous" ; + rdfs:comment "Level 6 - Autonomous. The system is capable of modifying its intended domain of use or its goals without external intervention, control or oversight."@en . + + a owl:NamedIndividual, + ns1:IsoAutomationLevel ; + rdfs:label "conditionalAutomation" ; + rdfs:comment "Level 3 - Conditional automation. The system can propose strategies and then automatically execute the approved plan, with an external agent being ready to take over when necessary."@en . + + a owl:NamedIndividual, + ns1:IsoAutomationLevel ; + rdfs:label "fullAutomation" ; + rdfs:comment "Level 5 - Full automation. The system is capable of performing its entire mission without external intervention."@en . + + a owl:NamedIndividual, + ns1:IsoAutomationLevel ; + rdfs:label "highAutomation" ; + rdfs:comment "Level 4 - High automation. The system performs parts of its mission without external intervention."@en . + + a owl:NamedIndividual, + ns1:IsoAutomationLevel ; + rdfs:label "notAutomated" ; + rdfs:comment "Level 0 - Not automated. No automation. The operator fully controls the system."@en . + + a owl:NamedIndividual, + ns1:IsoAutomationLevel ; + rdfs:label "partialAutomation" ; + rdfs:comment "Level 2 - Partial automation or task automation. Some sub-functions of the system are fully automated while the system remain under control of an external agent. The system can perform actions for an approved task without requiring the agent's continuous direct control."@en . + +ns1:LifecycleScopedRelationship a owl:Class, + sh:NodeShape ; + rdfs:comment "Provide context for a relationship that occurs in the lifecycle."@en ; + rdfs:subClassOf ns1:Relationship ; + sh:nodeKind sh:IRI ; + sh:property [ sh:class ns1:LifecycleScopeType ; + sh:in ( ) ; + sh:maxCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns1:scope ] . + +ns1:PackageVerificationCode a owl:Class, + sh:NodeShape ; + rdfs:comment "An SPDX version 2.X compatible verification method for software packages."@en ; + rdfs:subClassOf ns1:IntegrityMethod ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:property [ sh:datatype xsd:string ; + sh:nodeKind sh:Literal ; + sh:path ns1:packageVerificationCodeExcludedFile ], + [ sh:class ns1:HashAlgorithm ; + sh:in ( ) ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns1:algorithm ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:hashValue ] . + +ns1:Person a owl:Class ; + rdfs:comment "An individual human being."@en ; + rdfs:subClassOf ns1:Agent ; + sh:nodeKind sh:IRI . + +ns1:PhysicalLocation a owl:Class, + sh:NodeShape ; + rdfs:comment "A physical location is a tangible, geographically identifiable place where objects, people, or assets exist or operate."@en ; + rdfs:subClassOf ns1:Location ; + sh:nodeKind sh:IRI ; + sh:property [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:city ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:countyCode ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:postalName ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:provinceStateCode ], + [ sh:datatype xsd:string ; + sh:nodeKind sh:Literal ; + sh:path ns1:geographicPointLocation ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:country ; + sh:pattern "^[A-Z]{3}$" ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:postOfficeBoxNumber ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:streetAddress ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:postalCode ] . + + a owl:NamedIndividual, + ns1:ProcessReadinessType ; + rdfs:label "active" ; + rdfs:comment "in use"@en . + + a owl:NamedIndividual, + ns1:ProcessReadinessType ; + rdfs:label "draft" ; + rdfs:comment "in production"@en . + + a owl:NamedIndividual, + ns1:ProcessReadinessType ; + rdfs:label "obsolete" ; + rdfs:comment "superseded or not valid at present"@en . + + a owl:NamedIndividual, + ns1:ProcessReadinessType ; + rdfs:label "other" ; + rdfs:comment "other"@en . + + a owl:NamedIndividual, + ns1:ProfileIdentifierType ; + rdfs:label "ai" ; + rdfs:comment "The element follows the AI profile specification."@en . + + a owl:NamedIndividual, + ns1:ProfileIdentifierType ; + rdfs:label "build" ; + rdfs:comment "The element follows the Build profile specification."@en . + + a owl:NamedIndividual, + ns1:ProfileIdentifierType ; + rdfs:label "core" ; + rdfs:comment "The element follows the Core profile specification."@en . + + a owl:NamedIndividual, + ns1:ProfileIdentifierType ; + rdfs:label "dataset" ; + rdfs:comment "The element follows the Dataset profile specification."@en . + + a owl:NamedIndividual, + ns1:ProfileIdentifierType ; + rdfs:label "expandedLicensing" ; + rdfs:comment "The element follows the ExpandedLicensing profile specification."@en . + + a owl:NamedIndividual, + ns1:ProfileIdentifierType ; + rdfs:label "extension" ; + rdfs:comment "The element follows the Extension profile specification."@en . + + a owl:NamedIndividual, + ns1:ProfileIdentifierType ; + rdfs:label "hardware" ; + rdfs:comment "The element follows the Hardware profile specification."@en . + + a owl:NamedIndividual, + ns1:ProfileIdentifierType ; + rdfs:label "lite" ; + rdfs:comment "The element follows the Lite profile specification."@en . + + a owl:NamedIndividual, + ns1:ProfileIdentifierType ; + rdfs:label "security" ; + rdfs:comment "The element follows the Security profile specification."@en . + + a owl:NamedIndividual, + ns1:ProfileIdentifierType ; + rdfs:label "simpleLicensing" ; + rdfs:comment "The element follows the SimpleLicensing profile specification."@en . + + a owl:NamedIndividual, + ns1:ProfileIdentifierType ; + rdfs:label "software" ; + rdfs:comment "The element follows the Software profile specification."@en . + + a owl:NamedIndividual, + ns1:ProfileIdentifierType ; + rdfs:label "supplyChain" ; + rdfs:comment "The element follows the SupplyChain profile specification."@en . + +ns1:Regulation a owl:Class ; + rdfs:comment "Regulation represents a rule or directive maintained by an authority."@en ; + rdfs:subClassOf ns1:Specification ; + sh:nodeKind sh:IRI . + + a owl:NamedIndividual, + ns1:RelationshipCompleteness ; + rdfs:label "complete" ; + rdfs:comment "The relationship is known to be exhaustive."@en . + + a owl:NamedIndividual, + ns1:RelationshipCompleteness ; + rdfs:label "incomplete" ; + rdfs:comment "The relationship is known not to be exhaustive."@en . + + a owl:NamedIndividual, + ns1:RelationshipCompleteness ; + rdfs:label "noAssertion" ; + rdfs:comment "No assertion can be made about the completeness of the relationship."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "affects" ; + rdfs:comment "The `from` Vulnerability, Action or DefinedProcess affects each `to` Element."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "amendedBy" ; + rdfs:comment "The `from` Element is amended by each `to` Element."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "ancestorOf" ; + rdfs:comment "The `from` Element is an ancestor of each `to` Element."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "availableFrom" ; + rdfs:comment "The `from` Element is available from the additional supplier described by each `to` Element."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "configures" ; + rdfs:comment "The `from` Element is a configuration applied to each `to` Element, during a LifecycleScopeType period."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "conformsTo" ; + rdfs:comment "The `from` Element conforms to each `to` Specification."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "contains" ; + rdfs:comment "The `from` Element contains each `to` Element."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "coordinatedBy" ; + rdfs:comment "The `from` Vulnerability is coordinatedBy the `to` Agent(s) (vendor, researcher, or consumer agent)."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "copiedTo" ; + rdfs:comment "The `from` Element has been copied to each `to` Element."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "createdBy" ; + rdfs:comment "The `from` Element's Action or DefinedProcess is createdBy `to` Agent(s)."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "delegatedTo" ; + rdfs:comment "The `from` Agent is delegating an action to the Agent of the `to` Relationship (which shall be of type invokedBy), during a LifecycleScopeType (e.g. the `to` invokedBy Relationship is being done on behalf of `from`)."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "dependsOn" ; + rdfs:comment "The `from` Element depends on each `to` Element, during a LifecycleScopeType period."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "descendantOf" ; + rdfs:comment "The `from` Element is a descendant of each `to` Element."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "describes" ; + rdfs:comment "The `from` Element describes each `to` Element. To denote the root(s) of a tree of elements in a collection, the rootElement property shall be used."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "doesNotAffect" ; + rdfs:comment "The `from` Vulnerability has no impact on each `to` Element. The use of the `doesNotAffect` is constrained to `VexNotAffectedVulnAssessmentRelationship` classed relationships."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "evaluatedOn" ; + rdfs:comment "The `from` Element has been evaluated on the `to` Element(s)."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "expandsTo" ; + rdfs:comment "The `from` Element expands out as an artifact described by each `to` Element."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "exploitCreatedBy" ; + rdfs:comment "The `from` Vulnerability has had an exploit created against it by each `to` Agent."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "finetunedOn" ; + rdfs:comment "The `from` Element has been finetuned on the `to` Element(s)."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "fixedBy" ; + rdfs:comment "Designates a `from` Vulnerability has been fixed by the `to` Agent(s)."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "fixedIn" ; + rdfs:comment "A `from` Vulnerability has been fixed in each `to` Element. The use of the `fixedIn` type is constrained to `VexFixedVulnAssessmentRelationship` classed relationships."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "follows" ; + rdfs:comment "The `to` Element succeeds the `from` Element, establishing a unidirectional sequence. This succession is defined as chronological, procedural, or logical. It is used to represent either a temporal order (e.g., in a workflow) or a logical order for processing and traversal (e.g., in an ordered list)."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "foundBy" ; + rdfs:comment "Designates a `from` Vulnerability was originally discovered by the `to` Agent(s)."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "generates" ; + rdfs:comment "The `from` Element generates each `to` Element."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "hasAddedFile" ; + rdfs:comment "Every `to` Element is a file added to the `from` Element (`from` hasAddedFile `to`)."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "hasAssessmentFor" ; + rdfs:comment "Relates a `from` Vulnerability and each `to` Element with a security assessment. To be used with `VulnAssessmentRelationship` types."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "hasAssociatedVulnerability" ; + rdfs:comment "Used to associate a `from` Artifact with each `to` Vulnerability."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "hasConcludedLicense" ; + rdfs:comment "The `from` SoftwareArtifact is concluded by the SPDX data creator to be governed by each `to` AnyLicenseInfo."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "hasContactPoint" ; + rdfs:comment "The `from` Artifact has each `to` Agent as a contact point. The use of `hasContactPoint` type is constrained to `ContactPointRelationship` typed relationships. The type of contact (i.e. security) may be specified using a `ContactPointRelationship` element."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "hasDataFile" ; + rdfs:comment "The `from` Element treats each `to` Element as a data file. A data file is an artifact that stores data required or optional for the `from` Element's functionality. A data file can be a database file, an index file, a log file, an AI model file, a calibration data file, a temporary file, a backup file, and more. For AI training dataset, test dataset, test artifact, configuration data, build input data, and build output data, please consider using the more specific relationship types: `trainedOn`, `testedOn`, `hasTest`, `configures`, `hasInput`, and `hasOutput`, respectively. This relationship does not imply dependency."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "hasDeclaredLicense" ; + rdfs:comment "The `from` SoftwareArtifact was discovered to actually contain each `to` AnyLicenseInfo (for example, as detected by automated tooling)."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "hasDeletedFile" ; + rdfs:comment "Every `to` Element is a file deleted from the `from` Element (`from` hasDeletedFile `to`)."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "hasDependencyManifest" ; + rdfs:comment "The `from` Element has manifest files that contain dependency information in each `to` Element."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "hasDistributionArtifact" ; + rdfs:comment "The `from` Element is distributed as an artifact in each `to` Element (e.g. an RPM or archive file)."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "hasDocumentation" ; + rdfs:comment "The `from` Element is documented by each `to` Element."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "hasDynamicLink" ; + rdfs:comment "The `from` Element dynamically links in each `to` Element, during a LifecycleScopeType period."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "hasEvidence" ; + rdfs:comment "Every `to` Element is considered as evidence for the `from` Element (`from` hasEvidence `to`)."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "hasExample" ; + rdfs:comment "Every `to` Element is an example for the `from` Element (`from` hasExample `to`)."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "hasHost" ; + rdfs:comment "The `from` Build was run on the `to` Element during a LifecycleScopeType period (e.g. the host that the build runs on)."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "hasInput" ; + rdfs:comment "The `from` Build, DefinedProcess or Action element has each `to` Element as an input."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "hasMetadata" ; + rdfs:comment "Every `to` Element is metadata about the `from` Element (`from` hasMetadata `to`)."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "hasOptionalComponent" ; + rdfs:comment "Every `to` Element is an optional component of the `from` Element (`from` hasOptionalComponent `to`)."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "hasOptionalDependency" ; + rdfs:comment "The `from` Element optionally depends on each `to` Element, during a LifecycleScopeType period."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "hasOutput" ; + rdfs:comment "The `from` Build, DefinedProcess or Action element generates each `to` Element as an output."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "hasPrerequisite" ; + rdfs:comment "The `from` Element has a prerequisite on each `to` Element, during a LifecycleScopeType period."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "hasProvidedDependency" ; + rdfs:comment "The `from` Element has a dependency on each `to` Element, dependency is not in the distributed artifact, but assumed to be provided, during a LifecycleScopeType period."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "hasRequirement" ; + rdfs:comment "The `from` Element has a requirement on each `to` Element, during a LifecycleScopeType period."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "hasResolution" ; + rdfs:comment "The `from` ResolutionAction point to the `to` OutOfSpecAction that is addressed."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "hasSpecification" ; + rdfs:comment "Every `to` Element is a specification for the `from` Element (`from` hasSpecification `to`), during a LifecycleScopeType period."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "hasStaticLink" ; + rdfs:comment "The `from` Element statically links in each `to` Element, during a LifecycleScopeType period."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "hasTest" ; + rdfs:comment "Every `to` Element is a test artifact for the `from` Element (`from` hasTest `to`), during a LifecycleScopeType period."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "hasTestCase" ; + rdfs:comment "Every `to` Element is a test case for the `from` Element (`from` hasTestCase `to`)."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "hasVariant" ; + rdfs:comment "Every `to` Element is a variant the `from` Element (`from` hasVariant `to`)."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "implementedBy" ; + rdfs:comment "The `from` Requirement is implemented in the `to` Element(s)."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "invokedBy" ; + rdfs:comment "The `from` Element was invoked by the `to` Agent, during a LifecycleScopeType period (for example, a Build element that describes a build step)."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "locatedAt" ; + rdfs:comment "`from` element located at a specific `to` location. A time period is optional."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "modifiedBy" ; + rdfs:comment "The `from` Element is modified by each `to` Element."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "other" ; + rdfs:comment "Every `to` Element is related to the `from` Element where the relationship type is not described by any of the SPDX relationship types (this relationship is directionless)."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "packagedBy" ; + rdfs:comment "Every `to` Element is a packaged instance of the `from` Element (`from` packagedBy `to`)."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "patchedBy" ; + rdfs:comment "Every `to` Element is a patch for the `from` Element (`from` patchedBy `to`)."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "performedBy" ; + rdfs:comment "Every `from` action is performedBy `to` Agent."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "pretrainedOn" ; + rdfs:comment "The `from` Element has been pretrained on the `to` Element(s)."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "providesSupportFor" ; + rdfs:comment "The `from` Agent provides support for each `to` Artifact. Shall be a `SupportRelationship` type."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "publishedBy" ; + rdfs:comment "Designates a `from` Vulnerability was made available for public use or reference by each `to` Agent."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "reportedBy" ; + rdfs:comment "Designates a `from` Vulnerability was first reported to a project, vendor, or tracking database for formal identification by each `to` Agent."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "republishedBy" ; + rdfs:comment "Designates a `from` Vulnerability's details were tracked, aggregated, and/or enriched to improve context (i.e. NVD) by each `to` Agent."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "resolved" ; + rdfs:comment "The `to` OutOfSpecAction is resolved in the `from` ResolutionAction."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "runsOn" ; + rdfs:comment "The `from` Element (the instructions) of runs on each `to` Hardware (processing element), during a LifecycleScopeType period."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "serializedInArtifact" ; + rdfs:comment "The `from` SpdxDocument can be found in a serialized form in each `to` Artifact."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "testedOn" ; + rdfs:comment "The `from` Element has been tested on the `to` Element(s)."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "tracedToDetail" ; + rdfs:comment "the `from` Requirement is refined and further elaborated by each `to` Requirement, which contains more detailed implementation information."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "trainedOn" ; + rdfs:comment "The `from` Element has been trained on the `to` Element(s)."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "underInvestigationFor" ; + rdfs:comment "The `from` Vulnerability impact is being investigated for each `to` Element. The use of the `underInvestigationFor` type is constrained to `VexUnderInvestigationVulnAssessmentRelationship` classed relationships."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "usesTool" ; + rdfs:comment "The `from` Element uses each `to` Element as a tool, during a LifecycleScopeType period."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "validatedOn" ; + rdfs:comment "The `from` Element has been validated on the `to` Element(s)."@en . + + a owl:NamedIndividual, + ns1:RelationshipType ; + rdfs:label "verifiedBy" ; + rdfs:comment "The `from` Requirement that has verification (test, review, analysis etc.) details defined in the `to` RequirementVerification."@en . + +ns1:Requirement a owl:Class, + sh:NodeShape ; + rdfs:comment "A distinct unit representing a requirement, as used in systems, software, and hardware engineering."@en ; + rdfs:subClassOf ns1:Element ; + sh:nodeKind sh:IRI ; + sh:property [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:requirementStatement ], + [ sh:class ns1:LifecycleScopeType ; + sh:in ( ) ; + sh:nodeKind sh:IRI ; + sh:path ns1:devLifecycleStage ], + [ sh:class ns1:ExternalIdentifier ; + sh:maxCount 1 ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ns1:requirementUUID ], + [ sh:datatype xsd:string ; + sh:nodeKind sh:Literal ; + sh:path ns1:requirementRationale ] . + +ns1:SoftwareAgent a owl:Class ; + rdfs:comment "A software agent."@en ; + rdfs:subClassOf ns1:Agent ; + sh:nodeKind sh:IRI . + +ns1:SpdxDocument a owl:Class, + sh:NodeShape ; + rdfs:comment "A collection of SPDX Elements that could potentially be serialized."@en ; + rdfs:subClassOf ns1:ElementCollection ; + sh:nodeKind sh:IRI ; + sh:property [ sh:class ; + sh:maxCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns1:dataLicense ], + [ sh:class ns1:NamespaceMap ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ns1:namespaceMap ], + [ sh:class ns1:ExternalMap ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ns1:import ] . + + a owl:NamedIndividual, + ns1:SpecificationType ; + rdfs:label "formalStandard" ; + rdfs:comment "A formal standard is a standard ratified by a recognized standards-development organization and published as a normative reference."@en . + + a owl:NamedIndividual, + ns1:SpecificationType ; + rdfs:label "other" ; + rdfs:comment "Any specification that does not fall under any of the other entries."@en . + + a owl:NamedIndividual, + ns1:SpecificationType ; + rdfs:label "regulation" ; + rdfs:comment "A mandatory legal specification issued by a governmental or regulatory authority. Compliance is enforceable by law."@en . + + a owl:NamedIndividual, + ns1:SpecificationType ; + rdfs:label "specification" ; + rdfs:comment "A specification is a detailed document (or set of documents) that describes the requirements, design, behavior, or other characteristics of a system, component, or process so that all stakeholders have a clear, unambiguous reference."@en . + +ns1:SupportRelationship a owl:Class, + sh:NodeShape ; + rdfs:comment "Describes how an Agent provides support for an Artifact."@en ; + rdfs:subClassOf ns1:Relationship ; + sh:nodeKind sh:IRI ; + sh:property [ sh:class ns1:SupportType ; + sh:in ( ) ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns1:supportLevel ] . + +ns1:actionEndTime a owl:DatatypeProperty ; + rdfs:comment "Property that describes the time at which an action stops."@en ; + rdfs:range xsd:dateTimeStamp . + +ns1:actionLocation a owl:ObjectProperty ; + rdfs:comment "Location of a specific action."@en ; + rdfs:range ns1:Location . + +ns1:actionStartTime a owl:DatatypeProperty ; + rdfs:comment "Property describing the start time of an action."@en ; + rdfs:range xsd:dateTimeStamp . + +ns1:additionalInformation a owl:ObjectProperty ; + rdfs:comment "Additional relevance information."@en ; + rdfs:range ns1:DictionaryEntry . + +ns1:annotationType a owl:ObjectProperty ; + rdfs:comment "Describes the type of annotation."@en ; + rdfs:range ns1:AnnotationType . + +ns1:beginIntegerRange a owl:DatatypeProperty ; + rdfs:comment "Defines the beginning of a range."@en ; + rdfs:range xsd:positiveInteger . + +ns1:builtTime a owl:DatatypeProperty ; + rdfs:comment "Specifies the time an artifact was built."@en ; + rdfs:range xsd:dateTimeStamp . + +ns1:city a owl:DatatypeProperty ; + rdfs:comment "City is a specific name used to define a locality."@en ; + rdfs:range xsd:string . + +ns1:completeness a owl:ObjectProperty ; + rdfs:comment "Provides information about the completeness of relationships."@en ; + rdfs:range ns1:RelationshipCompleteness . + +ns1:contactType a owl:ObjectProperty ; + rdfs:comment "Identifies the nature of the contactPointRelationship."@en ; + rdfs:range ns1:ContactPointRelationshipType . + +ns1:context a owl:DatatypeProperty ; + rdfs:comment """Gives information about the circumstances or unifying properties +that Elements of the bundle have been assembled under."""@en ; + rdfs:range xsd:string . + +ns1:country a owl:DatatypeProperty ; + rdfs:comment "Specifies a country code of the location."@en ; + rdfs:range xsd:string . + +ns1:countyCode a owl:DatatypeProperty ; + rdfs:comment "A code that identifies a county."@en ; + rdfs:range xsd:string . + +ns1:created a owl:DatatypeProperty ; + rdfs:comment "Identifies when the Element was originally created."@en ; + rdfs:range xsd:dateTimeStamp . + +ns1:createdBy a owl:ObjectProperty ; + rdfs:comment "Identifies who or what created the Element."@en ; + rdfs:range ns1:Agent . + +ns1:createdUsing a owl:ObjectProperty ; + rdfs:comment "Identifies the tooling that was used during the creation of the Element."@en ; + rdfs:range ns1:Tool . + +ns1:creationInfo a owl:ObjectProperty ; + rdfs:comment "Provides information about the creation of the Element."@en ; + rdfs:range ns1:CreationInfo . + +ns1:dataLicense a owl:ObjectProperty ; + rdfs:comment """Provides the license under which the SPDX documentation of the Element can be +used."""@en ; + rdfs:range . + +ns1:definingArtifact a owl:ObjectProperty ; + rdfs:comment """Artifact representing a serialization instance of SPDX data containing the +definition of a particular Element."""@en ; + rdfs:range ns1:Artifact . + +ns1:definitionSource a owl:ObjectProperty ; + rdfs:comment "It is the authoritative or credible entity, document, or body of knowledge that provides the meaning of a type, ensuring accuracy, context, and standardization."@en ; + rdfs:range ns1:Specification . + +ns1:description a owl:DatatypeProperty ; + rdfs:comment "Provides a detailed description of the Element."@en ; + rdfs:range xsd:string . + +ns1:devLifecycleStage a owl:DatatypeProperty ; + rdfs:comment "The product lifecycle phase, the requirement is applicable for."@en ; + rdfs:range ns1:LifecycleScopeType . + +ns1:element a owl:ObjectProperty ; + rdfs:comment "Refers to one or more Elements that are part of an ElementCollection."@en ; + rdfs:range ns1:Element . + +ns1:elementValue a owl:ObjectProperty ; + rdfs:comment "A value used in a key-value pair with a generic key that refers to an Element"@en ; + rdfs:range ns1:Element . + +ns1:endIntegerRange a owl:DatatypeProperty ; + rdfs:comment "Defines the end of a range."@en ; + rdfs:range xsd:positiveInteger . + +ns1:endTime a owl:DatatypeProperty ; + rdfs:comment "Specifies the time from which an element is no longer applicable / valid."@en ; + rdfs:range xsd:dateTimeStamp . + +ns1:externalIdentifier a owl:ObjectProperty ; + rdfs:comment """Provides a reference to a resource outside the scope of SPDX 3 content +that uniquely identifies an Element."""@en ; + rdfs:range ns1:ExternalIdentifier . + +ns1:externalIdentifierType a owl:ObjectProperty ; + rdfs:comment "Specifies the type of the external identifier."@en ; + rdfs:range ns1:ExternalIdentifierType . + +ns1:externalRef a owl:ObjectProperty ; + rdfs:comment """Points to a resource outside the scope of the SPDX 3 content +that provides additional characteristics of an Element."""@en ; + rdfs:range ns1:ExternalRef . + +ns1:externalRefType a owl:ObjectProperty ; + rdfs:comment "Specifies the type of the external reference."@en ; + rdfs:range ns1:ExternalRefType . + +ns1:externalSpdxId a owl:DatatypeProperty ; + rdfs:comment """Identifies an external Element used within an SpdxDocument but defined +external to that SpdxDocument."""@en ; + rdfs:range xsd:anyURI . + +ns1:from a owl:ObjectProperty ; + rdfs:comment "References the Element on the left-hand side of a relationship."@en ; + rdfs:range ns1:Element . + +ns1:geographicPointLocation a owl:DatatypeProperty ; + rdfs:comment "This is a set of point coordinates as defined in by the GPS standard."@en ; + rdfs:range xsd:string . + +ns1:headquartersLocation a owl:ObjectProperty ; + rdfs:comment "The headquartersLocation defines the location of the organization's headquarters."@en ; + rdfs:range ns1:Location . + +ns1:identifier a owl:DatatypeProperty ; + rdfs:comment "Uniquely identifies an external element."@en ; + rdfs:range xsd:string . + +ns1:identifierLocator a owl:DatatypeProperty ; + rdfs:comment "Provides the location for more information regarding an external identifier."@en ; + rdfs:range xsd:anyURI . + +ns1:import a owl:ObjectProperty ; + rdfs:comment "Provides an ExternalMap of Element identifiers."@en ; + rdfs:range ns1:ExternalMap . + +ns1:inLanguage a owl:DatatypeProperty ; + rdfs:comment "Specifies a human language used within the content of an Element or a property."@en ; + rdfs:range xsd:string . + +ns1:intendedUse a owl:DatatypeProperty ; + rdfs:comment "The intendedUse property is designed to capture a summary of how or for what item or artifact is meant to be used for."@en ; + rdfs:range xsd:string . + +ns1:isoAutomationLevel a owl:ObjectProperty ; + rdfs:comment "ISO level of automation."@en ; + rdfs:range ns1:IsoAutomationLevel . + +ns1:issuingAuthority a owl:DatatypeProperty ; + rdfs:comment "An entity that is authorized to issue identification credentials."@en ; + rdfs:range xsd:string . + +ns1:locationHint a owl:DatatypeProperty ; + rdfs:comment "Provides an indication of where to retrieve an external Element."@en ; + rdfs:range xsd:anyURI . + +ns1:locationTime a owl:DatatypeProperty ; + rdfs:comment "A known location is specified at this time."@en ; + rdfs:range xsd:dateTimeStamp . + +ns1:locator a owl:DatatypeProperty ; + rdfs:comment "Provides the location of an external reference."@en ; + rdfs:range xsd:string . + +ns1:name a owl:DatatypeProperty ; + rdfs:comment "Identifies the name of an Element as designated by the creator."@en ; + rdfs:range xsd:string . + +ns1:namespace a owl:DatatypeProperty ; + rdfs:comment """Provides an unambiguous mechanism for conveying a URI fragment portion of an +Element ID."""@en ; + rdfs:range xsd:anyURI . + +ns1:namespaceMap a owl:ObjectProperty ; + rdfs:comment "Provides a NamespaceMap of prefixes and associated namespace partial URIs applicable to an SpdxDocument and independent of any specific serialization format or instance."@en ; + rdfs:range ns1:NamespaceMap . + +ns1:originatedBy a owl:ObjectProperty ; + rdfs:comment "Identifies from where or whom the Element originally came."@en ; + rdfs:range ns1:Agent . + +ns1:packageVerificationCodeExcludedFile a owl:DatatypeProperty ; + rdfs:comment """The relative file name of a file to be excluded from the +`PackageVerificationCode`."""@en ; + rdfs:range xsd:string . + +ns1:postOfficeBoxNumber a owl:DatatypeProperty ; + rdfs:comment "The number that identifies a PO box. A PO box is a box in a post office or other postal service location assigned to an organization where postal items may be kept."@en ; + rdfs:range xsd:string . + +ns1:postalCode a owl:DatatypeProperty ; + rdfs:comment "Text specifying the postal code for an address."@en ; + rdfs:range xsd:string . + +ns1:postalName a owl:DatatypeProperty ; + rdfs:comment "The name of the recipient expressed in text."@en ; + rdfs:range xsd:string . + +ns1:prefix a owl:DatatypeProperty ; + rdfs:comment "A substitute for a URI."@en ; + rdfs:range xsd:string . + +ns1:processRationale a owl:DatatypeProperty ; + rdfs:comment "The reason a process exists."@en ; + rdfs:range xsd:string . + +ns1:processReadiness a owl:DatatypeProperty ; + rdfs:comment "processReadiness describes the readiness of a process."@en ; + rdfs:range ns1:ProcessReadinessType . + +ns1:processVersion a owl:DatatypeProperty ; + rdfs:comment "Defines the version of a specific process."@en ; + rdfs:range xsd:string . + +ns1:profileConformance a owl:ObjectProperty ; + rdfs:comment """Describes one a profile which the creator of this ElementCollection intends to +conform to."""@en ; + rdfs:range ns1:ProfileIdentifierType . + +ns1:provinceStateCode a owl:DatatypeProperty ; + rdfs:comment "Text specifying a province or state."@en ; + rdfs:range xsd:string . + +ns1:quantity a owl:DatatypeProperty ; + rdfs:comment "Quantity is the amount in the selected QUDT unit."@en ; + rdfs:range xsd:string . + +ns1:relationshipType a owl:ObjectProperty ; + rdfs:comment "Information about the relationship between two Elements."@en ; + rdfs:range ns1:RelationshipType . + +ns1:releaseTime a owl:DatatypeProperty ; + rdfs:comment "Specifies the time an artifact was released."@en ; + rdfs:range xsd:dateTimeStamp . + +ns1:requirementRationale a owl:DatatypeProperty ; + rdfs:comment "Text used to define the rationale or additional information."@en ; + rdfs:range xsd:string . + +ns1:requirementStatement a owl:DatatypeProperty ; + rdfs:comment "A text describing the actual need defined by the requirement."@en ; + rdfs:range xsd:string . + +ns1:requirementUUID a owl:DatatypeProperty ; + rdfs:comment "Provides a universally unique Requirement ID."@en ; + rdfs:range ns1:ExternalIdentifier . + +ns1:rootElement a owl:ObjectProperty ; + rdfs:comment "This property is used to denote the root Element(s) of a tree of elements contained in a BOM."@en ; + rdfs:range ns1:Element . + +ns1:scope a owl:ObjectProperty ; + rdfs:comment "Capture the scope of information about a specific relationship between elements."@en ; + rdfs:range ns1:LifecycleScopeType . + +ns1:specType a owl:DatatypeProperty ; + rdfs:comment "A specification type defines the nature of a specification."@en ; + rdfs:range ns1:SpecificationType . + +ns1:specVersion a owl:DatatypeProperty ; + rdfs:comment """Provides a reference number that can be used to understand how to parse and +interpret an Element."""@en ; + rdfs:range xsd:string . + +ns1:standardName a owl:DatatypeProperty ; + rdfs:comment "The name of a relevant standard that may apply to an artifact."@en ; + rdfs:range xsd:string . + +ns1:startTime a owl:DatatypeProperty ; + rdfs:comment "Specifies the time from which an element is applicable / valid."@en ; + rdfs:range xsd:dateTimeStamp . + +ns1:statement a owl:DatatypeProperty ; + rdfs:comment "Commentary on an assertion that an annotator has made."@en ; + rdfs:range xsd:string . + +ns1:streetAddress a owl:DatatypeProperty ; + rdfs:comment "Street address includes a street number, name and unit ID to identify a specific street."@en ; + rdfs:range xsd:string . + +ns1:subject a owl:ObjectProperty ; + rdfs:comment "An Element an annotator has made an assertion about."@en ; + rdfs:range ns1:Element . + +ns1:summary a owl:DatatypeProperty ; + rdfs:comment "A short description of an Element."@en ; + rdfs:range xsd:string . + +ns1:to a owl:ObjectProperty ; + rdfs:comment "References an Element on the right-hand side of a relationship."@en ; + rdfs:range ns1:Element . + +ns1:typeFromSource a owl:DatatypeProperty ; + rdfs:comment "typeFromSource is a value used to define an item within the definitionSource."@en ; + rdfs:range xsd:string . + +ns1:unitQUDT a owl:DatatypeProperty ; + rdfs:comment "QUDT unit is used for measurement criteria based on product type, region and use."@en ; + rdfs:range xsd:string . + +ns1:validUntilTime a owl:DatatypeProperty ; + rdfs:comment """Specifies until when the artifact can be used before its usage needs to be +reassessed."""@en ; + rdfs:range xsd:dateTimeStamp . + +ns1:value a owl:DatatypeProperty ; + rdfs:comment "A value used in a generic key-value pair."@en ; + rdfs:range xsd:string . + + a owl:NamedIndividual, + ns3:ConfidentialityLevelType ; + rdfs:label "amber" ; + rdfs:comment "Data points in the dataset can be shared only with specific organizations and their clients on a need to know basis."@en . + + a owl:NamedIndividual, + ns3:ConfidentialityLevelType ; + rdfs:label "clear" ; + rdfs:comment "Dataset may be distributed freely, without restriction."@en . + + a owl:NamedIndividual, + ns3:ConfidentialityLevelType ; + rdfs:label "green" ; + rdfs:comment "Dataset can be shared within a community of peers and partners."@en . + + a owl:NamedIndividual, + ns3:ConfidentialityLevelType ; + rdfs:label "red" ; + rdfs:comment "Data points in the dataset are highly confidential and can only be shared with named recipients."@en . + + a owl:NamedIndividual, + ns3:DatasetAvailabilityType ; + rdfs:label "clickthrough" ; + rdfs:comment "Dataset is not publicly available and can only be accessed after affirmatively accepting terms on a clickthrough webpage."@en . + + a owl:NamedIndividual, + ns3:DatasetAvailabilityType ; + rdfs:label "directDownload" ; + rdfs:comment "Dataset is publicly available and can be downloaded directly."@en . + + a owl:NamedIndividual, + ns3:DatasetAvailabilityType ; + rdfs:label "query" ; + rdfs:comment "Dataset is publicly available, but not all at once, and can only be accessed through queries which return parts of the dataset."@en . + + a owl:NamedIndividual, + ns3:DatasetAvailabilityType ; + rdfs:label "registration" ; + rdfs:comment "Dataset is not publicly available and an email registration is required before accessing the dataset, although without an affirmative acceptance of terms."@en . + + a owl:NamedIndividual, + ns3:DatasetAvailabilityType ; + rdfs:label "scrapingScript" ; + rdfs:comment "Dataset provider is not making available the underlying data and the dataset shall be reassembled, typically using the provided script for scraping the data."@en . + +ns3:DatasetPackage a owl:Class, + sh:NodeShape ; + rdfs:comment "A Package that contains a dataset."@en ; + rdfs:subClassOf ns6:Package ; + sh:nodeKind sh:IRI ; + sh:property [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns3:datasetUpdateMechanism ], + [ sh:datatype xsd:string ; + sh:nodeKind sh:Literal ; + sh:path ns3:dataPreprocessing ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns3:intendedUse ], + [ sh:class ns1:DictionaryEntry ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ns3:sensor ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns3:dataCollectionProcess ], + [ sh:datatype xsd:string ; + sh:nodeKind sh:Literal ; + sh:path ns1:inLanguage ; + sh:pattern "^[a-zA-Z]{2,8}(-[a-zA-Z0-9]{1,8})*$" ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns3:datasetNoise ], + [ sh:class ns3:DatasetAvailabilityType ; + sh:in ( ) ; + sh:maxCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns3:datasetAvailability ], + [ sh:class ns3:DatasetType ; + sh:in ( ) ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns3:datasetType ], + [ sh:datatype xsd:string ; + sh:nodeKind sh:Literal ; + sh:path ns3:knownBias ], + [ sh:class ns3:ConfidentialityLevelType ; + sh:in ( ) ; + sh:maxCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns3:confidentialityLevel ], + [ sh:class ns1:PresenceType ; + sh:in ( ) ; + sh:maxCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns3:hasSensitivePersonalInformation ], + [ sh:datatype xsd:nonNegativeInteger ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns3:datasetSize ], + [ sh:datatype xsd:string ; + sh:nodeKind sh:Literal ; + sh:path ns3:anonymizationMethodUsed ] . + + a owl:NamedIndividual, + ns3:DatasetType ; + rdfs:label "audio" ; + rdfs:comment "Data is audio based, such as a collection of music from the 80s."@en . + + a owl:NamedIndividual, + ns3:DatasetType ; + rdfs:label "categorical" ; + rdfs:comment "Data that is classified into a discrete number of categories, such as the eye color of a population of people."@en . + + a owl:NamedIndividual, + ns3:DatasetType ; + rdfs:label "graph" ; + rdfs:comment "Data is in the form of a graph where entries are somehow related to each other through edges, such a social network of friends."@en . + + a owl:NamedIndividual, + ns3:DatasetType ; + rdfs:label "image" ; + rdfs:comment "Data is a collection of images such as pictures of animals."@en . + + a owl:NamedIndividual, + ns3:DatasetType ; + rdfs:label "noAssertion" ; + rdfs:comment "Data type is not known."@en . + + a owl:NamedIndividual, + ns3:DatasetType ; + rdfs:label "numeric" ; + rdfs:comment "Data consists only of numeric entries."@en . + + a owl:NamedIndividual, + ns3:DatasetType ; + rdfs:label "other" ; + rdfs:comment "Data is of a type not included in this list."@en . + + a owl:NamedIndividual, + ns3:DatasetType ; + rdfs:label "sensor" ; + rdfs:comment "Data is recorded from a physical sensor, such as a thermometer reading or biometric device."@en . + + a owl:NamedIndividual, + ns3:DatasetType ; + rdfs:label "structured" ; + rdfs:comment "Data is stored in tabular format or retrieved from a relational database."@en . + + a owl:NamedIndividual, + ns3:DatasetType ; + rdfs:label "syntactic" ; + rdfs:comment "Data describes the syntax or semantics of a language or text, such as a parse tree used for natural language processing."@en . + + a owl:NamedIndividual, + ns3:DatasetType ; + rdfs:label "text" ; + rdfs:comment "Data consists of unstructured text, such as a book, a Wikipedia article (without images), or a transcript."@en . + + a owl:NamedIndividual, + ns3:DatasetType ; + rdfs:label "timeseries" ; + rdfs:comment "Data is recorded in an ordered sequence of timestamped entries, such as the price of a stock over the course of a day."@en . + + a owl:NamedIndividual, + ns3:DatasetType ; + rdfs:label "timestamp" ; + rdfs:comment "Data is recorded with a timestamp for each entry, but not necessarily ordered or at specific intervals, such as when a taxi ride starts and ends."@en . + + a owl:NamedIndividual, + ns3:DatasetType ; + rdfs:label "video" ; + rdfs:comment "Data is video based, such as a collection of movie clips featuring Tom Hanks."@en . + +ns3:anonymizationMethodUsed a owl:DatatypeProperty ; + rdfs:comment "Anonymization methods used."@en ; + rdfs:range xsd:string . + +ns3:confidentialityLevel a owl:ObjectProperty ; + rdfs:comment "Confidentiality level of the data points contained in the dataset."@en ; + rdfs:range ns3:ConfidentialityLevelType . + +ns3:dataCollectionProcess a owl:DatatypeProperty ; + rdfs:comment "How the dataset was collected."@en ; + rdfs:range xsd:string . + +ns3:dataPreprocessing a owl:DatatypeProperty ; + rdfs:comment "Preprocessing steps that were applied to the raw data to create the given dataset."@en ; + rdfs:range xsd:string . + +ns3:datasetAvailability a owl:ObjectProperty ; + rdfs:comment "Availability of a dataset."@en ; + rdfs:range ns3:DatasetAvailabilityType . + +ns3:datasetNoise a owl:DatatypeProperty ; + rdfs:comment "Potentially noisy elements of the dataset."@en ; + rdfs:range xsd:string . + +ns3:datasetSize a owl:DatatypeProperty ; + rdfs:comment """**DEPRECATED in SPDX 3.1.** +Use [/Software/artifactSize](../../Software/Properties/artifactSize.md) +instead. + +Size of the dataset."""@en ; + rdfs:range xsd:nonNegativeInteger . + +ns3:datasetType a owl:ObjectProperty ; + rdfs:comment "Type of data in a dataset."@en ; + rdfs:range ns3:DatasetType . + +ns3:datasetUpdateMechanism a owl:DatatypeProperty ; + rdfs:comment "Mechanism to update the dataset."@en ; + rdfs:range xsd:string . + +ns3:hasSensitivePersonalInformation a owl:ObjectProperty ; + rdfs:comment "Describes if any sensitive personal information is present in the dataset."@en ; + rdfs:range ns1:PresenceType . + +ns3:intendedUse a owl:DatatypeProperty ; + rdfs:comment """**DEPRECATED in SPDX 3.1.** +Use [/Core/intendedUse](../../Core/Properties/intendedUse.md) instead. + +The intended use of a given dataset."""@en ; + rdfs:range xsd:string . + +ns3:knownBias a owl:DatatypeProperty ; + rdfs:comment "Records the biases that the dataset is known to encompass."@en ; + rdfs:range xsd:string . + +ns3:sensor a owl:ObjectProperty ; + rdfs:comment "Describes a sensor used for collecting the data."@en ; + rdfs:range ns1:DictionaryEntry . + +ns9:ConjunctiveLicenseSet a owl:Class, + sh:NodeShape ; + rdfs:comment """Portion of an AnyLicenseInfo representing a set of licensing information +where all elements apply."""@en ; + rdfs:subClassOf ; + sh:nodeKind sh:IRI ; + sh:property [ sh:class ; + sh:minCount 2 ; + sh:nodeKind sh:IRI ; + sh:path ns9:member ] . + +ns9:CustomLicense a owl:Class ; + rdfs:comment "A license that is not listed on the SPDX License List."@en ; + rdfs:subClassOf ns9:License ; + sh:nodeKind sh:IRI . + +ns9:CustomLicenseAddition a owl:Class ; + rdfs:comment "A license addition that is not listed on the SPDX Exceptions List."@en ; + rdfs:subClassOf ns9:LicenseAddition ; + sh:nodeKind sh:IRI . + +ns9:DisjunctiveLicenseSet a owl:Class, + sh:NodeShape ; + rdfs:comment """Portion of an AnyLicenseInfo representing a set of licensing information where +only one of the elements applies."""@en ; + rdfs:subClassOf ; + sh:nodeKind sh:IRI ; + sh:property [ sh:class ; + sh:minCount 2 ; + sh:nodeKind sh:IRI ; + sh:path ns9:member ] . + +ns9:ListedLicense a owl:Class, + sh:NodeShape ; + rdfs:comment "A license that is listed on the SPDX License List."@en ; + rdfs:subClassOf ns9:License ; + sh:nodeKind sh:IRI ; + sh:property [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns9:deprecatedVersion ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns9:listVersionAdded ] . + +ns9:ListedLicenseException a owl:Class, + sh:NodeShape ; + rdfs:comment "A license exception that is listed on the SPDX Exceptions list."@en ; + rdfs:subClassOf ns9:LicenseAddition ; + sh:nodeKind sh:IRI ; + sh:property [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns9:deprecatedVersion ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns9:listVersionAdded ] . + +ns9:OrLaterOperator a owl:Class, + sh:NodeShape ; + rdfs:comment """Portion of an AnyLicenseInfo representing this version, or any later version, +of the indicated License."""@en ; + rdfs:subClassOf ns9:ExtendableLicense ; + sh:nodeKind sh:IRI ; + sh:property [ sh:class ns9:License ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns9:subjectLicense ] . + +ns9:WithAdditionOperator a owl:Class, + sh:NodeShape ; + rdfs:comment """Portion of an AnyLicenseInfo representing a License which has additional +text applied to it."""@en ; + rdfs:subClassOf ; + sh:nodeKind sh:IRI ; + sh:property [ sh:class ns9:LicenseAddition ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns9:subjectAddition ], + [ sh:class ns9:ExtendableLicense ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns9:subjectExtendableLicense ] . + +ns9:additionText a owl:DatatypeProperty ; + rdfs:comment "Identifies the full text of a LicenseAddition."@en ; + rdfs:range xsd:string . + +ns9:isDeprecatedAdditionId a owl:DatatypeProperty ; + rdfs:comment "Specifies whether an additional text identifier has been marked as deprecated."@en ; + rdfs:range xsd:boolean . + +ns9:isDeprecatedLicenseId a owl:DatatypeProperty ; + rdfs:comment """Specifies whether a license or additional text identifier has been marked as +deprecated."""@en ; + rdfs:range xsd:boolean . + +ns9:isFsfLibre a owl:DatatypeProperty ; + rdfs:comment """Specifies whether the License is listed as free by the +Free Software Foundation (FSF)."""@en ; + rdfs:range xsd:boolean . + +ns9:isOsiApproved a owl:DatatypeProperty ; + rdfs:comment """Specifies whether the License is listed as approved by the +Open Source Initiative (OSI)."""@en ; + rdfs:range xsd:boolean . + +ns9:standardAdditionTemplate a owl:DatatypeProperty ; + rdfs:comment "Identifies the full text of a LicenseAddition, in SPDX templating format."@en ; + rdfs:range xsd:string . + +ns9:standardLicenseHeader a owl:DatatypeProperty ; + rdfs:comment """Provides a License author's preferred text to indicate that a file is covered +by the License."""@en ; + rdfs:range xsd:string . + +ns9:standardLicenseTemplate a owl:DatatypeProperty ; + rdfs:comment "Identifies the full text of a License, in SPDX templating format."@en ; + rdfs:range xsd:string . + +ns9:subjectAddition a owl:ObjectProperty ; + rdfs:comment "A LicenseAddition participating in a 'with addition' model."@en ; + rdfs:range ns9:LicenseAddition . + +ns9:subjectExtendableLicense a owl:ObjectProperty ; + rdfs:comment "A License participating in a 'with addition' model."@en ; + rdfs:range ns9:ExtendableLicense . + +ns9:subjectLicense a owl:ObjectProperty ; + rdfs:comment "A License participating in an 'or later' model."@en ; + rdfs:range ns9:License . + + a owl:DatatypeProperty ; + rdfs:comment "A name used in a CdxPropertyEntry name-value pair."@en ; + rdfs:range xsd:string . + + a owl:DatatypeProperty ; + rdfs:comment "A value used in a CdxPropertyEntry name-value pair."@en ; + rdfs:range xsd:string . + + a owl:ObjectProperty ; + rdfs:comment "Provides a map of a property name to a value."@en ; + rdfs:range . + +ns2:EvaluationResult a owl:Class, + sh:NodeShape ; + rdfs:comment "EvaluationResult is the result of an evaluation."@en ; + rdfs:subClassOf ns1:Element ; + sh:nodeKind sh:IRI ; + sh:property [ sh:class ns2:RequirementVerification ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns2:evaluationBasedOn ], + [ sh:class ns2:EvaluationResultType ; + sh:in ( ) ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns2:evaluation ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns2:evaluationRationale ] . + + a owl:NamedIndividual, + ns2:EvaluationResultType ; + rdfs:label "fail" ; + rdfs:comment "Indicates a failed evaluation where the requirement or condition is not met."@en . + + a owl:NamedIndividual, + ns2:EvaluationResultType ; + rdfs:label "inconclusive" ; + rdfs:comment "Inconclusive refers to a result or outcome from a verification, test, or analysis that cannot be clearly classified as either positive (successful, pass) or negative (failed, reject). An inconclusive result means there was not enough clear evidence, data, or signal to make a definitive determination, and further investigation or additional testing is necessary. An inconclusive result always shall need a comment on it."@en . + + a owl:NamedIndividual, + ns2:EvaluationResultType ; + rdfs:label "pass" ; + rdfs:comment "Indicates a successful evaluation where the requirement or condition is clearly met."@en . + +ns2:EvidenceRelationship a owl:Class, + sh:NodeShape ; + rdfs:comment "EvidenceRelationship defines the association between pieces of evidence and EvaluationResult."@en ; + rdfs:subClassOf ns1:Relationship ; + sh:nodeKind sh:IRI ; + sh:property [ sh:class ns2:EvidenceType ; + sh:in ( ) ; + sh:nodeKind sh:IRI ; + sh:path ns2:evidenceCategory ], + [ sh:class ns1:ExternalIdentifier ; + sh:maxCount 1 ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ns2:evidenceUUID ] . + + a owl:NamedIndividual, + ns2:EvidenceType ; + rdfs:label "log" ; + rdfs:comment "Time-stamped records capturing system or operational data recorded as usually as a response to specific triggers in a specified environment."@en . + + a owl:NamedIndividual, + ns2:EvidenceType ; + rdfs:label "observation" ; + rdfs:comment "Documentation of direct monitoring or witnessing of the demonstration of processes, tests, or any kind of system responses during a specified timeframe under specified environmental conditions."@en . + + a owl:NamedIndividual, + ns2:EvidenceType ; + rdfs:label "other" ; + rdfs:comment "Any other relevant type of proof or documentation not covered above."@en . + + a owl:NamedIndividual, + ns2:EvidenceType ; + rdfs:label "recording" ; + rdfs:comment "Captured datastream like audio, video, or any other kind of continuous electronic capture of events, behavior or conditions."@en . + + a owl:NamedIndividual, + ns2:EvidenceType ; + rdfs:label "report" ; + rdfs:comment "Structured documentation of test results, inspections, or analyses."@en . + + a owl:NamedIndividual, + ns2:VerificationType ; + rdfs:label "analysis" ; + rdfs:comment "Analytical evaluating of data, designs, or processes methodically to verify correctness against standards or expectations. Typical analysis methods are FMEA, FTA, STPA, static analysis for MISRA compliance etc."@en . + + a owl:NamedIndividual, + ns2:VerificationType ; + rdfs:label "assessment" ; + rdfs:comment "A systematic examination of a system, process, or outcome to evaluate compliance of specific work products with a specific expectation with a specification, regulation or standard. Often involves judgement and a rationale of this judgement."@en . + + a owl:NamedIndividual, + ns2:VerificationType ; + rdfs:label "audit" ; + rdfs:comment "An examination typically focusing on compliance with policies, standards, or regulations. Usually this is done during an audit meeting, while the assessment also involves deep and detailed reviews of work products (e.g. requirements, verification specifications, reports etc.)"@en . + + a owl:NamedIndividual, + ns2:VerificationType ; + rdfs:label "demonstration" ; + rdfs:comment "Demonstrating and monitoring or recording that the item under verification to confirm that a requirement is met by the item under verification."@en . + + a owl:NamedIndividual, + ns2:VerificationType ; + rdfs:label "inspection" ; + rdfs:comment "A thorough examination or checking of documentation, records, processes, or systems to confirm compliance or adherence. An inspection needs to have a defined set of acceptance criteria (e.g. a checklist), a documentation of roles involved in the inspection (e.g. to document the inspector's independence) and a clear documentation of when and how it was performed."@en . + + a owl:NamedIndividual, + ns2:VerificationType ; + rdfs:label "other" ; + rdfs:comment "Any other specialized or custom verification method that fits the context."@en . + + a owl:NamedIndividual, + ns2:VerificationType ; + rdfs:label "review" ; + rdfs:comment "A examination or checking of documentation, records, processes, or systems to confirm compliance or adherence with an upper level requirement. Typically done as peer review, offline review or review meeting."@en . + + a owl:NamedIndividual, + ns2:VerificationType ; + rdfs:label "test" ; + rdfs:comment "Conducting controlled tests, experiments or simulations to verify that specific requirements regarding performance, functionality, robustness, etc. are met."@en . + +ns2:evaluation a owl:DatatypeProperty ; + rdfs:comment "Evaluation is an outcome considering results of a verification."@en ; + rdfs:range ns2:EvaluationResultType . + +ns2:evaluationBasedOn a owl:ObjectProperty ; + rdfs:comment "Indicates the specific RequirementVerification instance on which the EvaluationResult is based."@en ; + rdfs:range ns2:RequirementVerification . + +ns2:evaluationRationale a owl:DatatypeProperty ; + rdfs:comment "Detailed explanation or reasoning that supports the EvaluationResult."@en ; + rdfs:range xsd:string . + +ns2:evidenceCategory a owl:ObjectProperty ; + rdfs:comment "evidenceCategory refers to a category of documented or observable proof."@en ; + rdfs:range ns2:EvidenceType . + +ns2:evidenceUUID a owl:ObjectProperty ; + rdfs:comment "A evidenceUUID is a universally unique identifier (UUID) assigned to an entity, item, or requirement."@en ; + rdfs:range ns1:ExternalIdentifier . + +ns2:verificationMethod a owl:DatatypeProperty ; + rdfs:comment "verificationMethod refers to the specific approach used for a checking an element's conformance with its requirements."@en ; + rdfs:range ns2:VerificationType . + +ns2:verificationPostcondition a owl:DatatypeProperty ; + rdfs:comment "Verification postcondition that are true immediately after a verification method has been performed"@en ; + rdfs:range xsd:string . + +ns2:verificationPrecondition a owl:DatatypeProperty ; + rdfs:comment "Verification preconditions are initial criteria that are to be met prior to initiating the verification method."@en ; + rdfs:range xsd:string . + +ns2:verificationRationale a owl:DatatypeProperty ; + rdfs:comment "A verificationRationale is supporting information that justifies the verification details."@en ; + rdfs:range xsd:string . + +ns2:verificationUUID a owl:ObjectProperty ; + rdfs:comment "A verificationUUID is a universally unique identifier (UUID) assigned to a Verification item."@en ; + rdfs:range ns1:ExternalIdentifier . + +ns10:BulkHardware a owl:Class, + sh:NodeShape ; + rdfs:comment "Products or commodities produced as a bulk unit are called bulk products. Commodities are often sold in bulk."@en ; + rdfs:subClassOf ns10:Hardware ; + sh:nodeKind sh:IRI ; + sh:property [ sh:class ns1:UnitOfMeasure ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ns10:bulkQuantity ] . + +ns10:PhysicalHardware a owl:Class, + sh:NodeShape ; + rdfs:comment "Class that describes a physical instance of Hardware."@en ; + rdfs:subClassOf ns10:Hardware ; + sh:nodeKind sh:IRI ; + sh:property [ sh:class ns10:Dimensions ; + sh:maxCount 1 ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ns10:dimensions ], + [ sh:class ns10:Dimensions ; + sh:maxCount 1 ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ns10:centerOfMass ], + [ sh:class ns1:MeasureOfMass ; + sh:maxCount 1 ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ns10:massOfHardware ] . + +ns10:ProductSpecification a owl:Class, + sh:NodeShape ; + rdfs:comment "A product specification (product spec) is a detailed document that outlines the technical, functional, and design requirements of a product."@en ; + rdfs:subClassOf ns1:Specification ; + sh:nodeKind sh:IRI ; + sh:property [ sh:datatype xsd:string ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns10:partNumber ], + [ sh:datatype xsd:string ; + sh:nodeKind sh:Literal ; + sh:path ns10:itemVersion ], + [ sh:class ns1:DefinedType ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ns10:hazard ] . + +ns10:VirtualHardware a owl:Class, + sh:NodeShape ; + rdfs:comment "Class that describes an instance of VirtualHardware."@en ; + rdfs:subClassOf ns10:Hardware ; + sh:nodeKind sh:IRI ; + sh:property [ sh:class ns10:VirtualHardwareModelType ; + sh:in ( ) ; + sh:maxCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns10:virtualHardwareModel ] . + + a owl:NamedIndividual, + ns10:VirtualHardwareModelType ; + rdfs:label "cycle" ; + rdfs:comment "Simulation architectures with precise cycle-level accuracy."@en . + + a owl:NamedIndividual, + ns10:VirtualHardwareModelType ; + rdfs:label "function" ; + rdfs:comment "Simulation the function of the hardware."@en . + + a owl:NamedIndividual, + ns10:VirtualHardwareModelType ; + rdfs:label "other" ; + rdfs:comment "All other simulation types."@en . + +ns10:additionalInformation a owl:ObjectProperty ; + rdfs:comment "Additional relevance information."@en ; + rdfs:range ns1:DictionaryEntry . + +ns10:additionalInformationSpecification a owl:ObjectProperty ; + rdfs:comment "It is the authoritative or credible entity, document, or body of knowledge that provides the meaning of an additionalInformation key and/or its values, ensuring accuracy, context, and standardization."@en ; + rdfs:range ns1:Specification . + +ns10:batchNumber a owl:DatatypeProperty ; + rdfs:comment "Identifier for product production batch."@en ; + rdfs:range xsd:string . + +ns10:bulkQuantity a owl:ObjectProperty ; + rdfs:comment "The amount or measure of a bulk product."@en ; + rdfs:range ns1:UnitOfMeasure . + +ns10:category a owl:ObjectProperty ; + rdfs:comment "The category describes the hardware item in a DefinedType."@en ; + rdfs:range ns1:DefinedType . + +ns10:centerOfMass a owl:ObjectProperty ; + rdfs:comment "A point representing the mean position of the matter in a body or system."@en ; + rdfs:range ns10:Dimensions . + +ns10:dimensions a owl:ObjectProperty ; + rdfs:comment "Information related to hardware dimension."@en ; + rdfs:range ns10:Dimensions . + +ns10:hardwareVersion a owl:DatatypeProperty ; + rdfs:comment "Version identifier for the hardware product."@en ; + rdfs:range xsd:string . + +ns10:itemVersion a owl:DatatypeProperty ; + rdfs:comment "Version identifier for the item."@en ; + rdfs:range xsd:string . + +ns10:massOfHardware a owl:DatatypeProperty ; + rdfs:comment "Information related to massOfHardware physical hardware."@en ; + rdfs:range ns1:MeasureOfMass . + +ns10:productAgent a owl:ObjectProperty ; + rdfs:comment "The Agent who is responsible for product branding such as an OEM."@en ; + rdfs:range ns1:Agent . + +ns10:releaseDate a owl:ObjectProperty ; + rdfs:comment "Date of product release."@en ; + rdfs:range xsd:dateTimeStamp . + +ns10:serialNumber a owl:DatatypeProperty ; + rdfs:comment "Identifier for specific product is called a serial number."@en ; + rdfs:range xsd:string . + +ns10:virtualHardwareModel a owl:DatatypeProperty ; + rdfs:comment "Information related to virtual hardware simulation."@en ; + rdfs:range ns10:VirtualHardwareModelType . + +ns10:xAxisLength a owl:ObjectProperty ; + rdfs:comment "Information related to hardware dimension."@en ; + rdfs:range ns1:MeasureOfLength . + +ns10:yAxisLength a owl:ObjectProperty ; + rdfs:comment "Information related to hardware dimension."@en ; + rdfs:range ns1:MeasureOfLength . + +ns10:zAxisLength a owl:ObjectProperty ; + rdfs:comment "Information related to hardware dimension."@en ; + rdfs:range ns1:MeasureOfLength . + + a owl:Class, + sh:NodeShape ; + rdfs:comment "Assement of an Element for export control classification."@en ; + rdfs:subClassOf ns1:Artifact ; + sh:nodeKind sh:IRI ; + sh:property [ sh:datatype xsd:dateTimeStamp ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ; + sh:pattern "^\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\dZ$" ], + [ sh:class ; + sh:minCount 1 ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ], + [ sh:class ns1:Agent ; + sh:maxCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ], + [ sh:class ns1:Element ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ], + [ sh:class ; + sh:maxCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ] . + + a owl:DatatypeProperty ; + rdfs:comment "Specifies an Element as subject of an assessment."@en ; + rdfs:range ns1:Element . + + a owl:DatatypeProperty ; + rdfs:comment "Sets the context for an assessment iby specifying the related project."@en ; + rdfs:range . + + a owl:DatatypeProperty ; + rdfs:comment "Specifies an Element as subject of an assessment."@en ; + rdfs:range . + + a owl:DatatypeProperty ; + rdfs:comment "Timestamp, when an assessment was conducted."@en ; + rdfs:range xsd:dateTimeStamp . + + a owl:DatatypeProperty ; + rdfs:comment "An entity providing an assessment."@en ; + rdfs:range ns1:Agent . + + a owl:DatatypeProperty ; + rdfs:comment "Expression for the export control classification."@en ; + rdfs:range xsd:string . + + a owl:DatatypeProperty ; + rdfs:comment "Specification basis for the export control classification."@en ; + rdfs:range ns1:Specification . + + a owl:DatatypeProperty ; + rdfs:comment "Country for which export controls must be taken into account."@en ; + rdfs:range xsd:string . + + a owl:DatatypeProperty ; + rdfs:comment "Link to the project contract."@en ; + rdfs:range xsd:anyURI . + + a owl:DatatypeProperty ; + rdfs:comment "Time when the project ends or is planned to end."@en ; + rdfs:range xsd:dateTimeStamp . + + a owl:DatatypeProperty ; + rdfs:comment "Owner or Lead of the project."@en ; + rdfs:range ns1:Agent . + + a owl:DatatypeProperty ; + rdfs:comment "Sponsor of the project."@en ; + rdfs:range ns1:Agent . + + a owl:DatatypeProperty ; + rdfs:comment "Time when the project starts or is planned to start."@en ; + rdfs:range xsd:dateTimeStamp . + + a owl:DatatypeProperty ; + rdfs:comment "Title of the project."@en ; + rdfs:range xsd:string . + + a owl:ObjectProperty ; + rdfs:comment "Weight to express relevance in de minimis consideration."@en ; + rdfs:range xsd:positiveInteger . + +ns4:CvssV2VulnAssessmentRelationship a owl:Class, + sh:NodeShape ; + rdfs:comment "Provides a CVSS version 2.0 assessment for a vulnerability."@en ; + rdfs:subClassOf ns4:VulnAssessmentRelationship ; + sh:nodeKind sh:IRI ; + sh:property [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns4:vectorString ], + [ sh:datatype xsd:decimal ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns4:score ] . + +ns4:CvssV3VulnAssessmentRelationship a owl:Class, + sh:NodeShape ; + rdfs:comment "Provides a CVSS version 3 assessment for a vulnerability."@en ; + rdfs:subClassOf ns4:VulnAssessmentRelationship ; + sh:nodeKind sh:IRI ; + sh:property [ sh:datatype xsd:decimal ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns4:score ], + [ sh:class ns4:CvssSeverityType ; + sh:in ( ) ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns4:severity ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns4:vectorString ] . + +ns4:CvssV4VulnAssessmentRelationship a owl:Class, + sh:NodeShape ; + rdfs:comment "Provides a CVSS version 4 assessment for a vulnerability."@en ; + rdfs:subClassOf ns4:VulnAssessmentRelationship ; + sh:nodeKind sh:IRI ; + sh:property [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns4:vectorString ], + [ sh:datatype xsd:decimal ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns4:score ], + [ sh:class ns4:CvssSeverityType ; + sh:in ( ) ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns4:severity ] . + +ns4:EpssVulnAssessmentRelationship a owl:Class, + sh:NodeShape ; + rdfs:comment "Provides an EPSS assessment for a vulnerability."@en ; + rdfs:subClassOf ns4:VulnAssessmentRelationship ; + sh:nodeKind sh:IRI ; + sh:property [ sh:datatype xsd:decimal ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns4:percentile ], + [ sh:datatype xsd:decimal ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns4:probability ] . + + a owl:NamedIndividual, + ns4:ExploitCatalogType ; + rdfs:label "kev" ; + rdfs:comment "CISA's Known Exploited Vulnerability (KEV) catalog."@en . + + a owl:NamedIndividual, + ns4:ExploitCatalogType ; + rdfs:label "other" ; + rdfs:comment "Other exploit catalogs."@en . + +ns4:ExploitCatalogVulnAssessmentRelationship a owl:Class, + sh:NodeShape ; + rdfs:comment "Provides an exploit assessment of a vulnerability."@en ; + rdfs:subClassOf ns4:VulnAssessmentRelationship ; + sh:nodeKind sh:IRI ; + sh:property [ sh:class ns4:ExploitCatalogType ; + sh:in ( ) ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns4:catalogType ], + [ sh:datatype xsd:anyURI ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns4:locator ], + [ sh:datatype xsd:boolean ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns4:exploited ] . + + a owl:NamedIndividual, + ns4:SsvcDecisionType ; + rdfs:label "act" ; + rdfs:comment "The vulnerability requires attention from the organization's internal, supervisory-level and leadership-level individuals. Necessary actions include requesting assistance or information about the vulnerability, as well as publishing a notification either internally and/or externally. Typically, internal groups would meet to determine the overall response and then execute agreed upon actions. CISA recommends remediating Act vulnerabilities as soon as possible."@en . + + a owl:NamedIndividual, + ns4:SsvcDecisionType ; + rdfs:label "attend" ; + rdfs:comment "The vulnerability requires attention from the organization's internal, supervisory-level individuals. Necessary actions include requesting assistance or information about the vulnerability, and may involve publishing a notification either internally and/or externally. CISA recommends remediating Attend vulnerabilities sooner than standard update timelines."@en . + + a owl:NamedIndividual, + ns4:SsvcDecisionType ; + rdfs:label "track" ; + rdfs:comment "The vulnerability does not require action at this time. The organization would continue to track the vulnerability and reassess it if new information becomes available. CISA recommends remediating Track vulnerabilities within standard update timelines."@en . + + a owl:NamedIndividual, + ns4:SsvcDecisionType ; + rdfs:label "trackStar" ; + rdfs:comment "(\"Track\\*\" in the SSVC spec) The vulnerability contains specific characteristics that may require closer monitoring for changes. CISA recommends remediating Track\\* vulnerabilities within standard update timelines."@en . + +ns4:SsvcVulnAssessmentRelationship a owl:Class, + sh:NodeShape ; + rdfs:comment "Provides an SSVC assessment for a vulnerability."@en ; + rdfs:subClassOf ns4:VulnAssessmentRelationship ; + sh:nodeKind sh:IRI ; + sh:property [ sh:class ns4:SsvcDecisionType ; + sh:in ( ) ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns4:decisionType ] . + +ns4:VexAffectedVulnAssessmentRelationship a owl:Class, + sh:NodeShape ; + rdfs:comment """Connects a vulnerability and an element designating the element as a product +affected by the vulnerability."""@en ; + rdfs:subClassOf ns4:VexVulnAssessmentRelationship ; + sh:nodeKind sh:IRI ; + sh:property [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns4:actionStatement ], + [ sh:datatype xsd:dateTimeStamp ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns4:actionStatementTime ; + sh:pattern "^\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\dZ$" ] . + +ns4:VexFixedVulnAssessmentRelationship a owl:Class ; + rdfs:comment """Links a vulnerability and elements representing products (in the VEX sense) where +a fix has been applied and are no longer affected."""@en ; + rdfs:subClassOf ns4:VexVulnAssessmentRelationship ; + sh:nodeKind sh:IRI . + + a owl:NamedIndividual, + ns4:VexJustificationType ; + rdfs:label "componentNotPresent" ; + rdfs:comment "The software is not affected because the vulnerable component is not in the product."@en . + + a owl:NamedIndividual, + ns4:VexJustificationType ; + rdfs:label "inlineMitigationsAlreadyExist" ; + rdfs:comment "Built-in inline controls or mitigations prevent an adversary from leveraging the vulnerability."@en . + + a owl:NamedIndividual, + ns4:VexJustificationType ; + rdfs:label "vulnerableCodeCannotBeControlledByAdversary" ; + rdfs:comment "The vulnerable component is present, and the component contains the vulnerable code. However, vulnerable code is used in such a way that an attacker cannot mount any anticipated attack."@en . + + a owl:NamedIndividual, + ns4:VexJustificationType ; + rdfs:label "vulnerableCodeNotInExecutePath" ; + rdfs:comment "The affected code is not reachable through the execution of the code, including non-anticipated states of the product."@en . + + a owl:NamedIndividual, + ns4:VexJustificationType ; + rdfs:label "vulnerableCodeNotPresent" ; + rdfs:comment "The product is not affected because the code underlying the vulnerability is not present in the product."@en . + +ns4:VexNotAffectedVulnAssessmentRelationship a owl:Class, + sh:NodeShape ; + rdfs:comment """Links a vulnerability and one or more elements designating the latter as products +not affected by the vulnerability."""@en ; + rdfs:subClassOf ns4:VexVulnAssessmentRelationship ; + sh:nodeKind sh:IRI ; + sh:property [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns4:impactStatement ], + [ sh:class ns4:VexJustificationType ; + sh:in ( ) ; + sh:maxCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns4:justificationType ], + [ sh:datatype xsd:dateTimeStamp ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns4:impactStatementTime ; + sh:pattern "^\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\dZ$" ] . + +ns4:VexUnderInvestigationVulnAssessmentRelationship a owl:Class ; + rdfs:comment """Designates elements as products where the impact of a vulnerability is being +investigated."""@en ; + rdfs:subClassOf ns4:VexVulnAssessmentRelationship ; + sh:nodeKind sh:IRI . + +ns4:Vulnerability a owl:Class, + sh:NodeShape ; + rdfs:comment "Specifies a vulnerability and its associated information."@en ; + rdfs:subClassOf ns1:Artifact ; + sh:nodeKind sh:IRI ; + sh:property [ sh:datatype xsd:dateTimeStamp ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns4:withdrawnTime ; + sh:pattern "^\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\dZ$" ], + [ sh:datatype xsd:dateTimeStamp ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns4:publishedTime ; + sh:pattern "^\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\dZ$" ], + [ sh:datatype xsd:dateTimeStamp ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns4:modifiedTime ; + sh:pattern "^\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\dZ$" ] . + +ns4:actionStatement a owl:DatatypeProperty ; + rdfs:comment """Provides advise on how to mitigate or remediate a vulnerability when a VEX product +is affected by it."""@en ; + rdfs:range xsd:string . + +ns4:actionStatementTime a owl:DatatypeProperty ; + rdfs:comment """Records the time when a recommended action was communicated in a VEX statement +to mitigate a vulnerability."""@en ; + rdfs:range xsd:dateTimeStamp . + +ns4:assessedElement a owl:ObjectProperty ; + rdfs:comment """Specifies an Element contained in a piece of software where a vulnerability was +found."""@en ; + rdfs:range ns6:SoftwareArtifact . + +ns4:catalogType a owl:ObjectProperty ; + rdfs:comment "Specifies the exploit catalog type."@en ; + rdfs:range ns4:ExploitCatalogType . + +ns4:decisionType a owl:ObjectProperty ; + rdfs:comment """Provide the enumeration of possible decisions in the +[Stakeholder-Specific Vulnerability Categorization (SSVC) decision tree](https://www.cisa.gov/stakeholder-specific-vulnerability-categorization-ssvc)."""@en ; + rdfs:range ns4:SsvcDecisionType . + +ns4:exploited a owl:DatatypeProperty ; + rdfs:comment "Denote whether a CVE is present in an exploit catalog."@en ; + rdfs:range xsd:boolean . + +ns4:impactStatement a owl:DatatypeProperty ; + rdfs:comment """Explains why a VEX product is not affected by a vulnerability. It is an +alternative in VexNotAffectedVulnAssessmentRelationship to the machine-readable +justification label."""@en ; + rdfs:range xsd:string . + +ns4:impactStatementTime a owl:DatatypeProperty ; + rdfs:comment "Timestamp of impact statement."@en ; + rdfs:range xsd:dateTimeStamp . + +ns4:justificationType a owl:ObjectProperty ; + rdfs:comment """Impact justification label to be used when linking a vulnerability to an element +representing a VEX product with a VexNotAffectedVulnAssessmentRelationship +relationship."""@en ; + rdfs:range ns4:VexJustificationType . + +ns4:locator a owl:DatatypeProperty ; + rdfs:comment "Provides the location of an exploit catalog."@en ; + rdfs:range xsd:anyURI . + +ns4:percentile a owl:DatatypeProperty ; + rdfs:comment "The percentile of the current probability score."@en ; + rdfs:range xsd:decimal . + +ns4:probability a owl:DatatypeProperty ; + rdfs:comment "A probability score between 0 and 1 of a vulnerability being exploited."@en ; + rdfs:range xsd:decimal . + +ns4:statusNotes a owl:DatatypeProperty ; + rdfs:comment "Conveys information about how VEX status was determined."@en ; + rdfs:range xsd:string . + +ns4:vexVersion a owl:DatatypeProperty ; + rdfs:comment "Specifies the version of a VEX statement."@en ; + rdfs:range xsd:string . + + a owl:NamedIndividual, + ns8:AuthenticationProtocolType ; + rdfs:label "crl" ; + rdfs:comment "Certificate Revocation List, or CRL, is a list of revoked certificates that is downloaded from the Certificate Authority (CA)."@en . + + a owl:NamedIndividual, + ns8:AuthenticationProtocolType ; + rdfs:label "ocsp" ; + rdfs:comment "Online Certificate Status Protocol, or OCSP, is a common scheme used to maintain the security of a server and other network resources."@en . + + a owl:NamedIndividual, + ns8:AuthenticationProtocolType ; + rdfs:label "other" ; + rdfs:comment "An authentication protocol not covered by one of the other AuthenticationProtocolTypes."@en . + + a owl:NamedIndividual, + ns8:AuthenticationProtocolType ; + rdfs:label "tls" ; + rdfs:comment "Transport Layer Security, or TLS, is a widely adopted security protocol designed to facilitate privacy and data security for communications over the Internet."@en . + +ns8:SoftwareService a owl:Class, + sh:NodeShape ; + rdfs:comment "Software provided as a service over a network."@en ; + rdfs:subClassOf ns1:Element ; + sh:nodeKind sh:IRI ; + sh:property [ sh:class ns8:AuthenticationProtocolType ; + sh:in ( ) ; + sh:nodeKind sh:IRI ; + sh:path ns8:serverAuthenticationProtocol ], + [ sh:class ns1:Agent ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns8:provider ], + [ sh:datatype xsd:string ; + sh:nodeKind sh:Literal ; + sh:path ns8:serviceHostingCountry ; + sh:pattern "^[A-Z]{3}$" ] . + +ns8:provider a owl:ObjectProperty ; + rdfs:comment "The provider of a SoftwareService."@en ; + rdfs:range ns1:Agent . + +ns8:serverAuthenticationProtocol a owl:ObjectProperty ; + rdfs:comment "Authentication protocol used by a server."@en ; + rdfs:range ns8:AuthenticationProtocolType . + +ns8:serviceHostingCountry a owl:DatatypeProperty ; + rdfs:comment "Specifies a country code where a software service is hosted."@en ; + rdfs:range xsd:string . + + a owl:Class, + sh:NodeShape ; + rdfs:comment "An SPDX Element containing an SPDX license expression string."@en ; + rdfs:subClassOf ; + sh:nodeKind sh:IRI ; + sh:property [ sh:class ns1:ElementMap ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ], + [ sh:class ns1:DictionaryEntry ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ; + sh:pattern "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$" ] . + + a owl:Class, + sh:NodeShape ; + rdfs:comment "A license or addition that is not listed on the SPDX License List."@en ; + rdfs:subClassOf ns1:Element ; + sh:nodeKind sh:IRI ; + sh:property [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ] . + + a owl:ObjectProperty ; + rdfs:comment """Maps a "LicenseRef-" string for a custom license or a "AdditionRef-" string for +a custom license addition to a `CustomLicense`, a `CustomLicenseAddition`, or a +`SimpleLicensingText`."""@en ; + rdfs:range ns1:ElementMap . + + a owl:ObjectProperty ; + rdfs:comment """**DEPRECATED in SPDX 3.1.** +Use [customIdToLicense](./customIdToLicense.md) instead. + +Maps a LicenseRef or AdditionRef string for a Custom License or a Custom +License Addition to its URI ID. + +**NOTE:** +This property is deprecated and only included for backward compatibility. +New documents should use [customIdToLicense](./customIdToLicense.md) instead."""@en ; + rdfs:range ns1:DictionaryEntry . + + a owl:DatatypeProperty ; + rdfs:comment "A string in the license expression format."@en ; + rdfs:range xsd:string . + + a owl:DatatypeProperty ; + rdfs:comment "The version of the SPDX License List used in the license expression."@en ; + rdfs:range xsd:string . + + a owl:NamedIndividual, + ns6:ContentIdentifierType ; + rdfs:label "gitoid" ; + rdfs:comment "[Gitoid](https://www.iana.org/assignments/uri-schemes/prov/gitoid), stands for [Git Object ID](https://git-scm.com/book/en/v2/Git-Internals-Git-Objects). A gitoid of type blob is a unique hash of a binary artifact. A gitoid may represent either an [Artifact Identifier](https://github.com/omnibor/spec/blob/eb1ee5c961c16215eb8709b2975d193a2007a35d/spec/SPEC.md#artifact-identifier-types) for the software artifact or an [Input Manifest Identifier](https://github.com/omnibor/spec/blob/eb1ee5c961c16215eb8709b2975d193a2007a35d/spec/SPEC.md#input-manifest-identifier) for the software artifact's associated [Artifact Input Manifest](https://github.com/omnibor/spec/blob/eb1ee5c961c16215eb8709b2975d193a2007a35d/spec/SPEC.md#artifact-input-manifest); this ambiguity exists because the Artifact Input Manifest is itself an artifact, and the gitoid of that artifact is its valid identifier. Gitoids calculated on software artifacts (Snippet, File, or Package Elements) should be recorded in the SPDX 3 SoftwareArtifact's contentIdentifier property. Gitoids calculated on the Artifact Input Manifest (Input Manifest Identifier) should be recorded in the SPDX 3 Element's externalIdentifier property. See [OmniBOR Specification](https://github.com/omnibor/spec/), a minimalistic specification for describing software [Artifact Dependency Graphs](https://github.com/omnibor/spec/blob/eb1ee5c961c16215eb8709b2975d193a2007a35d/spec/SPEC.md#artifact-dependency-graph-adg)."@en . + + a owl:NamedIndividual, + ns6:ContentIdentifierType ; + rdfs:label "swhid" ; + rdfs:comment "SoftWare Hash IDentifier, a persistent intrinsic identifier for digital artifacts, such as files, trees (also known as directories or folders), commits, and other objects typically found in version control systems. The format of the identifiers is defined in the [SWHID specification](https://www.swhid.org/swhid-specification/v1.2/) ([ISO/IEC 18670](https://www.iso.org/standard/89985.html)). They typically look like `swh:1:cnt:94a9ed024d3859793618152ea559a168bbcbb5e2`."@en . + + a owl:NamedIndividual, + ns6:FileKindType ; + rdfs:label "directory" ; + rdfs:comment "The file represents a directory and all content stored in that directory."@en . + + a owl:NamedIndividual, + ns6:FileKindType ; + rdfs:label "file" ; + rdfs:comment "The file represents a single file (default)."@en . + +ns6:Sbom a owl:Class, + sh:NodeShape ; + rdfs:comment "A collection of SPDX Elements describing a single package."@en ; + rdfs:subClassOf ns1:Bom ; + sh:nodeKind sh:IRI ; + sh:property [ sh:class ns6:SbomType ; + sh:in ( ) ; + sh:nodeKind sh:IRI ; + sh:path ns6:sbomType ] . + + a owl:NamedIndividual, + ns6:SbomType ; + rdfs:label "analyzed" ; + rdfs:comment "SBOM generated through analysis of artifacts (e.g., executables, packages, containers, and virtual machine images) after its build. Such analysis generally requires a variety of heuristics. In some contexts, this may also be referred to as a \"3rd party\" SBOM."@en . + + a owl:NamedIndividual, + ns6:SbomType ; + rdfs:label "build" ; + rdfs:comment "SBOM generated as part of the process of building the software to create a releasable artifact (e.g., executable or package) from data such as source files, dependencies, built components, build process ephemeral data, and other SBOMs."@en . + + a owl:NamedIndividual, + ns6:SbomType ; + rdfs:label "deployed" ; + rdfs:comment "SBOM provides an inventory of software that is present on a system. This may be an assembly of other SBOMs that combines analysis of configuration options, and examination of execution behavior in a (potentially simulated) deployment environment."@en . + + a owl:NamedIndividual, + ns6:SbomType ; + rdfs:label "design" ; + rdfs:comment "SBOM of intended, planned software project or product with included components (some of which may not yet exist) for a new software artifact."@en . + + a owl:NamedIndividual, + ns6:SbomType ; + rdfs:label "runtime" ; + rdfs:comment "SBOM generated through instrumenting the system running the software, to capture only components present in the system, as well as external call-outs or dynamically loaded components. In some contexts, this may also be referred to as an \"Instrumented\" or \"Dynamic\" SBOM."@en . + + a owl:NamedIndividual, + ns6:SbomType ; + rdfs:label "source" ; + rdfs:comment "SBOM created directly from the development environment, source files, and included dependencies used to build a product artifact."@en . + +ns6:Snippet a owl:Class, + sh:NodeShape ; + rdfs:comment "Describes a certain part of a file."@en ; + rdfs:subClassOf ns6:SoftwareArtifact ; + sh:nodeKind sh:IRI ; + sh:property [ sh:class ns6:File ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns6:snippetFromFile ], + [ sh:class ns1:PositiveIntegerRange ; + sh:maxCount 1 ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ns6:lineRange ], + [ sh:class ns1:PositiveIntegerRange ; + sh:maxCount 1 ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ns6:byteRange ] . + +ns6:additionalPurpose a owl:ObjectProperty ; + rdfs:comment "Provides additional purpose information of the software artifact."@en ; + rdfs:range ns6:SoftwarePurpose . + +ns6:artifactSize a owl:DatatypeProperty ; + rdfs:comment "Identifies the size of a software Artifact, in bytes."@en ; + rdfs:range xsd:nonNegativeInteger . + +ns6:attributionText a owl:DatatypeProperty ; + rdfs:comment """Provides a place for the SPDX data creator to record acknowledgement text for +a software Package, File or Snippet."""@en ; + rdfs:range xsd:string . + +ns6:byteRange a owl:DatatypeProperty ; + rdfs:comment """Defines the byte range in the original host file that the snippet information +applies to."""@en ; + rdfs:range ns1:PositiveIntegerRange . + +ns6:contentIdentifier a owl:DatatypeProperty ; + rdfs:comment """A canonical, unique, immutable identifier of the artifact content, that may be +used for verifying its identity and/or integrity."""@en ; + rdfs:range ns6:ContentIdentifier . + +ns6:contentIdentifierType a owl:ObjectProperty ; + rdfs:comment "Specifies the type of the content identifier."@en ; + rdfs:range ns6:ContentIdentifierType . + +ns6:contentIdentifierValue a owl:DatatypeProperty ; + rdfs:comment "Specifies the value of the content identifier."@en ; + rdfs:range xsd:anyURI . + +ns6:copyrightText a owl:DatatypeProperty ; + rdfs:comment """Identifies the text of one or more copyright notices for a software Package, +File or Snippet, if any."""@en ; + rdfs:range xsd:string . + +ns6:downloadLocation a owl:DatatypeProperty ; + rdfs:comment """Identifies the download Uniform Resource Identifier for the package at the time +that the document was created."""@en ; + rdfs:range xsd:anyURI . + +ns6:fileKind a owl:ObjectProperty ; + rdfs:comment "Describes if a given file is a directory or non-directory kind of file."@en ; + rdfs:range ns6:FileKindType . + +ns6:homePage a owl:DatatypeProperty ; + rdfs:comment """A place for the SPDX document creator to record a website that serves as the +package's home page."""@en ; + rdfs:range xsd:anyURI . + +ns6:lineRange a owl:DatatypeProperty ; + rdfs:comment """Defines the line range in the original host file that the snippet information +applies to."""@en ; + rdfs:range ns1:PositiveIntegerRange . + +ns6:packageUrl a owl:DatatypeProperty ; + rdfs:comment """Provides a place for the SPDX data creator to record the package URL string +(in accordance with the Package URL specification) for a software Package."""@en ; + rdfs:range xsd:anyURI . + +ns6:packageVersion a owl:DatatypeProperty ; + rdfs:comment "Identify the version of a package."@en ; + rdfs:range xsd:string . + +ns6:primaryPurpose a owl:ObjectProperty ; + rdfs:comment "Provides information about the primary purpose of the software artifact."@en ; + rdfs:range ns6:SoftwarePurpose . + +ns6:sbomType a owl:ObjectProperty ; + rdfs:comment "Provides information about the type of an SBOM."@en ; + rdfs:range ns6:SbomType . + +ns6:snippetFromFile a owl:ObjectProperty ; + rdfs:comment "Defines the original host file that the snippet information applies to."@en ; + rdfs:range ns6:File . + +ns6:sourceInfo a owl:DatatypeProperty ; + rdfs:comment """Records any relevant background information or additional comments +about the origin of the package."""@en ; + rdfs:range xsd:string . + +ns7:AssemblyAction a owl:Class ; + rdfs:comment "AssemblyAction represents the event of creating a product by assembling individual components."@en ; + rdfs:subClassOf ns7:CreateAction ; + sh:nodeKind sh:IRI . + +ns7:AssemblyProcess a owl:Class ; + rdfs:comment "The AssemblyProcess represents the process of creating a product by assembling a set of components, potentially in a way that allows for at disassembly (at least partially)."@en ; + rdfs:subClassOf ns7:CreateProcess ; + sh:nodeKind sh:IRI . + +ns7:BoundaryCrossingAction a owl:Class ; + rdfs:comment "An action of crossing a boundary is defined in this class."@en ; + rdfs:subClassOf ns7:UseAction ; + sh:nodeKind sh:IRI . + +ns7:BoundaryDefinitionAction a owl:Class, + sh:NodeShape ; + rdfs:comment "The boundary definition is used to define boundaries."@en ; + rdfs:subClassOf ns1:Action ; + sh:nodeKind sh:IRI ; + sh:property [ sh:class ns1:DictionaryEntry ; + sh:minCount 1 ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ns7:boundaryParameter ] . + +ns7:BoundaryDefinitionProcess a owl:Class ; + rdfs:comment "The Boundary Definition Process refers to the process class used to produce boundaries."@en ; + rdfs:subClassOf ns1:DefinedProcess ; + sh:nodeKind sh:IRI . + +ns7:ChangeAction a owl:Class ; + rdfs:comment "An actual change to a product."@en ; + rdfs:subClassOf ns7:ModifyAction ; + sh:nodeKind sh:IRI . + +ns7:ChangeProcess a owl:Class ; + rdfs:comment "A prescribed change to a product."@en ; + rdfs:subClassOf ns7:ModifyProcess ; + sh:nodeKind sh:IRI . + +ns7:DestroyAction a owl:Class, + sh:NodeShape ; + rdfs:comment "The record of destruction is entered in this action."@en ; + rdfs:subClassOf ns1:Action ; + sh:nodeKind sh:IRI ; + sh:property [ sh:class ns1:Agent ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns7:destructionPerformedBy ] . + +ns7:DestroyProcess a owl:Class ; + rdfs:comment "The destruction process is defined in this process."@en ; + rdfs:subClassOf ns1:DefinedProcess ; + sh:nodeKind sh:IRI . + +ns7:HarvestAction a owl:Class ; + rdfs:comment "HarvestAction represents the act of creating a product by directly extracting goods or materials from nature."@en ; + rdfs:subClassOf ns7:CreateAction ; + sh:nodeKind sh:IRI . + +ns7:HarvestProcess a owl:Class ; + rdfs:comment "Harvest is the process of extracting goods or products from nature."@en ; + rdfs:subClassOf ns7:CreateProcess ; + sh:nodeKind sh:IRI . + +ns7:InspectionAction a owl:Class ; + rdfs:comment "An inspection action refers to a specific activity or set of activities performed during an inspection to examine, verify, or evaluate an item, process, or system."@en ; + rdfs:subClassOf ns7:UseAction ; + sh:nodeKind sh:IRI . + +ns7:InspectionProcess a owl:Class, + sh:NodeShape ; + rdfs:comment "Inspection Process defines specific various processes needed to satisfy the inspection requirements for a specific product or service."@en ; + rdfs:subClassOf ns7:UseProcess ; + sh:nodeKind sh:IRI ; + sh:property [ sh:class ns1:Location ; + sh:nodeKind sh:IRI ; + sh:path ns7:plannedInspectionLocation ] . + +ns7:InstantiateVirtualHardwareProcess a owl:Class ; + rdfs:comment "Class that describes an InstantiateVirtualHardwareProcess that is used to define VirtualHardware and its source."@en ; + rdfs:subClassOf ns7:CreateProcess ; + sh:nodeKind sh:IRI . + +ns7:ManufactureAction a owl:Class ; + rdfs:comment "ManufactureAction represents the act of creating a product by a manufacturing process."@en ; + rdfs:subClassOf ns7:CreateAction ; + sh:nodeKind sh:IRI . + +ns7:ManufactureProcess a owl:Class ; + rdfs:comment "This class represents the process involved in manufacturing products."@en ; + rdfs:subClassOf ns7:CreateProcess ; + sh:nodeKind sh:IRI . + +ns7:OutOfSpecAction a owl:Class ; + rdfs:comment "An out of specification action is defined in this class."@en ; + rdfs:subClassOf ns7:UseAction ; + sh:nodeKind sh:IRI . + +ns7:PlanAction a owl:Class ; + rdfs:comment "A PlanAction involves the execution of a plan in relation to a PlanProcess."@en ; + rdfs:subClassOf ns7:UseAction ; + sh:nodeKind sh:IRI . + +ns7:PlanProcess a owl:Class ; + rdfs:comment "Process plans outline the stages of implementation or use related to a process."@en ; + rdfs:subClassOf ns7:UseProcess ; + sh:nodeKind sh:IRI . + +ns7:ReproduceAction a owl:Class ; + rdfs:comment "Reproduction is the biological process by which organisms generate new individuals of the same species."@en ; + rdfs:subClassOf ns7:CreateAction ; + sh:nodeKind sh:IRI . + +ns7:ReproduceProcess a owl:Class ; + rdfs:comment "Reproduction is the biological process by which living organisms produce offspring."@en ; + rdfs:subClassOf ns7:CreateProcess ; + sh:nodeKind sh:IRI . + +ns7:ResolutionAction a owl:Class ; + rdfs:comment "Products out of specification require a resolution action. This is the action of resolution."@en ; + rdfs:subClassOf ns7:UseAction ; + sh:nodeKind sh:IRI . + +ns7:ResponsibilityChangeAction a owl:Class, + sh:NodeShape ; + rdfs:comment "ResponsibilityChangeAction refers to the transfer of responsibility from one party to another."@en ; + rdfs:subClassOf ns1:Action ; + sh:nodeKind sh:IRI ; + sh:property [ sh:class ns1:Agent ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns7:current ], + [ sh:class ns1:Element ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns7:responsibilityChangedOn ], + [ sh:class ns1:Agent ; + sh:maxCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns7:previous ], + [ sh:class ns7:ResponsibilityType ; + sh:in ( ) ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns7:responsibilityCategory ] . + +ns7:ResponsibilityChangeProcess a owl:Class, + sh:NodeShape ; + rdfs:comment "ResponsibilityChangeProcess refers to the process of transferring responsibility from one party to another."@en ; + rdfs:subClassOf ns1:DefinedProcess ; + sh:nodeKind sh:IRI ; + sh:property [ sh:class ns7:ResponsibilityType ; + sh:in ( ) ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns7:responsibilityCategory ], + [ sh:class ns1:Agent ; + sh:maxCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns7:plannedCurrent ], + [ sh:class ns1:Element ; + sh:nodeKind sh:IRI ; + sh:path ns7:plannedProductOfResponsibilityChange ], + [ sh:class ns1:Agent ; + sh:maxCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns7:plannedPrevious ] . + +ns7:StateAction a owl:Class, + sh:NodeShape ; + rdfs:comment "This is the state of an affected Element at a specific moment in time."@en ; + rdfs:subClassOf ns7:UseAction ; + sh:nodeKind sh:IRI ; + sh:property [ sh:class ns7:State ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns7:currentState ], + [ sh:class ns7:DefinedStateProcess ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns7:decisionProcess ] . + +ns7:StorageAction a owl:Class ; + rdfs:comment "Records the storage of a product."@en ; + rdfs:subClassOf ns7:ModifyAction ; + sh:nodeKind sh:IRI . + +ns7:StorageProcess a owl:Class, + sh:NodeShape ; + rdfs:comment "Prescribes the storage of a product."@en ; + rdfs:subClassOf ns7:ModifyProcess ; + sh:nodeKind sh:IRI ; + sh:property [ sh:class ns1:Location ; + sh:nodeKind sh:IRI ; + sh:path ns7:plannedStorageLocation ] . + +ns7:TestAction a owl:Class ; + rdfs:comment "A test action is a specific action associated with a test."@en ; + rdfs:subClassOf ns7:UseAction ; + sh:nodeKind sh:IRI . + +ns7:TestProcess a owl:Class ; + rdfs:comment "Test Process defines the testing process for an element."@en ; + rdfs:subClassOf ns7:UseProcess ; + sh:nodeKind sh:IRI . + +ns7:TransportAction a owl:Class, + sh:NodeShape ; + rdfs:comment "An actual change to a product's location."@en ; + rdfs:subClassOf ns7:ModifyAction ; + sh:nodeKind sh:IRI ; + sh:property [ sh:class ns1:Location ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns7:pickupLocation ], + [ sh:class ns1:Location ; + sh:nodeKind sh:IRI ; + sh:path ns7:dropoffLocation ], + [ sh:datatype xsd:string ; + sh:nodeKind sh:Literal ; + sh:path ns7:transportRoute ] . + +ns7:TransportProcess a owl:Class, + sh:NodeShape ; + rdfs:comment "A prescribed change to a product's location."@en ; + rdfs:subClassOf ns7:ModifyProcess ; + sh:nodeKind sh:IRI ; + sh:property [ sh:class ns1:Location ; + sh:nodeKind sh:IRI ; + sh:path ns7:forPickupLocation ], + [ sh:datatype xsd:string ; + sh:nodeKind sh:Literal ; + sh:path ns7:plannedTransportRoutes ], + [ sh:class ns1:Location ; + sh:nodeKind sh:IRI ; + sh:path ns7:forDropoffLocation ] . + +ns7:boundaryParameter a owl:ObjectProperty ; + rdfs:comment "The boundary parameters define the area or region needed to describe a boundary."@en ; + rdfs:range ns1:DictionaryEntry . + +ns7:current a owl:ObjectProperty ; + rdfs:comment "This is the individual, business, or organization who currently manages goods, services, or assets."@en ; + rdfs:range ns1:Agent . + +ns7:currentState a owl:ObjectProperty ; + rdfs:comment "This is the state of an affected Element."@en ; + rdfs:range ns7:State . + +ns7:decisionProcess a owl:ObjectProperty ; + rdfs:comment "This is how the currentState of an affected Element is found."@en ; + rdfs:range ns7:DefinedStateProcess . + +ns7:destructionPerformedBy a owl:ObjectProperty ; + rdfs:comment "This is the agent that performed the act of destroying the item."@en ; + rdfs:range ns1:Agent . + +ns7:dropoffLocation a owl:ObjectProperty ; + rdfs:comment "The location for dropping off or delivering a package or item."@en ; + rdfs:range ns1:Location . + +ns7:forDropoffLocation a owl:ObjectProperty ; + rdfs:comment "The location that an item will be dropping off or delivered."@en ; + rdfs:range ns1:Location . + +ns7:forPickupLocation a owl:ObjectProperty ; + rdfs:comment "The location for picking up a package or item."@en ; + rdfs:range ns1:Location . + +ns7:pickupLocation a owl:ObjectProperty ; + rdfs:comment "The location for picking up a package or item."@en ; + rdfs:range ns1:Location . + +ns7:plannedCurrent a owl:ObjectProperty ; + rdfs:comment "This is the planned individual, business, or organization who currently manages goods, services, or assets."@en ; + rdfs:range ns1:Agent . + +ns7:plannedInspectionLocation a owl:ObjectProperty ; + rdfs:comment "The planned location that a good, product or material is inspected."@en ; + rdfs:range ns1:Location . + +ns7:plannedPrevious a owl:ObjectProperty ; + rdfs:comment "This is the planned individual, business, or organization who was previously managing goods, services, or assets."@en ; + rdfs:range ns1:Agent . + +ns7:plannedProductOfResponsibilityChange a owl:ObjectProperty ; + rdfs:comment "This is the planned product associated with the change of responsibility."@en ; + rdfs:range ns1:Element . + +ns7:plannedStorageLocation a owl:ObjectProperty ; + rdfs:comment "The planned location that a good, product or material is stored."@en ; + rdfs:range ns1:Location . + +ns7:plannedTransportRoutes a owl:DatatypeProperty ; + rdfs:comment "A transport route refers to the planned path or network used to move people, goods, data, or resources from one location to another."@en ; + rdfs:range xsd:string . + +ns7:previous a owl:ObjectProperty ; + rdfs:comment "This is the individual, business, or organization who was previously managing goods, services, or assets."@en ; + rdfs:range ns1:Agent . + +ns7:responsibilityChangedOn a owl:ObjectProperty ; + rdfs:comment "The element that has it's responsibility changed."@en ; + rdfs:range ns1:Element . + +ns7:transportRoute a owl:DatatypeProperty ; + rdfs:comment "A transport route refers to the specific path or network used to move people, goods, data, or resources from one location to another."@en ; + rdfs:range xsd:string . + +ns7:validState a owl:ObjectProperty ; + rdfs:comment "The valid state for DefinedStateProcess."@en ; + rdfs:range ns7:State . + +ns1:Bom a owl:Class ; + rdfs:comment """A container for a grouping of SPDX 3 content characterizing details +(provenance, composition, licensing, etc.) about a product."""@en ; + rdfs:subClassOf ns1:Bundle ; + sh:nodeKind sh:IRI . + + a owl:NamedIndividual, + ns1:HashAlgorithm ; + rdfs:label "adler32" ; + rdfs:comment "Adler-32 checksum is part of the widely used zlib compression library as defined in [RFC 1950](https://datatracker.ietf.org/doc/rfc1950/) Section 2.3."@en . + + a owl:NamedIndividual, + ns1:HashAlgorithm ; + rdfs:label "blake2b256" ; + rdfs:comment "BLAKE2b algorithm with a digest size of 256, as defined in [RFC 7693](https://datatracker.ietf.org/doc/rfc7693/) Section 4."@en . + + a owl:NamedIndividual, + ns1:HashAlgorithm ; + rdfs:label "blake2b384" ; + rdfs:comment "BLAKE2b algorithm with a digest size of 384, as defined in [RFC 7693](https://datatracker.ietf.org/doc/rfc7693/) Section 4."@en . + + a owl:NamedIndividual, + ns1:HashAlgorithm ; + rdfs:label "blake2b512" ; + rdfs:comment "BLAKE2b algorithm with a digest size of 512, as defined in [RFC 7693](https://datatracker.ietf.org/doc/rfc7693/) Section 4."@en . + + a owl:NamedIndividual, + ns1:HashAlgorithm ; + rdfs:label "blake3" ; + rdfs:comment "[BLAKE3](https://github.com/BLAKE3-team/BLAKE3-specs/blob/master/blake3.pdf)"@en . + + a owl:NamedIndividual, + ns1:HashAlgorithm ; + rdfs:label "crystalsDilithium" ; + rdfs:comment "[Dilithium](https://pq-crystals.org/dilithium/)"@en . + + a owl:NamedIndividual, + ns1:HashAlgorithm ; + rdfs:label "crystalsKyber" ; + rdfs:comment "[Kyber](https://pq-crystals.org/kyber/)"@en . + + a owl:NamedIndividual, + ns1:HashAlgorithm ; + rdfs:label "falcon" ; + rdfs:comment "[FALCON](https://falcon-sign.info/falcon.pdf)"@en . + + a owl:NamedIndividual, + ns1:HashAlgorithm ; + rdfs:label "md2" ; + rdfs:comment "MD2 message-digest algorithm, as defined in [RFC 1319](https://datatracker.ietf.org/doc/rfc1319/)."@en . + + a owl:NamedIndividual, + ns1:HashAlgorithm ; + rdfs:label "md4" ; + rdfs:comment "MD4 message-digest algorithm, as defined in [RFC 1186](https://datatracker.ietf.org/doc/rfc1186/)."@en . + + a owl:NamedIndividual, + ns1:HashAlgorithm ; + rdfs:label "md5" ; + rdfs:comment "MD5 message-digest algorithm, as defined in [RFC 1321](https://datatracker.ietf.org/doc/rfc1321/)."@en . + + a owl:NamedIndividual, + ns1:HashAlgorithm ; + rdfs:label "md6" ; + rdfs:comment "[MD6 hash function](https://people.csail.mit.edu/rivest/pubs/RABCx08.pdf)"@en . + + a owl:NamedIndividual, + ns1:HashAlgorithm ; + rdfs:label "other" ; + rdfs:comment "any hashing algorithm that does not exist in this list of entries"@en . + + a owl:NamedIndividual, + ns1:HashAlgorithm ; + rdfs:label "sha1" ; + rdfs:comment "SHA-1, a secure hashing algorithm, as defined in [RFC 3174](https://datatracker.ietf.org/doc/rfc3174/)."@en . + + a owl:NamedIndividual, + ns1:HashAlgorithm ; + rdfs:label "sha224" ; + rdfs:comment "SHA-2 with a digest length of 224, as defined in [RFC 3874](https://datatracker.ietf.org/doc/rfc3874/)."@en . + + a owl:NamedIndividual, + ns1:HashAlgorithm ; + rdfs:label "sha256" ; + rdfs:comment "SHA-2 with a digest length of 256, as defined in [RFC 6234](https://datatracker.ietf.org/doc/rfc6234/)."@en . + + a owl:NamedIndividual, + ns1:HashAlgorithm ; + rdfs:label "sha384" ; + rdfs:comment "SHA-2 with a digest length of 384, as defined in [RFC 6234](https://datatracker.ietf.org/doc/rfc6234/)."@en . + + a owl:NamedIndividual, + ns1:HashAlgorithm ; + rdfs:label "sha3_224" ; + rdfs:comment "SHA-3 with a digest length of 224, as defined in [FIPS 202](https://csrc.nist.gov/pubs/fips/202/final)."@en . + + a owl:NamedIndividual, + ns1:HashAlgorithm ; + rdfs:label "sha3_256" ; + rdfs:comment "SHA-3 with a digest length of 256, as defined in [FIPS 202](https://csrc.nist.gov/pubs/fips/202/final)."@en . + + a owl:NamedIndividual, + ns1:HashAlgorithm ; + rdfs:label "sha3_384" ; + rdfs:comment "SHA-3 with a digest length of 384, as defined in [FIPS 202](https://csrc.nist.gov/pubs/fips/202/final)."@en . + + a owl:NamedIndividual, + ns1:HashAlgorithm ; + rdfs:label "sha3_512" ; + rdfs:comment "SHA-3 with a digest length of 512, as defined in [FIPS 202](https://csrc.nist.gov/pubs/fips/202/final)."@en . + + a owl:NamedIndividual, + ns1:HashAlgorithm ; + rdfs:label "sha512" ; + rdfs:comment "SHA-2 with a digest length of 512, as defined in [RFC 6234](https://datatracker.ietf.org/doc/rfc6234/)."@en . + + a owl:NamedIndividual, + ns1:LifecycleScopeType ; + rdfs:label "build" ; + rdfs:comment "A relationship has specific context implications during an element's build phase, during development."@en . + + a owl:NamedIndividual, + ns1:LifecycleScopeType ; + rdfs:label "decommission" ; + rdfs:comment "A relationship has specific context implications for a product's retirement and/or decommissioning."@en . + + a owl:NamedIndividual, + ns1:LifecycleScopeType ; + rdfs:label "design" ; + rdfs:comment "A relationship has specific context implications during an element's design."@en . + + a owl:NamedIndividual, + ns1:LifecycleScopeType ; + rdfs:label "development" ; + rdfs:comment "A relationship has specific context implications during development phase of an element."@en . + + a owl:NamedIndividual, + ns1:LifecycleScopeType ; + rdfs:label "other" ; + rdfs:comment "A relationship has other specific context information necessary to capture that the above set of enumerations does not handle."@en . + + a owl:NamedIndividual, + ns1:LifecycleScopeType ; + rdfs:label "runtime" ; + rdfs:comment "A relationship has specific context implications during the execution phase of an element."@en . + + a owl:NamedIndividual, + ns1:LifecycleScopeType ; + rdfs:label "test" ; + rdfs:comment "A relationship has specific context implications during an element's testing phase, during development."@en . + + a owl:NamedIndividual, + ns1:LifecycleScopeType ; + rdfs:label "update" ; + rdfs:comment "A relationship has specific context implications for a product update."@en . + +ns1:Organization a owl:Class, + sh:NodeShape ; + rdfs:comment "A group of people who work together in an organized way for a shared purpose."@en ; + rdfs:subClassOf ns1:Agent ; + sh:nodeKind sh:IRI ; + sh:property [ sh:class ns1:Location ; + sh:nodeKind sh:IRI ; + sh:path ns1:headquartersLocation ] . + + a owl:NamedIndividual, + ns1:SupportType ; + rdfs:label "deployed" ; + rdfs:comment "In addition to being supported by the supplier, the software is known to have been deployed and is in use. For a software as a service provider, this implies the software is now available as a service."@en . + + a owl:NamedIndividual, + ns1:SupportType ; + rdfs:label "development" ; + rdfs:comment "The artifact is in active development and is not considered ready for formal support from the supplier."@en . + + a owl:NamedIndividual, + ns1:SupportType ; + rdfs:label "endOfSupport" ; + rdfs:comment "There is a defined end of support for the artifact from the supplier. This may also be referred to as end of life. There is a validUntilDate that can be used to signal when support ends for the artifact."@en . + + a owl:NamedIndividual, + ns1:SupportType ; + rdfs:label "limitedSupport" ; + rdfs:comment "The artifact has been released, and there is limited support available from the supplier. There is a validUntilDate that can provide additional information about the duration of support."@en . + + a owl:NamedIndividual, + ns1:SupportType ; + rdfs:label "noAssertion" ; + rdfs:comment "No assertion about the type of support is made. This is considered the default if no other support type is used."@en . + + a owl:NamedIndividual, + ns1:SupportType ; + rdfs:label "noSupport" ; + rdfs:comment "There is no support for the artifact from the supplier, consumer assumes any support obligations."@en . + + a owl:NamedIndividual, + ns1:SupportType ; + rdfs:label "support" ; + rdfs:comment "The artifact has been released, and is supported from the supplier. There is a validUntilDate that can provide additional information about the duration of support."@en . + +ns1:algorithm a owl:ObjectProperty ; + rdfs:comment "Specifies the algorithm used for calculating the hash value."@en ; + rdfs:range ns1:HashAlgorithm . + +ns1:extension a owl:ObjectProperty ; + rdfs:comment "Specifies an Extension characterization of some aspect of an Element."@en ; + rdfs:range . + +ns1:hashValue a owl:DatatypeProperty ; + rdfs:comment "The result of applying a hash algorithm to an Element."@en ; + rdfs:range xsd:string . + +ns1:key a owl:DatatypeProperty ; + rdfs:comment "A key used in a generic key-value pair."@en ; + rdfs:range xsd:string . + +ns1:suppliedBy a owl:ObjectProperty ; + rdfs:comment """Identifies who or what supplied the artifact or VulnAssessmentRelationship +referenced by the Element."""@en ; + rdfs:range ns1:Agent . + +ns1:supportLevel a owl:ObjectProperty ; + rdfs:comment "Specifies the level of support associated with an artifact."@en ; + rdfs:range ns1:SupportType . + +ns1:verifiedUsing a owl:ObjectProperty ; + rdfs:comment """Provides an IntegrityMethod with which the integrity of an Element can be +asserted."""@en ; + rdfs:range ns1:IntegrityMethod . + +ns9:deprecatedVersion a owl:DatatypeProperty ; + rdfs:comment """Specifies the SPDX License List version in which this license or exception +identifier was deprecated."""@en ; + rdfs:range xsd:string . + +ns9:licenseXml a owl:DatatypeProperty ; + rdfs:comment """Identifies all the text and metadata associated with a license in the license +XML format."""@en ; + rdfs:range xsd:string . + +ns9:listVersionAdded a owl:DatatypeProperty ; + rdfs:comment """Specifies the SPDX License List version in which this ListedLicense or +ListedLicenseException identifier was first added."""@en ; + rdfs:range xsd:string . + +ns9:member a owl:ObjectProperty ; + rdfs:comment "A license expression participating in a license set."@en ; + rdfs:range . + +ns9:obsoletedBy a owl:DatatypeProperty ; + rdfs:comment """Specifies the licenseId that is preferred to be used in place of a deprecated +License or LicenseAddition."""@en ; + rdfs:range xsd:string . + +ns9:seeAlso a owl:DatatypeProperty ; + rdfs:comment "Contains a URL where the License or LicenseAddition can be found in use."@en ; + rdfs:range xsd:anyURI . + +ns10:hazard a owl:ObjectProperty ; + rdfs:comment "Hazards are potential sources of harm, danger, or adverse effects to people, property, the environment, or systems within or related to a specific piece of hardware."@en ; + rdfs:range ns1:DefinedType . + +ns10:partNumber a owl:DatatypeProperty ; + rdfs:comment "Product Part Number as defined by OEM."@en ; + rdfs:range xsd:string . + + a owl:NamedIndividual, + ns4:CvssSeverityType ; + rdfs:label "critical" ; + rdfs:comment "When a CVSS score is between 9.0 - 10.0."@en . + + a owl:NamedIndividual, + ns4:CvssSeverityType ; + rdfs:label "high" ; + rdfs:comment "When a CVSS score is between 7.0 - 8.9."@en . + + a owl:NamedIndividual, + ns4:CvssSeverityType ; + rdfs:label "low" ; + rdfs:comment "When a CVSS score is between 0.1 - 3.9."@en . + + a owl:NamedIndividual, + ns4:CvssSeverityType ; + rdfs:label "medium" ; + rdfs:comment "When a CVSS score is between 4.0 - 6.9."@en . + + a owl:NamedIndividual, + ns4:CvssSeverityType ; + rdfs:label "none" ; + rdfs:comment "When a CVSS score is 0.0."@en . + +ns4:modifiedTime a owl:DatatypeProperty ; + rdfs:comment "Specifies a time when a vulnerability assessment was modified"@en ; + rdfs:range xsd:dateTimeStamp . + +ns4:publishedTime a owl:DatatypeProperty ; + rdfs:comment "Specifies the time when a vulnerability was published."@en ; + rdfs:range xsd:dateTimeStamp . + +ns4:severity a owl:ObjectProperty ; + rdfs:comment "Specifies the CVSS qualitative severity rating of a vulnerability in relation to a piece of software."@en ; + rdfs:range ns4:CvssSeverityType . + +ns4:withdrawnTime a owl:DatatypeProperty ; + rdfs:comment "Specified the time and date when a vulnerability was withdrawn."@en ; + rdfs:range xsd:dateTimeStamp . + + a owl:DatatypeProperty ; + rdfs:comment "Identifies the full text of a License or Addition."@en ; + rdfs:range xsd:string . + + a owl:NamedIndividual, + ns6:SoftwarePurpose ; + rdfs:label "application" ; + rdfs:comment "The Element is a software application."@en . + + a owl:NamedIndividual, + ns6:SoftwarePurpose ; + rdfs:label "archive" ; + rdfs:comment "The Element is an archived collection of one or more files (.tar, .zip, etc.)."@en . + + a owl:NamedIndividual, + ns6:SoftwarePurpose ; + rdfs:label "bom" ; + rdfs:comment "The Element is a bill of materials."@en . + + a owl:NamedIndividual, + ns6:SoftwarePurpose ; + rdfs:label "configuration" ; + rdfs:comment "The Element is configuration data."@en . + + a owl:NamedIndividual, + ns6:SoftwarePurpose ; + rdfs:label "container" ; + rdfs:comment "The Element is a container image which can be used by a container runtime application."@en . + + a owl:NamedIndividual, + ns6:SoftwarePurpose ; + rdfs:label "data" ; + rdfs:comment "The Element is data."@en . + + a owl:NamedIndividual, + ns6:SoftwarePurpose ; + rdfs:label "device" ; + rdfs:comment "The Element refers to a chipset, processor, or electronic board."@en . + + a owl:NamedIndividual, + ns6:SoftwarePurpose ; + rdfs:label "deviceDriver" ; + rdfs:comment "The Element represents software that controls hardware devices."@en . + + a owl:NamedIndividual, + ns6:SoftwarePurpose ; + rdfs:label "diskImage" ; + rdfs:comment "The Element refers to a disk image that can be written to a disk, booted in a VM, etc. A disk image typically contains most or all of the components necessary to boot, such as bootloaders, kernels, firmware, userspace, etc."@en . + + a owl:NamedIndividual, + ns6:SoftwarePurpose ; + rdfs:label "documentation" ; + rdfs:comment "The Element is documentation."@en . + + a owl:NamedIndividual, + ns6:SoftwarePurpose ; + rdfs:label "evidence" ; + rdfs:comment "The Element is the evidence that a specification or requirement has been fulfilled."@en . + + a owl:NamedIndividual, + ns6:SoftwarePurpose ; + rdfs:label "executable" ; + rdfs:comment "The Element is an Artifact that can be run on a computer."@en . + + a owl:NamedIndividual, + ns6:SoftwarePurpose ; + rdfs:label "file" ; + rdfs:comment "The Element is a single file which can be independently distributed (configuration file, statically linked binary, Kubernetes deployment, etc.)."@en . + + a owl:NamedIndividual, + ns6:SoftwarePurpose ; + rdfs:label "filesystemImage" ; + rdfs:comment "The Element is a file system image that can be written to a disk (or virtual) partition."@en . + + a owl:NamedIndividual, + ns6:SoftwarePurpose ; + rdfs:label "firmware" ; + rdfs:comment "The Element provides low level control over a device's hardware."@en . + + a owl:NamedIndividual, + ns6:SoftwarePurpose ; + rdfs:label "framework" ; + rdfs:comment "The Element is a software framework."@en . + + a owl:NamedIndividual, + ns6:SoftwarePurpose ; + rdfs:label "install" ; + rdfs:comment "The Element is used to install software on disk."@en . + + a owl:NamedIndividual, + ns6:SoftwarePurpose ; + rdfs:label "library" ; + rdfs:comment "The Element is a software library."@en . + + a owl:NamedIndividual, + ns6:SoftwarePurpose ; + rdfs:label "manifest" ; + rdfs:comment "The Element is a software manifest."@en . + + a owl:NamedIndividual, + ns6:SoftwarePurpose ; + rdfs:label "model" ; + rdfs:comment "The Element is a machine learning or artificial intelligence model."@en . + + a owl:NamedIndividual, + ns6:SoftwarePurpose ; + rdfs:label "module" ; + rdfs:comment "The Element is a module of a piece of software."@en . + + a owl:NamedIndividual, + ns6:SoftwarePurpose ; + rdfs:label "operatingSystem" ; + rdfs:comment "The Element is an operating system."@en . + + a owl:NamedIndividual, + ns6:SoftwarePurpose ; + rdfs:label "other" ; + rdfs:comment "The Element doesn't fit into any of the other categories."@en . + + a owl:NamedIndividual, + ns6:SoftwarePurpose ; + rdfs:label "patch" ; + rdfs:comment "The Element contains a set of changes to update, fix, or improve another Element."@en . + + a owl:NamedIndividual, + ns6:SoftwarePurpose ; + rdfs:label "platform" ; + rdfs:comment "The Element represents a runtime environment."@en . + + a owl:NamedIndividual, + ns6:SoftwarePurpose ; + rdfs:label "requirement" ; + rdfs:comment "The Element provides a requirement needed as input for another Element."@en . + + a owl:NamedIndividual, + ns6:SoftwarePurpose ; + rdfs:label "source" ; + rdfs:comment "The Element is a single or a collection of source files."@en . + + a owl:NamedIndividual, + ns6:SoftwarePurpose ; + rdfs:label "specification" ; + rdfs:comment "The Element is a plan, guideline or strategy how to create, perform or analyze an application."@en . + + a owl:NamedIndividual, + ns6:SoftwarePurpose ; + rdfs:label "test" ; + rdfs:comment "The Element is a test used to verify functionality on a software element."@en . + + a owl:NamedIndividual, + ns7:ResponsibilityType ; + rdfs:label "custody" ; + rdfs:comment "Custody refers to the responsibility, control, and safekeeping of an asset, person, or legal entity. It involves both physical possession and legal authority over something or someone."@en . + + a owl:NamedIndividual, + ns7:ResponsibilityType ; + rdfs:label "ownership" ; + rdfs:comment "Ownership refers to the legal right to control, manage, and benefit from an asset, resource, or responsibility. It establishes authority, accountability, and entitlements over something, whether it's property, a business, intellectual property, or responsibilities."@en . + +ns7:responsibilityCategory a owl:ObjectProperty ; + rdfs:comment "Requirements can be categorized into various types based on their focus, purpose, and scope."@en ; + rdfs:range ns7:ResponsibilityType . + +ns5:EnergyConsumption a owl:Class, + sh:NodeShape ; + rdfs:comment """A class for describing the energy consumption incurred by an AI model in +different stages of its lifecycle."""@en ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:property [ sh:class ns5:EnergyConsumptionDescription ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ns5:finetuningEnergyConsumption ], + [ sh:class ns5:EnergyConsumptionDescription ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ns5:trainingEnergyConsumption ], + [ sh:class ns5:EnergyConsumptionDescription ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ns5:inferenceEnergyConsumption ] . + +ns1:Bundle a owl:Class, + sh:NodeShape ; + rdfs:comment "A collection of Elements that have a shared context."@en ; + rdfs:subClassOf ns1:ElementCollection ; + sh:nodeKind sh:IRI ; + sh:property [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:context ] . + +ns1:ElementCollection a owl:Class, + sh:NodeShape ; + rdfs:comment "A collection of Elements, not necessarily with unifying context."@en ; + rdfs:subClassOf ns1:Element ; + sh:nodeKind sh:IRI ; + sh:property [ sh:class ns1:Element ; + sh:nodeKind sh:IRI ; + sh:path ns1:rootElement ], + [ sh:class ns1:Element ; + sh:nodeKind sh:IRI ; + sh:path ns1:element ], + [ sh:message "https://spdx.org/rdf/3.1/terms/Core/ElementCollection is an abstract class and should not be instantiated directly. Instantiate a subclass instead."@en ; + sh:not [ sh:hasValue ns1:ElementCollection ] ; + sh:path rdf:type ], + [ sh:class ns1:ProfileIdentifierType ; + sh:in ( ) ; + sh:nodeKind sh:IRI ; + sh:path ns1:profileConformance ] . + +ns1:ElementMap a owl:Class, + sh:NodeShape ; + rdfs:comment "A key with an Element."@en ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:property [ sh:class ns1:Element ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns1:elementValue ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:key ] . + +ns1:ExternalMap a owl:Class, + sh:NodeShape ; + rdfs:comment """A map of Element identifiers that are used within an SpdxDocument but defined +external to that SpdxDocument."""@en ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:property [ sh:datatype xsd:anyURI ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:externalSpdxId ], + [ sh:class ns1:IntegrityMethod ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ns1:verifiedUsing ], + [ sh:datatype xsd:anyURI ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:locationHint ], + [ sh:class ns1:Artifact ; + sh:maxCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns1:definingArtifact ] . + +ns1:ExternalRef a owl:Class, + sh:NodeShape ; + rdfs:comment "A reference to a resource outside the scope of SPDX 3 content related to an Element."@en ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:property [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:comment ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:contentType ; + sh:pattern "^[^\\/]+\\/[^\\/]+$" ], + [ sh:class ns1:ExternalRefType ; + sh:in ( ) ; + sh:maxCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns1:externalRefType ], + [ sh:datatype xsd:string ; + sh:nodeKind sh:Literal ; + sh:path ns1:locator ] . + +ns1:Hash a owl:Class, + sh:NodeShape ; + rdfs:comment "A mathematically calculated representation of a grouping of data."@en ; + rdfs:subClassOf ns1:IntegrityMethod ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:property [ sh:class ns1:HashAlgorithm ; + sh:in ( ) ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns1:algorithm ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:hashValue ] . + +ns1:IndividualElement a owl:Class ; + rdfs:comment """A concrete subclass of Element used by Individuals in the +Core profile."""@en ; + rdfs:subClassOf ns1:Element ; + sh:nodeKind sh:IRI . + +ns1:MeasureOfMass a owl:Class ; + rdfs:comment "The measure of mass refers to the quantity of matter in an object or substance."@en ; + rdfs:subClassOf ns1:UnitOfMeasure ; + sh:nodeKind sh:BlankNodeOrIRI . + +ns1:NamespaceMap a owl:Class, + sh:NodeShape ; + rdfs:comment "A mapping between prefixes and namespace partial URIs."@en ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:property [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:prefix ], + [ sh:datatype xsd:anyURI ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:namespace ] . + + a owl:NamedIndividual, + ns1:PresenceType ; + rdfs:label "no" ; + rdfs:comment "Indicates absence of the field."@en . + + a owl:NamedIndividual, + ns1:PresenceType ; + rdfs:label "noAssertion" ; + rdfs:comment "Makes no assertion about the field."@en . + + a owl:NamedIndividual, + ns1:PresenceType ; + rdfs:label "yes" ; + rdfs:comment "Indicates presence of the field."@en . + +ns1:Tool a owl:Class ; + rdfs:comment "An element of hardware and/or software utilized to carry out a particular function."@en ; + rdfs:subClassOf ns1:Element ; + sh:nodeKind sh:IRI . + +ns1:contentType a owl:DatatypeProperty ; + rdfs:comment "Provides information about the content type of an Element or a property."@en ; + rdfs:range xsd:string . + +ns9:IndividualLicensingInfo a owl:Class ; + rdfs:comment """A concrete subclass of AnyLicenseInfo used by Individuals in the +ExpandedLicensing profile."""@en ; + rdfs:subClassOf ; + sh:nodeKind sh:IRI . + + a owl:Class, + sh:NodeShape ; + rdfs:comment "A property name with an associated value."@en ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:property [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ] . + + a owl:Class ; + rdfs:comment "A characterization of some aspect of an Element that is associated with the Element in a generalized fashion."@en ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:property [ sh:message "https://spdx.org/rdf/3.1/terms/Extension/Extension is an abstract class and should not be instantiated directly. Instantiate a subclass instead."@en ; + sh:not [ sh:hasValue ] ; + sh:path rdf:type ] . + +ns2:RequirementVerification a owl:Class, + sh:NodeShape ; + rdfs:comment "RequirementVerification class defines the base properties of a verification."@en ; + rdfs:subClassOf ns1:Element ; + sh:nodeKind sh:IRI ; + sh:property [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns2:verificationRationale ], + [ sh:datatype xsd:string ; + sh:nodeKind sh:Literal ; + sh:path ns2:verificationPrecondition ], + [ sh:class ns1:ExternalIdentifier ; + sh:maxCount 1 ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ns2:verificationUUID ], + [ sh:class ns2:VerificationType ; + sh:in ( ) ; + sh:nodeKind sh:IRI ; + sh:path ns2:verificationMethod ], + [ sh:datatype xsd:string ; + sh:nodeKind sh:Literal ; + sh:path ns2:verificationPostcondition ] . + + a owl:Class, + sh:NodeShape ; + rdfs:comment "Temporary endeavor with a beginning and an end and that must be used to create a unique product, service or result."@en ; + rdfs:subClassOf ns1:Bundle ; + sh:nodeKind sh:IRI ; + sh:property [ sh:datatype xsd:dateTimeStamp ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ; + sh:pattern "^\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\dZ$" ], + [ sh:datatype xsd:dateTimeStamp ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ; + sh:pattern "^\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\dZ$" ], + [ sh:class ns1:Agent ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ], + [ sh:class ns1:Agent ; + sh:nodeKind sh:IRI ; + sh:path ], + [ sh:datatype xsd:anyURI ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ] . + +ns4:score a owl:DatatypeProperty ; + rdfs:comment "Provides a numerical (0-10) representation of the severity of a vulnerability."@en ; + rdfs:range xsd:decimal . + +ns4:vectorString a owl:DatatypeProperty ; + rdfs:comment "Specifies the CVSS vector string for a vulnerability."@en ; + rdfs:range xsd:string . + +ns6:ContentIdentifier a owl:Class, + sh:NodeShape ; + rdfs:comment "A canonical, unique, immutable identifier."@en ; + rdfs:subClassOf ns1:IntegrityMethod ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:property [ sh:class ns6:ContentIdentifierType ; + sh:in ( ) ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns6:contentIdentifierType ], + [ sh:datatype xsd:anyURI ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns6:contentIdentifierValue ] . + +ns6:File a owl:Class, + sh:NodeShape ; + rdfs:comment "Refers to any object that stores content on a computer."@en ; + rdfs:subClassOf ns6:SoftwareArtifact ; + sh:nodeKind sh:IRI ; + sh:property [ sh:class ns6:FileKindType ; + sh:in ( ) ; + sh:maxCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns6:fileKind ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:contentType ; + sh:pattern "^[^\\/]+\\/[^\\/]+$" ] . + +ns6:Package a owl:Class, + sh:NodeShape ; + rdfs:comment """Refers to any unit of content that can be associated with a distribution of +software."""@en ; + rdfs:subClassOf ns6:SoftwareArtifact ; + sh:nodeKind sh:IRI ; + sh:property [ sh:datatype xsd:anyURI ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns6:packageUrl ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns6:sourceInfo ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns6:packageVersion ], + [ sh:datatype xsd:anyURI ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns6:homePage ], + [ sh:datatype xsd:anyURI ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns6:downloadLocation ] . + +ns7:DefinedStateProcess a owl:Class, + sh:NodeShape ; + rdfs:comment "This process is used to determine the state of an affected Element."@en ; + rdfs:subClassOf ns7:UseProcess ; + sh:nodeKind sh:IRI ; + sh:property [ sh:class ns7:State ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns7:validState ] . + +ns1:AnnotationType a owl:Class ; + rdfs:comment "Specifies the type of an annotation."@en . + +ns10:Hardware a owl:Class, + sh:NodeShape ; + rdfs:comment "Class that describes an instance of Hardware."@en ; + rdfs:subClassOf ns1:Artifact ; + sh:nodeKind sh:IRI ; + sh:property [ sh:message "https://spdx.org/rdf/3.1/terms/Hardware/Hardware is an abstract class and should not be instantiated directly. Instantiate a subclass instead."@en ; + sh:not [ sh:hasValue ns10:Hardware ] ; + sh:path rdf:type ], + [ sh:class ns1:DefinedType ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ns10:hazard ], + [ sh:class ns1:DictionaryEntry ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ns10:additionalInformation ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns10:serialNumber ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns10:partNumber ], + [ sh:datatype xsd:dateTimeStamp ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns10:releaseDate ; + sh:pattern "^\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\dZ$" ], + [ sh:class ns1:Agent ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns10:productAgent ], + [ sh:class ns1:DefinedType ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ns10:category ], + [ sh:class ns1:Specification ; + sh:nodeKind sh:IRI ; + sh:path ns10:additionalInformationSpecification ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns10:hardwareVersion ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns10:batchNumber ] . + + a owl:Class, + sh:NodeShape ; + rdfs:comment "Assement of an Element for export control classification."@en ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:property [ sh:class ns1:Specification ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ], + [ sh:datatype xsd:positiveInteger ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:comment ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ; + sh:pattern "^[A-Z]{3}$" ] . + +ns4:ExploitCatalogType a owl:Class ; + rdfs:comment "Specifies the exploit catalog type."@en . + +ns6:ContentIdentifierType a owl:Class ; + rdfs:comment "Specifies the type of a content identifier."@en . + +ns6:FileKindType a owl:Class ; + rdfs:comment "Enumeration of the different kinds of SPDX file."@en . + +ns7:ModifyAction a owl:Class ; + rdfs:comment "An actual alteration of a product."@en ; + rdfs:subClassOf ns1:Action ; + sh:nodeKind sh:IRI ; + sh:property [ sh:message "https://spdx.org/rdf/3.1/terms/SupplyChain/ModifyAction is an abstract class and should not be instantiated directly. Instantiate a subclass instead."@en ; + sh:not [ sh:hasValue ns7:ModifyAction ] ; + sh:path rdf:type ] . + +ns7:ModifyProcess a owl:Class ; + rdfs:comment "A prescribed alteration of a product."@en ; + rdfs:subClassOf ns1:DefinedProcess ; + sh:nodeKind sh:IRI ; + sh:property [ sh:message "https://spdx.org/rdf/3.1/terms/SupplyChain/ModifyProcess is an abstract class and should not be instantiated directly. Instantiate a subclass instead."@en ; + sh:not [ sh:hasValue ns7:ModifyProcess ] ; + sh:path rdf:type ] . + +ns5:EnergyUnitType a owl:Class ; + rdfs:comment "Unit of energy consumption."@en . + +ns1:PositiveIntegerRange a owl:Class, + sh:NodeShape ; + rdfs:comment "A tuple of two positive integers that define a range."@en ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:property [ sh:datatype xsd:positiveInteger ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:endIntegerRange ], + [ sh:datatype xsd:positiveInteger ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:beginIntegerRange ] . + +ns1:RelationshipCompleteness a owl:Class ; + rdfs:comment "Indicates whether a relationship is known to be complete, incomplete, or if no assertion is made with respect to relationship completeness."@en . + +ns1:SpdxOrganization a owl:NamedIndividual, + ns1:Organization ; + rdfs:comment "An Organization representing the SPDX Project."@en ; + owl:sameAs ; + ns1:creationInfo . + +ns1:UnitOfMeasure a owl:Class, + sh:NodeShape ; + rdfs:comment "UnitofMeasure specify information structures through industry standards for Units of Measure, Quantity Kinds, Dimensions and Data Types."@en ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:property [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:quantity ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:unitQUDT ] . + +ns9:ExtendableLicense a owl:Class ; + rdfs:comment "Abstract class representing a License or an OrLaterOperator."@en ; + rdfs:subClassOf ; + sh:nodeKind sh:IRI ; + sh:property [ sh:message "https://spdx.org/rdf/3.1/terms/ExpandedLicensing/ExtendableLicense is an abstract class and should not be instantiated directly. Instantiate a subclass instead."@en ; + sh:not [ sh:hasValue ns9:ExtendableLicense ] ; + sh:path rdf:type ] . + +ns9:License a owl:Class, + sh:NodeShape ; + rdfs:comment "Abstract class for the portion of an AnyLicenseInfo representing a license."@en ; + rdfs:subClassOf ns9:ExtendableLicense ; + sh:nodeKind sh:IRI ; + sh:property [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ], + [ sh:datatype xsd:boolean ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns9:isOsiApproved ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns9:obsoletedBy ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns9:licenseXml ], + [ sh:message "https://spdx.org/rdf/3.1/terms/ExpandedLicensing/License is an abstract class and should not be instantiated directly. Instantiate a subclass instead."@en ; + sh:not [ sh:hasValue ns9:License ] ; + sh:path rdf:type ], + [ sh:datatype xsd:boolean ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns9:isDeprecatedLicenseId ], + [ sh:datatype xsd:anyURI ; + sh:nodeKind sh:Literal ; + sh:path ns9:seeAlso ], + [ sh:datatype xsd:boolean ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns9:isFsfLibre ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns9:standardLicenseTemplate ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns9:standardLicenseHeader ] . + +ns9:LicenseAddition a owl:Class, + sh:NodeShape ; + rdfs:comment """Abstract class for additional text intended to be added to a License, but +which is not itself a standalone License."""@en ; + rdfs:subClassOf ns1:Element ; + sh:nodeKind sh:IRI ; + sh:property [ sh:datatype xsd:boolean ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns9:isDeprecatedAdditionId ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns9:licenseXml ], + [ sh:datatype xsd:anyURI ; + sh:nodeKind sh:Literal ; + sh:path ns9:seeAlso ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns9:standardAdditionTemplate ], + [ sh:message "https://spdx.org/rdf/3.1/terms/ExpandedLicensing/LicenseAddition is an abstract class and should not be instantiated directly. Instantiate a subclass instead."@en ; + sh:not [ sh:hasValue ns9:LicenseAddition ] ; + sh:path rdf:type ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns9:additionText ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns9:obsoletedBy ] . + +ns2:EvaluationResultType a owl:Class ; + rdfs:comment "EvaluationResultType describes the outcome of an evaluation or verification process with."@en . + +ns10:Dimensions a owl:Class, + sh:NodeShape ; + rdfs:comment "Dimensions generally refer to measurable extents or attributes that define the size, shape, or scale of an object, system, or concept."@en ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:property [ sh:class ns1:MeasureOfLength ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ns10:yAxisLength ], + [ sh:class ns1:MeasureOfLength ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ns10:zAxisLength ], + [ sh:class ns1:MeasureOfLength ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ns10:xAxisLength ] . + +ns10:VirtualHardwareModelType a owl:Class ; + rdfs:comment "VirtualHardwareModelType sets the VirtualHardware Model Type."@en . + +ns4:VexVulnAssessmentRelationship a owl:Class, + sh:NodeShape ; + rdfs:comment "Abstract ancestor class for all VEX relationships."@en ; + rdfs:subClassOf ns4:VulnAssessmentRelationship ; + sh:nodeKind sh:IRI ; + sh:property [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns4:statusNotes ], + [ sh:message "https://spdx.org/rdf/3.1/terms/Security/VexVulnAssessmentRelationship is an abstract class and should not be instantiated directly. Instantiate a subclass instead."@en ; + sh:not [ sh:hasValue ns4:VexVulnAssessmentRelationship ] ; + sh:path rdf:type ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns4:vexVersion ] . + +ns7:CreateAction a owl:Class ; + rdfs:comment "CreationAction represents an event of product creation."@en ; + rdfs:subClassOf ns1:Action ; + sh:nodeKind sh:IRI ; + sh:property [ sh:message "https://spdx.org/rdf/3.1/terms/SupplyChain/CreateAction is an abstract class and should not be instantiated directly. Instantiate a subclass instead."@en ; + sh:not [ sh:hasValue ns7:CreateAction ] ; + sh:path rdf:type ] . + +ns7:ResponsibilityType a owl:Class ; + rdfs:comment "These categories help define sets Responsibility Type."@en . + +ns7:State a owl:Class ; + rdfs:comment "A state is an instance that describes what a system, component, subsystem, process, or project has achieved at any given time."@en ; + rdfs:subClassOf ns1:Artifact ; + sh:nodeKind sh:IRI . + +ns7:UseProcess a owl:Class ; + rdfs:comment "Use Process defines actions used by elements."@en ; + rdfs:subClassOf ns1:DefinedProcess ; + sh:nodeKind sh:IRI ; + sh:property [ sh:message "https://spdx.org/rdf/3.1/terms/SupplyChain/UseProcess is an abstract class and should not be instantiated directly. Instantiate a subclass instead."@en ; + sh:not [ sh:hasValue ns7:UseProcess ] ; + sh:path rdf:type ] . + +ns5:SafetyRiskAssessmentType a owl:Class ; + rdfs:comment "Safety risk level."@en . + +ns1:ContactPointRelationshipType a owl:Class ; + rdfs:comment "Information about the type of contact point for `ContactPointRelationship`s."@en . + +ns1:DefinedType a owl:Class, + sh:NodeShape ; + rdfs:comment "The DefinedType class associates a specific type with its defined source."@en ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:property [ sh:class ns1:Specification ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns1:definitionSource ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:typeFromSource ] . + +ns1:ProcessReadinessType a owl:Class ; + rdfs:comment "The ProcessReadinessType is defined by the enumeration."@en . + +ns1:Relationship a owl:Class, + sh:NodeShape ; + rdfs:comment "Describes a relationship between one or more elements."@en ; + rdfs:subClassOf ns1:Element ; + sh:nodeKind sh:IRI ; + sh:property [ sh:datatype xsd:dateTimeStamp ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:endTime ; + sh:pattern "^\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\dZ$" ], + [ sh:class ns1:Element ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns1:from ], + [ sh:class ns1:Element ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns1:to ], + [ sh:class ns1:RelationshipType ; + sh:in ( ) ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns1:relationshipType ], + [ sh:class ns1:RelationshipCompleteness ; + sh:in ( ) ; + sh:maxCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns1:completeness ], + [ sh:datatype xsd:dateTimeStamp ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:startTime ; + sh:pattern "^\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\dZ$" ] . + +ns1:SpecificationType a owl:Class ; + rdfs:comment "A specification type defines the nature of a specification."@en . + +ns1:comment a owl:DatatypeProperty ; + rdfs:comment """Provide consumers with comments by the creator of the Element about the +Element."""@en ; + rdfs:range xsd:string . + +ns3:ConfidentialityLevelType a owl:Class ; + rdfs:comment "Confidentiality level."@en . + +ns4:SsvcDecisionType a owl:Class ; + rdfs:comment "Specifies the SSVC decision type."@en . + +ns8:AuthenticationProtocolType a owl:Class ; + rdfs:comment "Protocols which support authentication."@en . + +ns6:SoftwareArtifact a owl:Class, + sh:NodeShape ; + rdfs:comment "A distinct article or unit related to Software."@en ; + rdfs:subClassOf ns1:Artifact ; + sh:nodeKind sh:IRI ; + sh:property [ sh:class ns6:SoftwarePurpose ; + sh:in ( ) ; + sh:maxCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns6:primaryPurpose ], + [ sh:class ns6:ContentIdentifier ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ns6:contentIdentifier ], + [ sh:message "https://spdx.org/rdf/3.1/terms/Software/SoftwareArtifact is an abstract class and should not be instantiated directly. Instantiate a subclass instead."@en ; + sh:not [ sh:hasValue ns6:SoftwareArtifact ] ; + sh:path rdf:type ], + [ sh:class ns6:SoftwarePurpose ; + sh:in ( ) ; + sh:nodeKind sh:IRI ; + sh:path ns6:additionalPurpose ], + [ sh:datatype xsd:nonNegativeInteger ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns6:artifactSize ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns6:copyrightText ], + [ sh:datatype xsd:string ; + sh:nodeKind sh:Literal ; + sh:path ns6:attributionText ] . + +ns7:CreateProcess a owl:Class ; + rdfs:comment "The CreateProcess refers to the abstract process class that can be used to represent the process of creation of a product."@en ; + rdfs:subClassOf ns1:DefinedProcess ; + sh:nodeKind sh:IRI ; + sh:property [ sh:message "https://spdx.org/rdf/3.1/terms/SupplyChain/CreateProcess is an abstract class and should not be instantiated directly. Instantiate a subclass instead."@en ; + sh:not [ sh:hasValue ns7:CreateProcess ] ; + sh:path rdf:type ] . + +ns5:EnergyConsumptionDescription a owl:Class, + sh:NodeShape ; + rdfs:comment """The class that helps note down the quantity of energy consumption and the unit +used for measurement."""@en ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:property [ sh:class ns5:EnergyUnitType ; + sh:in ( ) ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns5:energyUnit ], + [ sh:datatype xsd:decimal ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns5:energyQuantity ] . + +ns1:Action a owl:Class, + sh:NodeShape ; + rdfs:comment "Class that describes an action that has occurred."@en ; + rdfs:subClassOf ns1:Artifact ; + sh:nodeKind sh:IRI ; + sh:property [ sh:datatype xsd:dateTimeStamp ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:actionStartTime ; + sh:pattern "^\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\dZ$" ], + [ sh:class ns1:DictionaryEntry ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ns1:additionalInformation ], + [ sh:message "https://spdx.org/rdf/3.1/terms/Core/Action is an abstract class and should not be instantiated directly. Instantiate a subclass instead."@en ; + sh:not [ sh:hasValue ns1:Action ] ; + sh:path rdf:type ], + [ sh:datatype xsd:dateTimeStamp ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:actionEndTime ; + sh:pattern "^\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\dZ$" ], + [ sh:class ns1:Location ; + sh:nodeKind sh:IRI ; + sh:path ns1:actionLocation ] . + +ns1:DefinedProcess a owl:Class, + sh:NodeShape ; + rdfs:comment "Class that describes a process."@en ; + rdfs:subClassOf ns1:Artifact ; + sh:nodeKind sh:IRI ; + sh:property [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:processVersion ], + [ sh:class ns1:ProcessReadinessType ; + sh:in ( ) ; + sh:maxCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns1:processReadiness ], + [ sh:message "https://spdx.org/rdf/3.1/terms/Core/DefinedProcess is an abstract class and should not be instantiated directly. Instantiate a subclass instead."@en ; + sh:not [ sh:hasValue ns1:DefinedProcess ] ; + sh:path rdf:type ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:processRationale ] . + +ns1:IntegrityMethod a owl:Class, + sh:NodeShape ; + rdfs:comment "Provides an independently reproducible mechanism that permits verification of a specific Element."@en ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:property [ sh:message "https://spdx.org/rdf/3.1/terms/Core/IntegrityMethod is an abstract class and should not be instantiated directly. Instantiate a subclass instead."@en ; + sh:not [ sh:hasValue ns1:IntegrityMethod ] ; + sh:path rdf:type ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:comment ] . + +ns1:MeasureOfLength a owl:Class ; + rdfs:comment "The measure of length refers to the dimension of an object or space that describes how long it is, typically expressed in various units depending on the system of measurement being used."@en ; + rdfs:subClassOf ns1:UnitOfMeasure ; + sh:nodeKind sh:BlankNodeOrIRI . + +ns3:DatasetAvailabilityType a owl:Class ; + rdfs:comment "Availability of dataset."@en . + +ns2:EvidenceType a owl:Class ; + rdfs:comment "EvidenceType refers to categories of documented or observable proof used to verify compliance, qualification, or performance"@en . + +ns4:VexJustificationType a owl:Class ; + rdfs:comment "Specifies the VEX justification type."@en . + +ns1:CreationInfo a owl:Class, + sh:NodeShape ; + rdfs:comment "Provides information about the creation of the Element."@en ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:property [ sh:datatype xsd:dateTimeStamp ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:created ; + sh:pattern "^\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\dZ$" ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:comment ], + [ sh:class ns1:Tool ; + sh:nodeKind sh:IRI ; + sh:path ns1:createdUsing ], + [ sh:class ns1:Agent ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns1:createdBy ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:specVersion ; + sh:pattern "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$" ] . + +ns4:CvssSeverityType a owl:Class ; + rdfs:comment "Specifies the CVSS base, temporal, threat, or environmental severity type."@en . + +ns4:VulnAssessmentRelationship a owl:Class, + sh:NodeShape ; + rdfs:comment "Abstract ancestor class for all vulnerability assessments."@en ; + rdfs:subClassOf ns1:Relationship ; + sh:nodeKind sh:IRI ; + sh:property [ sh:datatype xsd:dateTimeStamp ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns4:modifiedTime ; + sh:pattern "^\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\dZ$" ], + [ sh:datatype xsd:dateTimeStamp ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns4:publishedTime ; + sh:pattern "^\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\dZ$" ], + [ sh:datatype xsd:dateTimeStamp ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns4:withdrawnTime ; + sh:pattern "^\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\dZ$" ], + [ sh:class ns6:SoftwareArtifact ; + sh:maxCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns4:assessedElement ], + [ sh:message "https://spdx.org/rdf/3.1/terms/Security/VulnAssessmentRelationship is an abstract class and should not be instantiated directly. Instantiate a subclass instead."@en ; + sh:not [ sh:hasValue ns4:VulnAssessmentRelationship ] ; + sh:path rdf:type ], + [ sh:class ns1:Agent ; + sh:maxCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns1:suppliedBy ] . + +ns6:SbomType a owl:Class ; + rdfs:comment """Provides a set of values to be used to describe the common types of SBOMs that +tools may create."""@en . + +ns7:UseAction a owl:Class ; + rdfs:comment "The action of product use."@en ; + rdfs:subClassOf ns1:Action ; + sh:nodeKind sh:IRI ; + sh:property [ sh:message "https://spdx.org/rdf/3.1/terms/SupplyChain/UseAction is an abstract class and should not be instantiated directly. Instantiate a subclass instead."@en ; + sh:not [ sh:hasValue ns7:UseAction ] ; + sh:path rdf:type ] . + +ns1:ExternalIdentifier a owl:Class, + sh:NodeShape ; + rdfs:comment "A reference to a resource identifier defined outside the scope of SPDX 3 content that uniquely identifies an Element."@en ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:property [ sh:datatype xsd:anyURI ; + sh:nodeKind sh:Literal ; + sh:path ns1:identifierLocator ], + [ sh:class ns1:ExternalIdentifierType ; + sh:in ( ) ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns1:externalIdentifierType ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:identifier ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:comment ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:issuingAuthority ] . + +ns1:IsoAutomationLevel a owl:Class ; + rdfs:comment "Defines the level of automation a system possesses."@en . + +ns1:PresenceType a owl:Class ; + rdfs:comment "Categories of presence or absence."@en . + +ns1:Specification a owl:Class, + sh:NodeShape ; + rdfs:comment """A specification is a detailed description of the design, requirements, +or features of a product, process, or system."""@en ; + rdfs:subClassOf ns1:Artifact ; + sh:nodeKind sh:IRI ; + sh:property [ sh:class ns1:SpecificationType ; + sh:in ( ) ; + sh:maxCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns1:specType ] . + +ns1:SupportType a owl:Class ; + rdfs:comment "Type of support that is associated with an artifact."@en . + +ns2:VerificationType a owl:Class ; + rdfs:comment "Enumeration of verification types."@en . + +ns1:Artifact a owl:Class, + sh:NodeShape ; + rdfs:comment "A distinct article or unit within the domain."@en ; + rdfs:subClassOf ns1:Element ; + sh:nodeKind sh:IRI ; + sh:property [ sh:datatype xsd:dateTimeStamp ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:builtTime ; + sh:pattern "^\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\dZ$" ], + [ sh:class ns1:Agent ; + sh:maxCount 1 ; + sh:nodeKind sh:IRI ; + sh:path ns1:suppliedBy ], + [ sh:datatype xsd:dateTimeStamp ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:validUntilTime ; + sh:pattern "^\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\dZ$" ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:intendedUse ], + [ sh:class ns1:Agent ; + sh:nodeKind sh:IRI ; + sh:path ns1:originatedBy ], + [ sh:datatype xsd:string ; + sh:nodeKind sh:Literal ; + sh:path ns1:standardName ], + [ sh:datatype xsd:dateTimeStamp ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:releaseTime ; + sh:pattern "^\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\dZ$" ], + [ sh:class ns1:SupportType ; + sh:in ( ) ; + sh:nodeKind sh:IRI ; + sh:path ns1:supportLevel ], + [ sh:message "https://spdx.org/rdf/3.1/terms/Core/Artifact is an abstract class and should not be instantiated directly. Instantiate a subclass instead."@en ; + sh:not [ sh:hasValue ns1:Artifact ] ; + sh:path rdf:type ] . + +ns1:LifecycleScopeType a owl:Class ; + rdfs:comment "Provide an enumerated set of lifecycle phases that can provide context to relationships."@en . + + a owl:Class ; + rdfs:comment "Abstract class representing a license combination consisting of one or more licenses."@en ; + rdfs:subClassOf ns1:Element ; + sh:nodeKind sh:IRI ; + sh:property [ sh:message "https://spdx.org/rdf/3.1/terms/SimpleLicensing/AnyLicenseInfo is an abstract class and should not be instantiated directly. Instantiate a subclass instead."@en ; + sh:not [ sh:hasValue ] ; + sh:path rdf:type ] . + +ns1:ProfileIdentifierType a owl:Class ; + rdfs:comment "Enumeration of the valid profiles."@en . + +ns3:DatasetType a owl:Class ; + rdfs:comment "Enumeration of dataset types."@en . + +ns1:Location a owl:Class, + sh:NodeShape ; + rdfs:comment "Location is used to define the location, address or coordinates of a place."@en ; + rdfs:subClassOf ns1:Element ; + sh:nodeKind sh:IRI ; + sh:property [ sh:message "https://spdx.org/rdf/3.1/terms/Core/Location is an abstract class and should not be instantiated directly. Instantiate a subclass instead."@en ; + sh:not [ sh:hasValue ns1:Location ] ; + sh:path rdf:type ], + [ sh:datatype xsd:dateTimeStamp ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:locationTime ; + sh:pattern "^\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\dZ$" ] . + +ns1:DictionaryEntry a owl:Class, + sh:NodeShape ; + rdfs:comment "A key with an associated value."@en ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:property [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:key ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:value ] . + +ns1:ExternalIdentifierType a owl:Class ; + rdfs:comment "Specifies the type of an external identifier."@en . + +ns1:HashAlgorithm a owl:Class ; + rdfs:comment "A mathematical algorithm that maps data of arbitrary size to a bit string."@en . + +ns1:Agent a owl:Class ; + rdfs:comment "Agent represents anything with the potential to act on a system."@en ; + rdfs:subClassOf ns1:Element ; + sh:nodeKind sh:IRI . + +ns6:SoftwarePurpose a owl:Class ; + rdfs:comment "Provides information about the primary purpose of an Element."@en . + +ns1:Element a owl:Class, + sh:NodeShape ; + rdfs:comment "Base domain class from which all other SPDX 3 domain classes derive."@en ; + sh:nodeKind sh:IRI ; + sh:property [ sh:class ns1:ExternalRef ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ns1:externalRef ], + [ sh:message "https://spdx.org/rdf/3.1/terms/Core/Element is an abstract class and should not be instantiated directly. Instantiate a subclass instead."@en ; + sh:not [ sh:hasValue ns1:Element ] ; + sh:path rdf:type ], + [ sh:class ns1:CreationInfo ; + sh:maxCount 1 ; + sh:minCount 1 ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ns1:creationInfo ], + [ sh:class ns1:IntegrityMethod ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ns1:verifiedUsing ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:summary ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:comment ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:name ], + [ sh:class ns1:ExternalIdentifier ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ns1:externalIdentifier ], + [ sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path ns1:description ], + [ sh:message "Class is known to not derive from Extension and cannot be used"@en ; + sh:not [ sh:or ( [ sh:class ns2:RequirementVerification ] [ sh:class ns2:EvidenceRelationship ] [ sh:class ns2:EvaluationResult ] [ sh:class ns4:SsvcVulnAssessmentRelationship ] [ sh:class ns4:CvssV2VulnAssessmentRelationship ] [ sh:class ns4:ExploitCatalogVulnAssessmentRelationship ] [ sh:class ns4:CvssV4VulnAssessmentRelationship ] [ sh:class ns4:VexAffectedVulnAssessmentRelationship ] [ sh:class ns4:VexNotAffectedVulnAssessmentRelationship ] [ sh:class ns4:CvssV3VulnAssessmentRelationship ] [ sh:class ns4:Vulnerability ] [ sh:class ns4:VexUnderInvestigationVulnAssessmentRelationship ] [ sh:class ns4:EpssVulnAssessmentRelationship ] [ sh:class ns4:VexFixedVulnAssessmentRelationship ] [ sh:class ns1:NamespaceMap ] [ sh:class ns1:LifecycleScopedRelationship ] [ sh:class ns1:ElementMap ] [ sh:class ns1:SupportRelationship ] [ sh:class ns1:Hash ] [ sh:class ns1:Agent ] [ sh:class ns1:MeasureOfLength ] [ sh:class ns1:CreationInfo ] [ sh:class ns1:ContactPointRelationship ] [ sh:class ns1:ExternalRef ] [ sh:class ns1:Specification ] [ sh:class ns1:Bom ] [ sh:class ns1:IndividualElement ] [ sh:class ns1:Relationship ] [ sh:class ns1:UnitOfMeasure ] [ sh:class ns1:PositiveIntegerRange ] [ sh:class ns1:DictionaryEntry ] [ sh:class ns1:ExternalMap ] [ sh:class ns1:Annotation ] [ sh:class ns1:DefinedType ] [ sh:class ns1:SpdxDocument ] [ sh:class ns1:Person ] [ sh:class ns1:Organization ] [ sh:class ns1:MeasureOfMass ] [ sh:class ns1:Bundle ] [ sh:class ns1:Tool ] [ sh:class ns1:Requirement ] [ sh:class ns1:ExternalIdentifier ] [ sh:class ns1:SoftwareAgent ] [ sh:class ns1:PackageVerificationCode ] [ sh:class ns1:PhysicalLocation ] [ sh:class ns1:Regulation ] [ sh:class ns5:AIPackage ] [ sh:class ns5:EnergyConsumptionDescription ] [ sh:class ns5:EnergyConsumption ] [ sh:class ] [ sh:class ns3:DatasetPackage ] [ sh:class ns9:CustomLicense ] [ sh:class ns9:OrLaterOperator ] [ sh:class ns9:ListedLicense ] [ sh:class ns9:DisjunctiveLicenseSet ] [ sh:class ns9:ListedLicenseException ] [ sh:class ns9:WithAdditionOperator ] [ sh:class ns9:IndividualLicensingInfo ] [ sh:class ns9:CustomLicenseAddition ] [ sh:class ns9:ConjunctiveLicenseSet ] [ sh:class ] [ sh:class ] [ sh:class ] [ sh:class ] [ sh:class ] [ sh:class ] [ sh:class ns7:StateAction ] [ sh:class ns7:State ] [ sh:class ns7:AssemblyAction ] [ sh:class ns7:BoundaryDefinitionProcess ] [ sh:class ns7:DestroyProcess ] [ sh:class ns7:ResponsibilityChangeAction ] [ sh:class ns7:PlanAction ] [ sh:class ns7:DestroyAction ] [ sh:class ns7:ChangeAction ] [ sh:class ns7:TransportProcess ] [ sh:class ns7:ResolutionAction ] [ sh:class ns7:TestProcess ] [ sh:class ns7:DefinedStateProcess ] [ sh:class ns7:StorageProcess ] [ sh:class ns7:StorageAction ] [ sh:class ns7:PlanProcess ] [ sh:class ns7:ReproduceAction ] [ sh:class ns7:ReproduceProcess ] [ sh:class ns7:BoundaryCrossingAction ] [ sh:class ns7:InspectionProcess ] [ sh:class ns7:InstantiateVirtualHardwareProcess ] [ sh:class ns7:AssemblyProcess ] [ sh:class ns7:ManufactureProcess ] [ sh:class ns7:BoundaryDefinitionAction ] [ sh:class ns7:OutOfSpecAction ] [ sh:class ns7:ResponsibilityChangeProcess ] [ sh:class ns7:HarvestProcess ] [ sh:class ns7:InspectionAction ] [ sh:class ns7:ChangeProcess ] [ sh:class ns7:ManufactureAction ] [ sh:class ns7:TransportAction ] [ sh:class ns7:HarvestAction ] [ sh:class ns7:TestAction ] [ sh:class ns8:SoftwareService ] [ sh:class ns6:Package ] [ sh:class ns6:File ] [ sh:class ns6:Sbom ] [ sh:class ns6:Snippet ] [ sh:class ns6:ContentIdentifier ] [ sh:class ns10:VirtualHardware ] [ sh:class ns10:Dimensions ] [ sh:class ns10:ProductSpecification ] [ sh:class ns10:PhysicalHardware ] [ sh:class ns10:BulkHardware ] ) ] ; + sh:path ns1:extension ], + [ sh:nodeKind sh:BlankNodeOrIRI ; + sh:path ns1:extension ] . + +ns1:ExternalRefType a owl:Class ; + rdfs:comment "Specifies the type of an external reference."@en . + +ns1:RelationshipType a owl:Class ; + rdfs:comment "Information about the relationship between two Elements."@en . + diff --git a/tests/data/spdx/README.md b/tests/data/spdx/README.md new file mode 100644 index 00000000..aaa18b7f --- /dev/null +++ b/tests/data/spdx/README.md @@ -0,0 +1,26 @@ +# SPDX 3 model fixtures + +Vendored, pinned snapshots of the SPDX 3 ontology, used by +`tests/test_python_spdx_protocols.py` to exercise codegen against a real, +large-scale model. +Fetched once and committed rather than downloaded at test time, so the tests +are deterministic and offline. + +## Contents + +| Directory | Version | Source | +|-------------|------------------------|----------------------------------------------------------------| +| `3.0.1/` | SPDX 3.0.1 (stable) | | +| | | | +| | | | +| `3.1-dev/` | SPDX 3.1 (pre-release) | | +| | | | +| | | | + +Fetched: 2026-08-28. + +## License + +These files are from the [spdx/spdx-3-model](https://github.com/spdx/spdx-3-model) +repository, published by the SPDX Working Group under the +[Community Specification License 1.0](https://github.com/spdx/spdx-3-model/blob/develop/License.md). diff --git a/tests/test_python_lazy_loading.py b/tests/test_python_lazy_loading.py index 30e3bfe8..01a5af14 100644 --- a/tests/test_python_lazy_loading.py +++ b/tests/test_python_lazy_loading.py @@ -107,7 +107,7 @@ def test_protocols_submodule_import_stays_lazy( output_dir = tmp_path / module_name shacl2code_generate( ["--input", TEST_MODEL, "--context", test_context_url], - ["--include-protocols", "yes"], + ["--include-protocols", "iri"], output_dir, ) diff --git a/tests/test_python_protocols.py b/tests/test_python_protocols.py index 83a4c50b..1754ca5f 100644 --- a/tests/test_python_protocols.py +++ b/tests/test_python_protocols.py @@ -4,6 +4,7 @@ # SPDX-License-Identifier: MIT import importlib +import json import os import subprocess import sys @@ -17,7 +18,7 @@ import rdflib -from shacl2code.lang.python import protocols_use_datetime +from shacl2code.lang.python import protocols_use_datetime, protocols_use_object_refs from shacl2code.model import Class, Model, Property from shacl2code.urlcontext import UrlContext @@ -115,19 +116,58 @@ def test_raises_on_unknown_datatype(self) -> None: protocols_use_datetime([bad_class]) +class TestProtocolsUseObjectRefs: + """ + Direct, in-process unit tests for protocols_use_object_refs(). Exercises + both branches without going through code generation, so coverage doesn't + depend on incidental property ordering in a generated model. + """ + + def test_true_when_object_ref_property_present(self) -> None: + """TEST_MODEL has sh:class-typed scalar and list properties.""" + assert protocols_use_object_refs(_load_classes(TEST_MODEL)) is True + + def test_false_when_no_object_ref_property(self) -> None: + """NO_DATETIME_MODEL has only a plain string property.""" + assert protocols_use_object_refs(_load_classes(NO_DATETIME_MODEL)) is False + + def test_false_for_enum_property(self) -> None: + """ + An enum property also carries prop.class_id (pointing at the enum + type), but prop_shape() excludes it from has_ref -- confirms + protocols_use_object_refs() doesn't mistake an enum for an + object-reference. + """ + enum_prop = Property( + path="http://example.org/color", + varname="color", + class_id="http://example.org/Color", + enum_values=["red", "green"], + max_count=1, + ) + enum_only_class = Class( + _id="http://example.org/EnumOnlyClass", + clsname="EnumOnlyClass", + parent_ids=[], + derived_ids=[], + properties=[enum_prop], + ) + assert protocols_use_object_refs([enum_only_class]) is False + + def _generate_protocols_fixture( tmp_path_factory: pytest.TempPathFactory, test_context_url: str, model_path: Path, version: str, ) -> Tuple[Path, str]: - """Generate a --include-protocols yes module for one version fixture.""" + """Generate a --include-protocols iri module for one version fixture.""" tmp_directory = tmp_path_factory.mktemp(f"protocols_{version}") module_name = f"pymodel_{version}" output_dir = tmp_directory / module_name shacl2code_generate( ["--input", model_path, "--context", test_context_url], - ["--include-protocols", "yes"], + ["--include-protocols", "iri"], output_dir, ) (output_dir / "py.typed").touch() @@ -138,7 +178,7 @@ def _generate_protocols_fixture( def python_model_v1_protocols( tmp_path_factory: pytest.TempPathFactory, test_context_url: str ) -> Tuple[Path, str]: - """v1 model generated with --include-protocols yes.""" + """v1 model generated with --include-protocols iri.""" return _generate_protocols_fixture( tmp_path_factory, test_context_url, TEST_MODEL, "v1" ) @@ -148,7 +188,7 @@ def python_model_v1_protocols( def python_model_v2_protocols( tmp_path_factory: pytest.TempPathFactory, test_context_url: str ) -> Tuple[Path, str]: - """v2 model (backward-compatible extension) generated with --include-protocols yes.""" + """v2 model (backward-compatible extension) generated with --include-protocols iri.""" return _generate_protocols_fixture( tmp_path_factory, test_context_url, TEST_V2_MODEL, "v2" ) @@ -158,7 +198,7 @@ def python_model_v2_protocols( def python_model_v3_protocols( tmp_path_factory: pytest.TempPathFactory, test_context_url: str ) -> Tuple[Path, str]: - """v3 model (backward-compatible extension of v2) with --include-protocols yes.""" + """v3 model (backward-compatible extension of v2) with --include-protocols iri.""" return _generate_protocols_fixture( tmp_path_factory, test_context_url, TEST_V3_MODEL, "v3" ) @@ -168,7 +208,7 @@ def python_model_v3_protocols( def python_model_v4_protocols( tmp_path_factory: pytest.TempPathFactory, test_context_url: str ) -> Tuple[Path, str]: - """v4 model (backward-compatible extension of v3) with --include-protocols yes.""" + """v4 model (backward-compatible extension of v3) with --include-protocols iri.""" return _generate_protocols_fixture( tmp_path_factory, test_context_url, TEST_V4_MODEL, "v4" ) @@ -176,11 +216,11 @@ def python_model_v4_protocols( @pytest.fixture(scope="module") def python_model_no_datetime(tmp_path_factory: pytest.TempPathFactory) -> Path: - """--include-protocols yes generated from a model with no datetime property.""" + """--include-protocols iri generated from a model with no datetime property.""" output_dir = tmp_path_factory.mktemp("no_datetime") / "pymodel" shacl2code_generate( ["--input", NO_DATETIME_MODEL], - ["--include-protocols", "yes"], + ["--include-protocols", "iri"], output_dir, ) return output_dir @@ -250,7 +290,7 @@ def test_flake8_all_files( self, python_model_v1_protocols: Tuple[Path, str] ) -> None: """ - flake8 over the whole output directory with --include-protocols yes, + flake8 over the whole output directory with --include-protocols iri, not just protocols.py -- catches issues in the conditional protocols import inside __init__.py that a protocols.py-only check would miss. """ @@ -266,13 +306,13 @@ def test_flake8_no_datetime_properties( self, python_model_no_datetime: Path ) -> None: """ - protocols.py must not unconditionally import `datetime`. A model with - no datetime-typed property must not produce an unused import (F401). + protocols.py must not unconditionally import `datetime` or `Union`. + A model with no datetime-typed and no object-reference property must + not produce an unused import (F401). """ - assert ( - "import datetime" - not in (python_model_no_datetime / "protocols.py").read_text() - ) + protocols_src = (python_model_no_datetime / "protocols.py").read_text() + assert "import datetime" not in protocols_src + assert "Union" not in protocols_src subprocess.run( [ "flake8", @@ -310,7 +350,7 @@ def test_conformance_mypy( script = tmp_path / "conformance.py" script.write_text(textwrap.dedent(f"""\ - from typing import Any, Optional + from typing import Any, Iterable, Optional, Union import {module_name} from {module_name} import protocols @@ -322,10 +362,15 @@ def test_conformance_mypy( def set_scalar(o: protocols.test_class, v: Optional[str]) -> None: o.test_class_string_scalar_prop = v - # Object-ref Any read + Any-setter write through protocol. - def get_ref(o: protocols.test_class) -> Any: + # Object-ref typed read + Any-setter write through protocol. + def get_ref(o: protocols.test_class) -> Optional[Union[str, protocols.test_class]]: return o.test_class_class_prop + def get_ref_list( + o: protocols.test_class, + ) -> Iterable[Union[str, protocols.test_class]]: + return o.test_class_class_list_prop + def set_ref(o: protocols.test_class, v: {module_name}.test_class) -> None: o.test_class_class_prop = v @@ -462,7 +507,7 @@ def test_cross_version_mypy( script = tmp_path / "cross_version.py" script.write_text(textwrap.dedent(f"""\ - from typing import Any, Optional + from typing import Iterable, Optional, Union import {older_name}, {newer_name} from {older_name} import protocols as op @@ -477,10 +522,17 @@ def get_scalar(o: op.test_class) -> Optional[str]: get_scalar(a) - # Object-ref typed read through older protocol on newer object. - def get_ref(o: op.test_class) -> Any: + # Object-ref typed read through older protocol on newer object: + # newer's own class_prop (typed with newer's own concrete class) + # still satisfies older's precisely-typed Protocol getter. + def get_ref(o: op.test_class) -> Optional[Union[str, op.test_class]]: return o.test_class_class_prop + def get_ref_list( + o: op.test_class, + ) -> Iterable[Union[str, op.test_class]]: + return o.test_class_class_list_prop + # Any-setter write through older protocol on newer object. def set_ref(o: op.test_class, v: {newer_name}.test_class) -> None: o.test_class_class_prop = v @@ -526,7 +578,7 @@ def test_prerelease_with_protocols(tmp_path: Path) -> None: output_dir = tmp_path / "pymodel_prerelease_protocols" shacl2code_generate( ["--input", PRERELEASE_MODEL], - ["--include-protocols", "yes"], + ["--include-protocols", "iri"], output_dir, ) assert "IS_PRERELEASE = True" in (output_dir / "__init__.py").read_text() @@ -544,3 +596,132 @@ def test_prerelease_with_protocols(tmp_path: Path) -> None: "pymodel_prerelease_protocols." ): del sys.modules[m] + + +def _write_versioned_book_model(directory: Path, version: str) -> Tuple[Path, Path]: + """A single-class toy model whose class IRI embeds `version`, mirroring + SPDX's own practice of putting its spec version in every term IRI (e.g. + https://spdx.org/rdf/3.0.1/terms/Core/CreationInfo vs .../3.1/terms/...). + The compact term names ("Book", "title") stay the same across versions -- + only the context's target IRIs change -- mirroring SPDX's own context + files (verified directly against spdx.org's 3.0.1 and 3.1 contexts). + """ + base = f"http://example.org/toy/{version}" + ttl = directory / f"book-{version}.ttl" + ttl.write_text(f"""\ +@prefix rdfs: . +@prefix sh: . +@prefix owl: . +@prefix xsd: . + +<{base}/Book> a rdfs:Class, sh:NodeShape, owl:Class ; + sh:property [ + sh:datatype xsd:string ; + sh:path <{base}/Book/title> ; + sh:maxCount 1 + ] . +""") + context = directory / f"book-{version}-context.json" + context.write_text( + json.dumps( + { + "@context": { + "Book": f"{base}/Book", + "title": {"@id": f"{base}/Book/title"}, + } + } + ) + ) + return ttl, context + + +class TestProtocolDiscriminatorKey: + """ + --include-protocols's discriminator key choice ("iri" vs + "compact-name"), exercised against a toy model whose class IRI embeds a + version segment -- the pattern SPDX itself uses. Existing fixtures + (test.ttl/test-v2..v4.ttl) keep class IRIs stable across versions, so + they can't exercise this. + """ + + @pytest.mark.parametrize( + ("key", "expect_success"), + [ + pytest.param( + "compact-name", + True, + id="compact-name-survives", + ), + pytest.param( + "iri", + False, + id="iri-breaks", + ), + ], + ) + def test_discriminator_key_vs_versioned_class_iris( + self, tmp_path: Path, key: str, expect_success: bool + ) -> None: + """ + 'compact-name' keys the discriminator by the --context-compacted + class name, which this toy model (like SPDX) keeps stable across + versions even though the underlying class IRI changes -- so a newer + Book still satisfies an older Book Protocol. 'iri' keys it by the + class's full IRI, which this toy model changes between versions, so + a newer Book does NOT satisfy an older Book Protocol -- proving the + 'compact-name' fix is real, not a no-op. + """ + v1_ttl, v1_ctx = _write_versioned_book_model(tmp_path, "1.0.0") + v2_ttl, v2_ctx = _write_versioned_book_model(tmp_path, "2.0.0") + + v1_dir = tmp_path / "book_v1" + shacl2code_generate( + [ + "--input", + v1_ttl, + "--context-url", + v1_ctx, + "http://example.org/toy/1.0.0/context.json", + ], + ["--include-protocols", key], + v1_dir, + ) + v2_dir = tmp_path / "book_v2" + shacl2code_generate( + [ + "--input", + v2_ttl, + "--context-url", + v2_ctx, + "http://example.org/toy/2.0.0/context.json", + ], + ["--include-protocols", key], + v2_dir, + ) + + env = _env_with_pythonpath(v1_dir, v2_dir) + script = tmp_path / "cross_version.py" + script.write_text(textwrap.dedent("""\ + import book_v1, book_v2 + from book_v1 import protocols as p1 + + b: p1.Book = book_v2.Book() + """)) + if expect_success: + subprocess.run( + ["mypy", "--strict", str(script)], + encoding="utf-8", + env=env, + check=True, + ) + else: + result = subprocess.run( + ["mypy", "--strict", str(script)], + encoding="utf-8", + env=env, + capture_output=True, + ) + assert result.returncode != 0, ( + "Expected mypy to reject book_v2.Book as book_v1.protocols.Book " + f"under the {key!r} discriminator key (versioned class IRIs)" + ) diff --git a/tests/test_python_spdx_protocols.py b/tests/test_python_spdx_protocols.py new file mode 100644 index 00000000..9cfcc0b0 --- /dev/null +++ b/tests/test_python_spdx_protocols.py @@ -0,0 +1,524 @@ +# SPDX-FileContributor: Arthit Suriyawongkul +# SPDX-FileCopyrightText: 2026 Joshua Watt +# SPDX-FileType: SOURCE +# SPDX-License-Identifier: MIT +""" +Cross-version Protocol compatibility against the real SPDX 3 ontology. + +The toy fixtures in test_python_protocols.py exercise the mechanism (a +class IRI that embeds a version segment, --include-protocols compact-name +vs. iri) in isolation. This file locks the same mechanism in against the +real, unmodified SPDX 3.0.1 and 3.1-dev models (168 classes, multiple +inheritance, enums, named individuals, and profiles like AI/Hardware) +vendored under tests/data/spdx/ -- see tests/data/spdx/README.md for +provenance and license. A future change to protocol_discriminator_name(), +prop_shape(), or the protocols.py.j2/model.py.j2 templates could pass every +toy-model test and still silently break compatibility against a real-world +ontology shaped like SPDX; these tests are what would catch that. + +Coverage beyond the base classes (Element/CreationInfo/Tool/Relationship): +- Enum-typed properties (relationshipType, ai_autonomyType), whose values + are NAMED_INDIVIDUALS-backed IRI constants, not raw strings -- a subtlety + that only surfaces at runtime, not under mypy, since the property's + static type is plain ``Optional[str]``. +- Named individuals as sentinel values (NoAssertionLicense/NoneLicense). +- A class from the AI profile (ai_AIPackage), not just Core. +- A class that exists ONLY in 3.1-dev (hardware_PhysicalHardware, from the + Hardware profile) but subclasses a Core class present in 3.0.1 + (Artifact/Element) -- proving a 3.0.1-typed function accepts a type + introduced by a later spec version it was never written against. +""" + +import importlib +import os +import subprocess +import sys +import textwrap +from pathlib import Path +from typing import Iterable, Optional, Tuple, Union, get_type_hints + +import pytest + +THIS_FILE = Path(__file__) +THIS_DIR = THIS_FILE.parent +SPDX_DIR = THIS_DIR / "data" / "spdx" + +SPDX301_DIR = SPDX_DIR / "3.0.1" +SPDX301_CONTEXT_URL = "https://spdx.org/rdf/3.0/spdx-context.jsonld" + +SPDX31DEV_DIR = SPDX_DIR / "3.1-dev" +SPDX31DEV_CONTEXT_URL = "https://spdx.org/rdf/3.1/spdx-context.jsonld" + +# The vendored .ttl/.jsonld files are excluded from the sdist (see +# pyproject.toml's [tool.hatch.build.targets.sdist] and +# tests/data/spdx/README.md's license note) -- they're only present in a +# git checkout. Skip gracefully rather than fail when they're absent. +pytestmark = pytest.mark.skipif( + not (SPDX301_DIR / "spdx-model.ttl").exists() + or not (SPDX31DEV_DIR / "spdx-model.ttl").exists(), + reason=( + "vendored SPDX fixture data not present (excluded from sdist; " + "needs a full git checkout -- see tests/data/spdx/README.md)" + ), +) + + +def shacl2code_generate(args, python_args, outfile): + p = subprocess.run( + [ + "shacl2code", + "generate", + ] + + args + + ["python"] + + python_args + + [ + "--output", + outfile, + ], + check=True, + stdout=subprocess.PIPE, + encoding="utf-8", + ) + + # Add a py.typed file for type checking + (outfile / "py.typed").touch() + return p + + +def _env_with_pythonpath(*paths: Path) -> "dict[str, str]": + """A copy of the current environment with `paths` appended to PYTHONPATH.""" + env = os.environ.copy() + env["PYTHONPATH"] = os.pathsep.join( + env.get("PYTHONPATH", "").split(os.pathsep) + [str(p) for p in paths] + ) + return env + + +def _assert_typechecks_and_runs(script: Path, env: "dict[str, str]") -> None: + """mypy --strict proves the *types* line up; actually running it proves + construction succeeds too (e.g. enum properties validate against a + fixed set of IRIs at runtime -- see PresenceType/RelationshipType's + NAMED_INDIVIDUALS -- which mypy's Any-typed setters can't catch).""" + r = subprocess.run( + ["mypy", "--strict", str(script)], + capture_output=True, + encoding="utf-8", + env=env, + ) + assert r.returncode == 0, r.stdout + r.stderr + + r = subprocess.run( + ["python", str(script)], + capture_output=True, + encoding="utf-8", + env=env, + ) + assert r.returncode == 0, r.stdout + r.stderr + + +def _generate_spdx( + tmp_path_factory: pytest.TempPathFactory, + name: str, + model_dir: Path, + context_url: str, + pre_release: bool, +) -> Tuple[Path, str]: + """Generate a --include-protocols compact-name module for one SPDX version. + + compact-name is required, not a stylistic choice: SPDX embeds its own + spec version directly in every class/property IRI (e.g. + https://spdx.org/rdf/3.0.1/terms/Core/CreationInfo vs. + .../3.1/terms/Core/CreationInfo), so the 'iri' discriminator key would + differ between 3.0.1 and 3.1-dev for every class -- see + test_python_protocols.py::TestProtocolDiscriminatorKey for the same + mechanism demonstrated on a minimal toy model. + """ + outdir = tmp_path_factory.mktemp(name) + module_name = f"spdx_{name}" + args = [ + "--input", + model_dir / "spdx-model.ttl", + "--input", + model_dir / "spdx-json-serialize-annotations.ttl", + "--context-url", + model_dir / "spdx-context.jsonld", + context_url, + ] + if pre_release: + args.append("--pre-release") + shacl2code_generate( + args, + ["--include-protocols", "compact-name"], + outdir / module_name, + ) + return outdir, module_name + + +@pytest.fixture(scope="module") +def spdx301_pkg(tmp_path_factory: pytest.TempPathFactory) -> Tuple[Path, str]: + """SPDX 3.0.1, generated once and shared by every test in this module.""" + return _generate_spdx( + tmp_path_factory, "spdx301", SPDX301_DIR, SPDX301_CONTEXT_URL, False + ) + + +@pytest.fixture(scope="module") +def spdx31dev_pkg(tmp_path_factory: pytest.TempPathFactory) -> Tuple[Path, str]: + """SPDX 3.1-dev (pre-release), generated once and shared.""" + return _generate_spdx( + tmp_path_factory, "spdx31dev", SPDX31DEV_DIR, SPDX31DEV_CONTEXT_URL, True + ) + + +class TestSpdxProtocolSignature: + """ + The generated protocols.py for a real, large ontology is well-formed + Python and has the shape the codegen intends, at a scale (168 classes, + multiple inheritance, named individuals) the smaller toy fixtures don't + reach. + """ + + def test_flake8(self, spdx301_pkg: Tuple[Path, str]) -> None: + module_path, module_name = spdx301_pkg + r = subprocess.run( + [ + "flake8", + "--max-line-length=100", + str(module_path / module_name / "protocols.py"), + ], + capture_output=True, + encoding="utf-8", + ) + assert r.returncode == 0, r.stdout + r.stderr + + def test_black(self, spdx301_pkg: Tuple[Path, str]) -> None: + # Scoped to protocols.py, not the whole package: model.py/model.pyi + # are unrelated to the Protocol feature and carry pre-existing, + # local-black-version-only false-positive reformat hunks at + # real-world scale (see tests/test_python_protocols.py's own + # comments on this) that would make this test flaky for reasons + # this file isn't meant to guard against. + module_path, module_name = spdx301_pkg + r = subprocess.run( + ["black", "--check", str(module_path / module_name / "protocols.py")], + capture_output=True, + encoding="utf-8", + ) + assert r.returncode == 0, r.stdout + r.stderr + + def test_expected_classes_and_shape(self, spdx301_pkg: Tuple[Path, str]) -> None: + # Imports and inspects the actual runtime shape rather than + # substring-matching generated source text, so this doesn't break + # on a purely cosmetic codegen formatting change. + module_path, module_name = spdx301_pkg + sys.path.insert(0, str(module_path)) + try: + protocols = importlib.import_module(f"{module_name}.protocols") + + # Base class with no properties of its own beyond the + # discriminator. + assert protocols.SHACLObjectProtocol in protocols.CreationInfo.__bases__ + # Single inheritance. + assert protocols.Element in protocols.Relationship.__bases__ + assert protocols.Element in protocols.Tool.__bases__ + assert protocols.Element in protocols.Agent.__bases__ + # Multiple levels of inheritance. + assert protocols.ElementCollection in protocols.SpdxDocument.__bases__ + assert protocols.Element in protocols.ElementCollection.__bases__ + + # Object-reference properties precisely typed (not Any) for + # reads. Discriminator keyed by compact name, matching the + # class's own name. + assert hasattr(protocols.CreationInfo, "_protocol_CreationInfo") + assert hasattr(protocols.Element, "_protocol_Element") + created_by_hints = get_type_hints(protocols.CreationInfo.createdBy.fget) + assert created_by_hints["return"] == Iterable[Union[str, protocols.Agent]] + + # AI profile: a class from a non-Core profile, subclassing a + # Software profile class, itself subclassing Core -- an enum + # property (ai_autonomyType, a PresenceType). + assert protocols.software_Package in protocols.ai_AIPackage.__bases__ + ai_hints = get_type_hints(protocols.ai_AIPackage) + assert ai_hints["ai_autonomyType"] == Optional[str] + + # Named individuals: NoAssertionLicense/NoneLicense are + # sentinel values (real IRIs, not synthetic test data) exposed + # as class-level str constants via NAMED_INDIVIDUALS. + individual_licensing_info = ( + protocols.expandedlicensing_IndividualLicensingInfo + ) + assert ( + protocols.simplelicensing_AnyLicenseInfo + in individual_licensing_info.__bases__ + ) + named_individual_hints = get_type_hints(individual_licensing_info) + assert named_individual_hints["NoAssertionLicense"] == str + assert named_individual_hints["NoneLicense"] == str + finally: + sys.path.remove(str(module_path)) + for m in list(sys.modules): + if m == module_name or m.startswith(module_name + "."): + del sys.modules[m] + + def test_mypy_strict(self, spdx301_pkg: Tuple[Path, str]) -> None: + module_path, module_name = spdx301_pkg + env = _env_with_pythonpath(module_path) + r = subprocess.run( + ["mypy", "--strict", str(module_path / module_name / "protocols.py")], + capture_output=True, + encoding="utf-8", + env=env, + ) + assert r.returncode == 0, r.stdout + r.stderr + + +class TestSpdxCrossVersionProtocols: + """ + Functions written once, typed against SPDX 3.0.1's Protocols, checked + against both SPDX 3.0.1 and SPDX 3.1-dev objects -- and a class that is + genuinely unrelated in the real model is still rejected. Mirrors the + ad-hoc verification done manually against live-fetched SPDX models + during development of this feature, now pinned as a regression test. + + Split by feature group (core/base classes vs. enum+named-individual+AI + profile properties) rather than one large script, so a failure in one + group doesn't bury the others in a single mypy/traceback dump. + """ + + def test_core_functions_accept_v301_and_v31dev_objects( + self, + tmp_path: Path, + spdx301_pkg: Tuple[Path, str], + spdx31dev_pkg: Tuple[Path, str], + ) -> None: + v301_path, v301_name = spdx301_pkg + v31dev_path, v31dev_name = spdx31dev_pkg + env = _env_with_pythonpath(v301_path, v31dev_path) + + script = tmp_path / "cross_version_accept_core.py" + script.write_text( + textwrap.dedent(f"""\ + from typing import Any, Iterable, Optional, Union + import {v301_name} + import {v31dev_name} + from {v301_name} import protocols as p1 + + # Functions written ONCE, typed against the OLDER (3.0.1) + # Protocols -- never mentioning {v301_name} or {v31dev_name} + # directly. + + def describe_creation_info( + ci: p1.CreationInfo, + ) -> Iterable[Union[str, p1.Agent]]: + return ci.createdBy + + def summarize_element(e: p1.Element) -> Optional[str]: + return e.name + + def relationship_targets( + r: p1.Relationship, + ) -> Iterable[Union[str, p1.Element]]: + return r.to + + def tag_tool(t: p1.Tool, comment: str) -> None: + t.comment = comment + + def set_creator(ci: p1.CreationInfo, agent: Any) -> None: + ci.createdBy = [agent] + + # --- OLDER (3.0.1) objects --- + older_ci = {v301_name}.CreationInfo( + createdBy=[{v301_name}.Agent(spdxId="urn:older-agent")] + ) + older_tool = {v301_name}.Tool(name="older-tool") + older_agent = {v301_name}.Agent(spdxId="urn:older-set-creator-agent") + older_rel = {v301_name}.Relationship(to=[older_tool]) + + describe_creation_info(older_ci) + summarize_element(older_tool) + summarize_element(older_rel) + relationship_targets(older_rel) + tag_tool(older_tool, "hello") + set_creator(older_ci, older_agent) + + # --- NEWER (3.1-dev) objects -- the actual cross-version proof: + # these functions were never written against {v31dev_name} at + # all, yet accept its objects. + newer_ci = {v31dev_name}.CreationInfo( + createdBy=[{v31dev_name}.Agent(spdxId="urn:newer-agent")] + ) + newer_tool = {v31dev_name}.Tool(name="newer-tool") + newer_agent = {v31dev_name}.Agent(spdxId="urn:newer-set-creator-agent") + newer_rel = {v31dev_name}.Relationship(to=[newer_tool]) + + describe_creation_info(newer_ci) + summarize_element(newer_tool) + summarize_element(newer_rel) + relationship_targets(newer_rel) + tag_tool(newer_tool, "hello from 3.1-dev") + set_creator(newer_ci, newer_agent) + """) + ) + + _assert_typechecks_and_runs(script, env) + + def test_enum_and_named_individual_properties_accept_v301_and_v31dev_objects( + self, + tmp_path: Path, + spdx301_pkg: Tuple[Path, str], + spdx31dev_pkg: Tuple[Path, str], + ) -> None: + """ + The properties/values that don't show up in the toy fixtures: + enum-typed properties (relationshipType, ai_autonomyType) backed by + NAMED_INDIVIDUALS IRI constants, a class from a non-Core profile + (ai_AIPackage, from AI), and named-individual sentinel values + (NoAssertionLicense/NoneLicense). + """ + v301_path, v301_name = spdx301_pkg + v31dev_path, v31dev_name = spdx31dev_pkg + env = _env_with_pythonpath(v301_path, v31dev_path) + + script = tmp_path / "cross_version_accept_enums.py" + script.write_text( + textwrap.dedent(f"""\ + from typing import Optional, Union + import {v301_name} + import {v31dev_name} + from {v301_name} import protocols as p1 + + def relationship_type(r: p1.Relationship) -> Optional[str]: + # Enum-typed property (RelationshipType named individuals). + return r.relationshipType + + def summarize_ai_package(pkg: p1.ai_AIPackage) -> Optional[str]: + # AI profile: subclasses Software profile's Package, which + # subclasses Core's Artifact/Element. + return pkg.name + + def ai_autonomy(pkg: p1.ai_AIPackage) -> Optional[str]: + # Enum-typed property (PresenceType named individuals). + return pkg.ai_autonomyType + + def license_or_id( + value: Union[str, p1.expandedlicensing_IndividualLicensingInfo], + ) -> str: + # Named individuals: NoAssertionLicense/NoneLicense are + # sentinel IRIs exposed as class-level str constants. + return str(value) + + # --- OLDER (3.0.1) objects --- + older_rel = {v301_name}.Relationship( + to=[{v301_name}.Tool(name="older-target")], + relationshipType={v301_name}.RelationshipType.describes, + ) + older_ai_pkg = {v301_name}.ai_AIPackage( + name="older-ai-pkg", + ai_autonomyType={v301_name}.PresenceType.yes, + ) + + relationship_type(older_rel) + summarize_ai_package(older_ai_pkg) + ai_autonomy(older_ai_pkg) + license_or_id( + {v301_name}.expandedlicensing_IndividualLicensingInfo.NoAssertionLicense + ) + + # --- NEWER (3.1-dev) objects -- never written against + # {v31dev_name} at all, yet accepted. + newer_rel = {v31dev_name}.Relationship( + to=[{v31dev_name}.Tool(name="newer-target")], + relationshipType={v31dev_name}.RelationshipType.describes, + ) + newer_ai_pkg = {v31dev_name}.ai_AIPackage( + name="newer-ai-pkg", + ai_autonomyType={v31dev_name}.PresenceType.noAssertion, + ) + + relationship_type(newer_rel) + summarize_ai_package(newer_ai_pkg) + ai_autonomy(newer_ai_pkg) + license_or_id( + {v31dev_name}.expandedlicensing_IndividualLicensingInfo.NoneLicense + ) + """) + ) + + _assert_typechecks_and_runs(script, env) + + def test_v301_typed_functions_accept_v31dev_only_subclass( + self, + tmp_path: Path, + spdx301_pkg: Tuple[Path, str], + spdx31dev_pkg: Tuple[Path, str], + ) -> None: + """ + Future-proofing: a function typed against a 3.0.1 Protocol accepts + an instance of a class that didn't exist when that Protocol was + generated -- hardware_PhysicalHardware, from 3.1-dev's Hardware + profile (absent from 3.0.1 entirely), whose ancestry + (hardware_Hardware -> Artifact -> Element) reaches back to Core + classes that DID exist in 3.0.1. Structural typing means the + function only needs the ancestor's shape and discriminator, so any + future subclass of an existing class -- from a profile that didn't + exist yet, added by a later spec version -- satisfies it + automatically, with no re-generation of the older side required. + """ + v301_path, v301_name = spdx301_pkg + v31dev_path, v31dev_name = spdx31dev_pkg + env = _env_with_pythonpath(v301_path, v31dev_path) + + script = tmp_path / "future_proof.py" + script.write_text( + textwrap.dedent(f"""\ + from typing import Optional + import {v301_name} + import {v31dev_name} + from {v301_name} import protocols as p1 + + # Typed against 3.0.1's Artifact Protocol -- written before + # the Hardware profile (3.1-only) existed. + def artifact_summary(a: p1.Artifact) -> Optional[str]: + return a.name + + hw = {v31dev_name}.hardware_PhysicalHardware(name="future-proof-hw") + artifact_summary(hw) + """) + ) + + _assert_typechecks_and_runs(script, env) + + def test_unrelated_class_rejected_by_protocol( + self, tmp_path: Path, spdx301_pkg: Tuple[Path, str] + ) -> None: + module_path, module_name = spdx301_pkg + env = _env_with_pythonpath(module_path) + + script = tmp_path / "cross_version_reject.py" + script.write_text( + textwrap.dedent(f"""\ + import {module_name} + from {module_name} import protocols as p1 + + def summarize_element(e: p1.Element) -> None: + print(e.name) + + # CreationInfo does NOT inherit Element in the real SPDX model + # -- must be rejected. + bad = {module_name}.CreationInfo() + summarize_element(bad) + """) + ) + + r = subprocess.run( + ["mypy", "--strict", str(script)], + capture_output=True, + encoding="utf-8", + env=env, + ) + assert r.returncode != 0, ( + "Expected mypy to reject CreationInfo as Element " + "(they are unrelated in the real SPDX model)" + ) + assert "incompatible type" in r.stdout From 4a5e0d3f50eb2cdbc9e4a9f957bc0b86361843e9 Mon Sep 17 00:00:00 2001 From: Arthit Suriyawongkul Date: Sat, 29 Aug 2026 13:14:33 +0700 Subject: [PATCH 39/40] Fix formatting Signed-off-by: Arthit Suriyawongkul --- tests/test_python_spdx_protocols.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/tests/test_python_spdx_protocols.py b/tests/test_python_spdx_protocols.py index 9cfcc0b0..16965095 100644 --- a/tests/test_python_spdx_protocols.py +++ b/tests/test_python_spdx_protocols.py @@ -298,7 +298,8 @@ def test_core_functions_accept_v301_and_v31dev_objects( script = tmp_path / "cross_version_accept_core.py" script.write_text( - textwrap.dedent(f"""\ + textwrap.dedent( + f"""\ from typing import Any, Iterable, Optional, Union import {v301_name} import {v31dev_name} @@ -358,7 +359,8 @@ def set_creator(ci: p1.CreationInfo, agent: Any) -> None: relationship_targets(newer_rel) tag_tool(newer_tool, "hello from 3.1-dev") set_creator(newer_ci, newer_agent) - """) + """ + ) ) _assert_typechecks_and_runs(script, env) @@ -382,7 +384,8 @@ def test_enum_and_named_individual_properties_accept_v301_and_v31dev_objects( script = tmp_path / "cross_version_accept_enums.py" script.write_text( - textwrap.dedent(f"""\ + textwrap.dedent( + f"""\ from typing import Optional, Union import {v301_name} import {v31dev_name} @@ -442,7 +445,8 @@ def license_or_id( license_or_id( {v31dev_name}.expandedlicensing_IndividualLicensingInfo.NoneLicense ) - """) + """ + ) ) _assert_typechecks_and_runs(script, env) @@ -471,7 +475,8 @@ def test_v301_typed_functions_accept_v31dev_only_subclass( script = tmp_path / "future_proof.py" script.write_text( - textwrap.dedent(f"""\ + textwrap.dedent( + f"""\ from typing import Optional import {v301_name} import {v31dev_name} @@ -484,7 +489,8 @@ def artifact_summary(a: p1.Artifact) -> Optional[str]: hw = {v31dev_name}.hardware_PhysicalHardware(name="future-proof-hw") artifact_summary(hw) - """) + """ + ) ) _assert_typechecks_and_runs(script, env) @@ -497,7 +503,8 @@ def test_unrelated_class_rejected_by_protocol( script = tmp_path / "cross_version_reject.py" script.write_text( - textwrap.dedent(f"""\ + textwrap.dedent( + f"""\ import {module_name} from {module_name} import protocols as p1 @@ -508,7 +515,8 @@ def summarize_element(e: p1.Element) -> None: # -- must be rejected. bad = {module_name}.CreationInfo() summarize_element(bad) - """) + """ + ) ) r = subprocess.run( From 9411c3c8e6f0e66297b075a08a683f3f3a352e07 Mon Sep 17 00:00:00 2001 From: Arthit Suriyawongkul Date: Sat, 29 Aug 2026 13:21:13 +0700 Subject: [PATCH 40/40] Fix black formatting Use latest black on Python 3.14 Signed-off-by: Arthit Suriyawongkul --- tests/test_python_spdx_protocols.py | 32 ++++++++--------------------- 1 file changed, 8 insertions(+), 24 deletions(-) diff --git a/tests/test_python_spdx_protocols.py b/tests/test_python_spdx_protocols.py index 16965095..cd872145 100644 --- a/tests/test_python_spdx_protocols.py +++ b/tests/test_python_spdx_protocols.py @@ -297,9 +297,7 @@ def test_core_functions_accept_v301_and_v31dev_objects( env = _env_with_pythonpath(v301_path, v31dev_path) script = tmp_path / "cross_version_accept_core.py" - script.write_text( - textwrap.dedent( - f"""\ + script.write_text(textwrap.dedent(f"""\ from typing import Any, Iterable, Optional, Union import {v301_name} import {v31dev_name} @@ -359,9 +357,7 @@ def set_creator(ci: p1.CreationInfo, agent: Any) -> None: relationship_targets(newer_rel) tag_tool(newer_tool, "hello from 3.1-dev") set_creator(newer_ci, newer_agent) - """ - ) - ) + """)) _assert_typechecks_and_runs(script, env) @@ -383,9 +379,7 @@ def test_enum_and_named_individual_properties_accept_v301_and_v31dev_objects( env = _env_with_pythonpath(v301_path, v31dev_path) script = tmp_path / "cross_version_accept_enums.py" - script.write_text( - textwrap.dedent( - f"""\ + script.write_text(textwrap.dedent(f"""\ from typing import Optional, Union import {v301_name} import {v31dev_name} @@ -445,9 +439,7 @@ def license_or_id( license_or_id( {v31dev_name}.expandedlicensing_IndividualLicensingInfo.NoneLicense ) - """ - ) - ) + """)) _assert_typechecks_and_runs(script, env) @@ -474,9 +466,7 @@ def test_v301_typed_functions_accept_v31dev_only_subclass( env = _env_with_pythonpath(v301_path, v31dev_path) script = tmp_path / "future_proof.py" - script.write_text( - textwrap.dedent( - f"""\ + script.write_text(textwrap.dedent(f"""\ from typing import Optional import {v301_name} import {v31dev_name} @@ -489,9 +479,7 @@ def artifact_summary(a: p1.Artifact) -> Optional[str]: hw = {v31dev_name}.hardware_PhysicalHardware(name="future-proof-hw") artifact_summary(hw) - """ - ) - ) + """)) _assert_typechecks_and_runs(script, env) @@ -502,9 +490,7 @@ def test_unrelated_class_rejected_by_protocol( env = _env_with_pythonpath(module_path) script = tmp_path / "cross_version_reject.py" - script.write_text( - textwrap.dedent( - f"""\ + script.write_text(textwrap.dedent(f"""\ import {module_name} from {module_name} import protocols as p1 @@ -515,9 +501,7 @@ def summarize_element(e: p1.Element) -> None: # -- must be rejected. bad = {module_name}.CreationInfo() summarize_element(bad) - """ - ) - ) + """)) r = subprocess.run( ["mypy", "--strict", str(script)],