diff --git a/CHANGELOG.md b/CHANGELOG.md index 797be5ba9..e5689c4ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## (unreleased) + +### New features and changes + +* experimental SPDX 3.0 support, built on the `spdx-python-model` bindings: + * read a SPDX 3 JSON-LD file into memory via `spdx_tools.spdx3.parser.parse_file` + ## v0.8.5 (2026-03-13) ### New features and changes diff --git a/pyproject.toml b/pyproject.toml index e773d841c..3035e717b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,6 +37,7 @@ dependencies = [ "pyyaml", "rdflib", "semantic_version", + "spdx-python-model>=0.0.6", "uritools", "xmltodict", ] @@ -44,9 +45,9 @@ dynamic = ["version"] [project.optional-dependencies] test = ["pyshacl", "pytest", "tzdata"] -code_style = ["black", "flake8", "isort"] +code_style = ["black", "flake8", "isort", "mypy"] graph_generation = ["networkx", "pygraphviz"] -development = ["black", "flake8", "isort", "networkx", "pyshacl", "pytest"] +development = ["black", "flake8", "isort", "mypy", "networkx", "pyshacl", "pytest"] [project.scripts] pyspdxtools = "spdx_tools.spdx.clitools.pyspdxtools:main" @@ -59,6 +60,32 @@ Repository = "https://github.com/spdx/tools-python.git" Issues = "https://github.com/spdx/tools-python/issues" Changelog = "https://github.com/spdx/tools-python/blob/main/CHANGELOG.md" +[tool.mypy] +python_version = "3.10" +strict = true +mypy_path = "src" +explicit_package_bases = true +# The new SPDX 3 code is fully typed and checked under --strict. +# The legacy SPDX 2 modules are not strict-clean yet and are out of scope here; +# they are excluded below so they don't mask issues in the new code. +files = [ + "src/spdx_tools/spdx3/formats.py", + "src/spdx_tools/spdx3/object_set.py", + "src/spdx_tools/spdx3/parser", +] + +# spdx-python-model ships a py.typed marker but its generated bindings expose +# many attributes as Any; this keeps strict checks meaningful without noise. +[[tool.mypy.overrides]] +module = ["spdx_python_model.*"] +ignore_missing_imports = true + +# Pre-existing SPDX 2 code imported by the new modules (e.g. SPDXParsingError) +# is not yet strict-typed; do not fail the new-code check on it. +[[tool.mypy.overrides]] +module = ["spdx_tools.spdx.*"] +ignore_errors = true + [tool.setuptools] include-package-data = true @@ -78,6 +105,7 @@ release = "clean --all sdist --formats=gztar bdist_wheel" [tool.black] line-length = 119 +target-version = ["py310", "py311", "py312", "py313", "py314"] include = "(^/src/.*.py|^/tests/.*.py)" [tool.isort] diff --git a/src/spdx_tools/spdx3/formats.py b/src/spdx_tools/spdx3/formats.py new file mode 100644 index 000000000..3a770ae4f --- /dev/null +++ b/src/spdx_tools/spdx3/formats.py @@ -0,0 +1,21 @@ +# SPDX-FileCopyrightText: 2026-present SPDX contributors +# SPDX-License-Identifier: Apache-2.0 +from enum import Enum, auto + +from spdx_tools.spdx.parser.error import SPDXParsingError + + +class FileFormat(Enum): + JSON_LD = auto() + + +def file_name_to_format(file_name: str) -> FileFormat: + # SPDX 3.0 currently defines a single serialization: JSON-LD. + # Common extensions are "spdx3.json", ".json" and ".jsonld". + # See: + # https://github.com/OpenChain-Project/Telco-WG/blob/main/OpenChain-Telco-SBOM-Guide_1.2_DRAFT_EN.md + # https://www.iana.org/assignments/media-types/application/spdx3+json + if file_name.endswith(".json") or file_name.endswith(".jsonld"): + return FileFormat.JSON_LD + else: + raise SPDXParsingError(["Unsupported SPDX 3 file type: " + str(file_name)]) diff --git a/src/spdx_tools/spdx3/object_set.py b/src/spdx_tools/spdx3/object_set.py new file mode 100644 index 000000000..41cb13e71 --- /dev/null +++ b/src/spdx_tools/spdx3/object_set.py @@ -0,0 +1,22 @@ +# SPDX-FileCopyrightText: 2026-present SPDX contributors +# SPDX-License-Identifier: Apache-2.0 +from typing import Any, Iterable, Optional, Protocol, Set, runtime_checkable + + +@runtime_checkable +class SpdxObjectSet(Protocol): + """Version-agnostic structural type for a SHACL object set. + + Satisfied by ``SHACLObjectSet`` from any ``spdx_python_model.vX_Y_Z`` + version module, so callers are not tied to a specific SPDX spec version. + """ + + def foreach(self) -> Iterable[Any]: ... + + def foreach_type(self, typ: Any, *, match_subclass: bool = True) -> Iterable[Any]: ... + + def find_by_id(self, _id: str, default: Optional[Any] = None) -> Optional[Any]: ... + + def add(self, obj: Any) -> Any: ... + + def link(self) -> Set[str]: ... diff --git a/src/spdx_tools/spdx3/parser/__init__.py b/src/spdx_tools/spdx3/parser/__init__.py new file mode 100644 index 000000000..631f0ae42 --- /dev/null +++ b/src/spdx_tools/spdx3/parser/__init__.py @@ -0,0 +1,5 @@ +# SPDX-FileCopyrightText: 2026-present SPDX contributors +# SPDX-License-Identifier: Apache-2.0 +from spdx_tools.spdx3.parser.parse_anything import parse_file + +__all__ = ["parse_file"] diff --git a/src/spdx_tools/spdx3/parser/json_ld/__init__.py b/src/spdx_tools/spdx3/parser/json_ld/__init__.py new file mode 100644 index 000000000..2173e3181 --- /dev/null +++ b/src/spdx_tools/spdx3/parser/json_ld/__init__.py @@ -0,0 +1,2 @@ +# SPDX-FileCopyrightText: 2026-present SPDX contributors +# SPDX-License-Identifier: Apache-2.0 diff --git a/src/spdx_tools/spdx3/parser/json_ld/json_ld_parser.py b/src/spdx_tools/spdx3/parser/json_ld/json_ld_parser.py new file mode 100644 index 000000000..2afe74303 --- /dev/null +++ b/src/spdx_tools/spdx3/parser/json_ld/json_ld_parser.py @@ -0,0 +1,21 @@ +# SPDX-FileCopyrightText: 2026-present SPDX contributors +# SPDX-License-Identifier: Apache-2.0 +from spdx_python_model import v3_0_1 as spdx_3_0 + +from spdx_tools.spdx3.object_set import SpdxObjectSet +from spdx_tools.spdx.parser.error import SPDXParsingError + + +def parse_from_file(file_name: str, encoding: str = "utf-8") -> SpdxObjectSet: + """Read a SPDX 3 JSON-LD file into a SHACLObjectSet (the in-memory representation + provided by the spdx-python-model bindings).""" + object_set = spdx_3_0.SHACLObjectSet() + try: + # The binding's deserializer reads from a binary stream. + with open(file_name, "rb") as file: + spdx_3_0.JSONLDDeserializer().read(file, object_set) + except OSError as err: + raise SPDXParsingError([f"Could not open file {file_name}: {err}"]) + except Exception as err: + raise SPDXParsingError([f"Error while parsing {file_name}: {err}"]) + return object_set diff --git a/src/spdx_tools/spdx3/parser/parse_anything.py b/src/spdx_tools/spdx3/parser/parse_anything.py new file mode 100644 index 000000000..58fd3bc14 --- /dev/null +++ b/src/spdx_tools/spdx3/parser/parse_anything.py @@ -0,0 +1,18 @@ +# SPDX-FileCopyrightText: 2026-present SPDX contributors +# SPDX-License-Identifier: Apache-2.0 +from spdx_tools.spdx3.formats import FileFormat, file_name_to_format +from spdx_tools.spdx3.object_set import SpdxObjectSet +from spdx_tools.spdx3.parser.json_ld import json_ld_parser +from spdx_tools.spdx.parser.error import SPDXParsingError + + +def parse_file(file_name: str, encoding: str = "utf-8") -> SpdxObjectSet: + """Parse a SPDX 3 file into a SHACLObjectSet, dispatching on the file format. + + SPDX 3.0 currently defines a single serialization (JSON-LD); the dispatch is + kept to mirror the SPDX 2 ``parse_file`` API and to ease adding formats later. + """ + input_format = file_name_to_format(file_name) + if input_format == FileFormat.JSON_LD: + return json_ld_parser.parse_from_file(file_name, encoding) + raise SPDXParsingError([f"Unsupported SPDX 3 file format: {input_format}"]) diff --git a/src/spdx_tools/spdx3/py.typed b/src/spdx_tools/spdx3/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/tests/spdx3/conftest.py b/tests/spdx3/conftest.py new file mode 100644 index 000000000..2173e3181 --- /dev/null +++ b/tests/spdx3/conftest.py @@ -0,0 +1,2 @@ +# SPDX-FileCopyrightText: 2026-present SPDX contributors +# SPDX-License-Identifier: Apache-2.0 diff --git a/tests/spdx3/data/example.spdx3.json b/tests/spdx3/data/example.spdx3.json new file mode 100644 index 000000000..7dd85c050 --- /dev/null +++ b/tests/spdx3/data/example.spdx3.json @@ -0,0 +1,426 @@ +{ + "@context" : "https://spdx.org/rdf/3.0.1/spdx-context.jsonld", + "@graph" : [ { + "@id" : "_:creationInfo_0", + "type" : "CreationInfo", + "specVersion" : "3.0.1", + "createdBy" : [ "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd0" ], + "createdUsing" : [ "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/additionalToolSPDXRef-gnrtd2", "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/additionalToolSPDXRef-gnrtd1" ], + "created" : "2024-11-18T10:22:12Z" + }, { + "@id" : "_:creationInfo_1", + "type" : "CreationInfo", + "specVersion" : "3.0.1", + "createdBy" : [ "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd40" ], + "createdUsing" : [ "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/additionalToolSPDXRef-gnrtd41", "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/additionalToolSPDXRef-gnrtd42" ], + "created" : "2024-11-18T10:22:12Z" + }, { + "spdxId" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd3", + "type" : "Relationship", + "relationshipType" : "describes", + "completeness" : "noAssertion", + "to" : [ "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd4" ], + "from" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/document0", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd5", + "type" : "Relationship", + "relationshipType" : "contains", + "completeness" : "noAssertion", + "to" : [ "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd6" ], + "from" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd4", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd7", + "type" : "Relationship", + "relationshipType" : "generates", + "completeness" : "noAssertion", + "to" : [ "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd4" ], + "from" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd6", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd8", + "type" : "Relationship", + "relationshipType" : "hasConcludedLicense", + "to" : [ "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd9" ], + "from" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd6", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd10", + "type" : "Relationship", + "relationshipType" : "hasDeclaredLicense", + "to" : [ "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd9" ], + "from" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd6", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd11", + "type" : "Relationship", + "relationshipType" : "contains", + "completeness" : "noAssertion", + "to" : [ "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd12" ], + "from" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd4", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd13", + "type" : "Relationship", + "relationshipType" : "hasTestCase", + "to" : [ "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd12" ], + "completeness" : "noAssertion", + "from" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd4", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd14", + "type" : "Relationship", + "relationshipType" : "hasConcludedLicense", + "to" : [ "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd9" ], + "from" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd12", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd15", + "type" : "Relationship", + "relationshipType" : "hasDeclaredLicense", + "to" : [ "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd9" ], + "from" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd12", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd16", + "type" : "Relationship", + "relationshipType" : "hasDynamicLink", + "to" : [ "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd4" ], + "completeness" : "noAssertion", + "from" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd17", + "comment" : "Relationship based on Maven POM file dependency information", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd18", + "type" : "Relationship", + "relationshipType" : "hasConcludedLicense", + "to" : [ "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd19" ], + "from" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd17", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd20", + "type" : "Relationship", + "relationshipType" : "hasDeclaredLicense", + "to" : [ "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd19" ], + "from" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd17", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd21", + "type" : "Relationship", + "relationshipType" : "hasDynamicLink", + "to" : [ "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd4" ], + "completeness" : "noAssertion", + "from" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd22", + "comment" : "Relationship based on Maven POM file dependency information", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd23", + "type" : "Relationship", + "relationshipType" : "hasConcludedLicense", + "to" : [ "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd19" ], + "from" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd22", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd24", + "type" : "Relationship", + "relationshipType" : "hasDeclaredLicense", + "to" : [ "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd19" ], + "from" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd22", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd25", + "type" : "Relationship", + "relationshipType" : "hasDynamicLink", + "to" : [ "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd4" ], + "completeness" : "noAssertion", + "from" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd26", + "comment" : "Relationship based on Maven POM file dependency information", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd27", + "type" : "Relationship", + "relationshipType" : "hasConcludedLicense", + "to" : [ "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd19" ], + "from" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd26", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd28", + "type" : "Relationship", + "relationshipType" : "hasDeclaredLicense", + "to" : [ "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd19" ], + "from" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd26", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd29", + "type" : "Relationship", + "relationshipType" : "hasDynamicLink", + "to" : [ "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd4" ], + "completeness" : "noAssertion", + "from" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd30", + "comment" : "Relationship based on Maven POM file dependency information", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd31", + "type" : "Relationship", + "relationshipType" : "hasConcludedLicense", + "to" : [ "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd19" ], + "from" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd30", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd32", + "type" : "Relationship", + "relationshipType" : "hasDeclaredLicense", + "to" : [ "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd19" ], + "from" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd30", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd33", + "type" : "Relationship", + "relationshipType" : "hasConcludedLicense", + "to" : [ "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd9" ], + "from" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd4", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd36", + "type" : "Relationship", + "relationshipType" : "hasDistributionArtifact", + "to" : [ "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd35" ], + "completeness" : "complete", + "from" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd4", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd38", + "type" : "Relationship", + "relationshipType" : "hasDeclaredLicense", + "to" : [ "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd9" ], + "from" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd4", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd39", + "type" : "Relationship", + "relationshipType" : "amendedBy", + "to" : [ "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/document0" ], + "completeness" : "noAssertion", + "from" : "http://spdx.org/spdxdocs/examplemaven-0.0.1#SPDXRef-DOCUMENT", + "comment" : "The original document and been enriched by the Parlay application", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd46", + "type" : "Relationship", + "relationshipType" : "hasConcludedLicense", + "to" : [ "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd19" ], + "from" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd44", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd48", + "type" : "Relationship", + "relationshipType" : "hasDeclaredLicense", + "to" : [ "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd49" ], + "from" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd44", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/document0", + "type" : "SpdxDocument", + "dataLicense" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd43", + "rootElement" : [ "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd4" ], + "import" : [ { + "type" : "ExternalMap", + "verifiedUsing" : [ { + "type" : "Hash", + "algorithm" : "sha1", + "hashValue" : "3f9deeef2efdbb0eb4b15ec216f5c4e3af2d13e2" + } ], + "externalSpdxId" : "http://spdx.org/spdxdocs/examplemaven-0.0.1#SPDXRef-DOCUMENT", + "locationHint" : "http://spdx.org/spdxdocs/examplemaven-0.0.1" + } ], + "name" : "examplemaven", + "namespaceMap" : [ { + "type" : "NamespaceMap", + "prefix" : "DocumentRef-original", + "namespace" : "http://spdx.org/spdxdocs/examplemaven-0.0.1#" + } ], + "creationInfo" : "_:creationInfo_1" + }, { + "spdxId" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/additionalToolSPDXRef-gnrtd1", + "type" : "Tool", + "name" : "spdx-maven-plugin", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/additionalToolSPDXRef-gnrtd2", + "type" : "Tool", + "name" : "Parlay", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/additionalToolSPDXRef-gnrtd41", + "type" : "Tool", + "name" : "spdx-maven-plugin", + "creationInfo" : "_:creationInfo_1" + }, { + "spdxId" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/additionalToolSPDXRef-gnrtd42", + "type" : "Tool", + "name" : "Parlay", + "creationInfo" : "_:creationInfo_1" + }, { + "spdxId" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd9", + "type" : "simplelicensing_LicenseExpression", + "simplelicensing_licenseExpression" : "Apache-2.0", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd19", + "type" : "simplelicensing_LicenseExpression", + "simplelicensing_licenseExpression" : "NOASSERTION", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd43", + "type" : "simplelicensing_LicenseExpression", + "simplelicensing_licenseExpression" : "CC0-1.0", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd49", + "type" : "simplelicensing_LicenseExpression", + "simplelicensing_licenseExpression" : "CPL-1.0", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd45", + "type" : "LifecycleScopedRelationship", + "relationshipType" : "dependsOn", + "scope" : "test", + "to" : [ "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd44" ], + "completeness" : "noAssertion", + "from" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd4", + "comment" : "Relationship created based on Maven POM information", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd34", + "type" : "Organization", + "name" : "Linux Foundation", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd37", + "type" : "Organization", + "name" : "SPDX", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd47", + "type" : "Organization", + "name" : "JUnit", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd0", + "type" : "Person", + "name" : "Gary O'Neall", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd40", + "type" : "Person", + "name" : "Gary O'Neall", + "creationInfo" : "_:creationInfo_1" + }, { + "spdxId" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd6", + "type" : "software_File", + "software_copyrightText" : "Copyright (c) 2020 Source Auditor Inc.", + "verifiedUsing" : [ { + "type" : "Hash", + "algorithm" : "sha1", + "hashValue" : "a6f47dbc7e4615058490055172fe0065c55f8fc5" + } ], + "software_attributionText" : [ "SPDX-License-Identifier: Apache-2.0\nCopyright (c) 2022 Source Auditor Inc." ], + "name" : "./src/main/java/org/spdx/examplemaven/App.java", + "software_primaryPurpose" : "source", + "comment" : "This file contains SPDX-License-Identifiers for Apache-2.0", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd12", + "type" : "software_File", + "software_copyrightText" : "Copyright (c) 2020 Source Auditor Inc.", + "verifiedUsing" : [ { + "type" : "Hash", + "algorithm" : "sha1", + "hashValue" : "4b4df52d36588c8e9482d56eebc42336447f3dad" + } ], + "software_attributionText" : [ "SPDX-License-Identifier: Apache-2.0\nCopyright (c) 2022 Source Auditor Inc." ], + "name" : "./src/test/java/org/spdx/examplemaven/AppTest.java", + "software_primaryPurpose" : "source", + "comment" : "This file contains SPDX-License-Identifiers for Apache-2.0", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd35", + "type" : "software_File", + "verifiedUsing" : [ { + "type" : "Hash", + "algorithm" : "sha1", + "hashValue" : "b8a7e6c75001e6d78625cfc9a3103bf121abf8b4" + } ], + "name" : "examplemaven-0.0.1.jar", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd4", + "type" : "software_Package", + "software_copyrightText" : "Copyright (c) 2022 Source Auditor Inc.", + "suppliedBy" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd37", + "verifiedUsing" : [ { + "type" : "PackageVerificationCode", + "algorithm" : "sha1", + "hashValue" : "c12417def36d7804096521de4280721e5863e68b" + } ], + "name" : "examplemaven", + "software_primaryPurpose" : "library", + "software_downloadLocation" : "NOASSERTION", + "summary" : "This is a simple example Maven project created using the Maven quickstart archetype with one dependency added.", + "software_packageVersion" : "0.0.1", + "software_homePage" : "https://github.com/spdx/spdx-examples", + "description" : "This is a simple example Maven project created using the Maven quickstart archetype with one dependency added.", + "originatedBy" : [ "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd34" ], + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd17", + "type" : "software_Package", + "software_copyrightText" : "UNSPECIFIED", + "software_downloadLocation" : "NOASSERTION", + "summary" : "The Apache Log4j Implementation", + "description" : "The Apache Log4j Implementation", + "name" : "Apache Log4j Core", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd22", + "type" : "software_Package", + "software_copyrightText" : "UNSPECIFIED", + "software_downloadLocation" : "NOASSERTION", + "summary" : "The Apache Log4j API", + "description" : "The Apache Log4j API", + "name" : "Apache Log4j API", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd26", + "type" : "software_Package", + "software_copyrightText" : "UNSPECIFIED", + "software_downloadLocation" : "NOASSERTION", + "summary" : "The slf4j API", + "software_homePage" : "http://www.slf4j.org", + "description" : "The slf4j API", + "name" : "SLF4J API Module", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd30", + "type" : "software_Package", + "software_copyrightText" : "UNSPECIFIED", + "software_downloadLocation" : "NOASSERTION", + "summary" : "The Apache Log4j SLF4J API binding to Log4j 2 Core", + "description" : "The Apache Log4j SLF4J API binding to Log4j 2 Core", + "name" : "Apache Log4j SLF4J Binding", + "creationInfo" : "_:creationInfo_0" + }, { + "spdxId" : "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd44", + "type" : "software_Package", + "software_copyrightText" : "UNSPECIFIED", + "software_downloadLocation" : "NOASSERTION", + "summary" : "JUnit is a regression testing framework written by Erich Gamma and Kent Beck. It is used by the developer who implements unit tests in Java.", + "software_packageVersion" : "3.8.1", + "software_homePage" : "http://junit.org", + "description" : "JUnit is a regression testing framework written by Erich Gamma and Kent Beck. It is used by the developer who implements unit tests in Java.", + "name" : "JUnit", + "originatedBy" : [ "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd47" ], + "creationInfo" : "_:creationInfo_0" + } ] +} \ No newline at end of file diff --git a/tests/spdx3/parser/__init__.py b/tests/spdx3/parser/__init__.py new file mode 100644 index 000000000..2173e3181 --- /dev/null +++ b/tests/spdx3/parser/__init__.py @@ -0,0 +1,2 @@ +# SPDX-FileCopyrightText: 2026-present SPDX contributors +# SPDX-License-Identifier: Apache-2.0 diff --git a/tests/spdx3/parser/test_read.py b/tests/spdx3/parser/test_read.py new file mode 100644 index 000000000..f2238a588 --- /dev/null +++ b/tests/spdx3/parser/test_read.py @@ -0,0 +1,38 @@ +# SPDX-FileCopyrightText: 2026-present SPDX contributors +# SPDX-License-Identifier: Apache-2.0 +import os + +import pytest + +from spdx_tools.spdx3.object_set import SpdxObjectSet +from spdx_tools.spdx3.parser import parse_file +from spdx_tools.spdx.parser.error import SPDXParsingError + +EXAMPLE_FILE = os.path.join(os.path.dirname(__file__), os.pardir, "data", "example.spdx3.json") + + +def test_parse_file_returns_object_set(): + object_set = parse_file(EXAMPLE_FILE) + + assert isinstance(object_set, SpdxObjectSet) + + elements = list(object_set.foreach()) + assert len(elements) == 60 + + relationships = list(object_set.foreach_type("Relationship")) + assert len(relationships) == 28 + + +def test_parse_file_find_by_id(): + object_set = parse_file(EXAMPLE_FILE) + + spdx_id = "http://spdx.org/spdxdocs/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd0" + element = object_set.find_by_id(spdx_id) + + assert element is not None + assert str(element._id) == spdx_id + + +def test_parse_file_unsupported_extension(): + with pytest.raises(SPDXParsingError): + parse_file("some_file.rdf") diff --git a/tests/spdx3/writer/tag_value/test_write_document.py b/tests/spdx3/writer/tag_value/test_write_document.py deleted file mode 100644 index 10d5d54c6..000000000 --- a/tests/spdx3/writer/tag_value/test_write_document.py +++ /dev/null @@ -1,42 +0,0 @@ -# SPDX-FileCopyrightText: 2024 spdx contributors -# -# SPDX-License-Identifier: Apache-2.0 -import io -from datetime import datetime - -from semantic_version import Version - -from spdx_tools.spdx3.model import CreationInfo, ProfileIdentifierType, SpdxDocument -from spdx_tools.spdx3.writer.console.spdx_document_writer import write_spdx_document - - -def test_render_creation_info(): - fake_datetime = datetime(year=2024, month=1, day=1) - spec_version = Version("3.0.0") - creation_info = CreationInfo( - spec_version=spec_version, - created=fake_datetime, - created_by=[], - profile=[ProfileIdentifierType.SOFTWARE], - ) - spdx_document = SpdxDocument( - spdx_id="SPDXRef-FOO", - name="BAR", - element=[], - root_element=[], - creation_info=creation_info, - ) - output_str = io.StringIO() - write_spdx_document(spdx_document, text_output=output_str) - - assert output_str.getvalue() == """\ -## SPDX Document -SPDXID: SPDXRef-FOO -name: BAR -# Creation Information - specVersion: 3.0.0 - created: 2024-01-01T00:00:00Z - profile: SOFTWARE - data license: CC0-1.0 -elements: -""" # noqa: W291 # elements: are printed with a space