From 3a4f8a484101ab0beed64af78c9879a23c07ec50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Armin=20T=C3=A4nzer?= Date: Wed, 19 Apr 2023 16:12:18 +0200 Subject: [PATCH] [issue-593] add spdx2_document_from_scratch example MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Armin Tänzer --- README.md | 10 +- examples/spdx2_document_from_scratch.py | 145 ++++++++++++++++++++++++ 2 files changed, 150 insertions(+), 5 deletions(-) create mode 100644 examples/spdx2_document_from_scratch.py diff --git a/README.md b/README.md index e5c345b26..eeb17f368 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ instead of `bin`. ## Library usage 1. **DATA MODEL** - * The `spdx_tools.spdx.model` package constitutes the internal SPDX v2.3 data model (v2.2 is a simply a subset of this). All relevant classes for SPDX document creation are exposed in the [__init__.py](src%2Fspdx_tools%2Fspdx%2Fmodel%2F__init__.py). + * The `spdx_tools.spdx.model` package constitutes the internal SPDX v2.3 data model (v2.2 is simply a subset of this). All relevant classes for SPDX document creation are exposed in the `__init__.py` found [here](src%2Fspdx_tools%2Fspdx%2Fmodel%2F__init__.py). * SPDX objects are implemented via `@dataclass_with_properties`, a custom extension of `@dataclass`. * Each class starts with a list of its properties and their possible types. When no default value is provided, the property is mandatory and must be set during initialization. * Using the type hints, type checking is enforced when initializing a new instance or setting/getting a property on an instance @@ -117,15 +117,15 @@ instead of `bin`. Caution: Only valid documents can be serialized reliably; serialization of invalid documents is not supported. ## Example -Here are some examples of possible use cases to quickly get you started with the spdx-tools: +Here are some examples of possible use cases to quickly get you started with the spdx-tools. +If you want a more comprehensive example about how to create an SPDX document from scratch, have a look [here](examples%2Fspdx2_document_from_scratch.py). ```python import logging from license_expression import get_spdx_licensing -from spdx_tools.spdx.model import Checksum, ChecksumAlgorithm -from spdx_tools.spdx.model import File, FileType -from spdx_tools.spdx.model import Relationship, RelationshipType +from spdx_tools.spdx.model import (Checksum, ChecksumAlgorithm, File, + FileType, Relationship, RelationshipType) from spdx_tools.spdx.parser.parse_anything import parse_file from spdx_tools.spdx.validation.document_validator import validate_full_spdx_document from spdx_tools.spdx.writer.write_anything import write_file diff --git a/examples/spdx2_document_from_scratch.py b/examples/spdx2_document_from_scratch.py new file mode 100644 index 000000000..24276d307 --- /dev/null +++ b/examples/spdx2_document_from_scratch.py @@ -0,0 +1,145 @@ +# SPDX-FileCopyrightText: 2023 spdx contributors +# +# SPDX-License-Identifier: Apache-2.0 +import logging +from datetime import datetime +from typing import List + +from license_expression import get_spdx_licensing + +from spdx_tools.spdx.model import ( + Actor, + ActorType, + Checksum, + ChecksumAlgorithm, + CreationInfo, + Document, + ExternalPackageRef, + ExternalPackageRefCategory, + File, + FileType, + Package, + PackagePurpose, + PackageVerificationCode, + Relationship, + RelationshipType, +) +from spdx_tools.spdx.validation.document_validator import validate_full_spdx_document +from spdx_tools.spdx.validation.validation_message import ValidationMessage +from spdx_tools.spdx.writer.write_anything import write_file + +# This example shows how to use the spdx-tools to create an SPDX document from scratch, +# validate it and write it to a file. + +# First up, we need general information about the creation of the document, summarised by the CreationInfo class. +creation_info = CreationInfo( + spdx_version="SPDX-2.3", + spdx_id="SPDXRef-DOCUMENT", + name="document name", + data_license="CC0-1.0", + document_namespace="https://some.namespace", + creators=[Actor(ActorType.PERSON, "Jane Doe", "jane.doe@example.com")], + created=datetime(2022, 1, 1), +) + +# creation_info is the only required property of the Document class (have a look there!), the rest are optional lists. +# So, we are set up to create a new document instance. +document = Document(creation_info) + +# The document currently does not describe anything. Let's create a package that we can add to it. +# The Package class has quite a few properties (have a look there!), +# but only name, spdx_id and download_location are mandatory in SPDX v2.3. +package = Package( + name="package name", + spdx_id="SPDXRef-Package", + download_location="https://download.com", + version="2.2.1", + file_name="./foo.bar", + supplier=Actor(ActorType.PERSON, "Jane Doe", "jane.doe@example.com"), + originator=Actor(ActorType.ORGANIZATION, "some organization", "contact@example.com"), + files_analyzed=True, + verification_code=PackageVerificationCode( + value="d6a770ba38583ed4bb4525bd96e50461655d2758", excluded_files=["./some.file"] + ), + checksums=[ + Checksum(ChecksumAlgorithm.SHA1, "d6a770ba38583ed4bb4525bd96e50461655d2758"), + Checksum(ChecksumAlgorithm.MD5, "624c1abb3664f4b35547e7c73864ad24"), + ], + license_concluded=get_spdx_licensing().parse("GPL-2.0-only OR MIT"), + license_info_from_files=[get_spdx_licensing().parse("GPL-2.0-only"), get_spdx_licensing().parse("MIT")], + license_declared=get_spdx_licensing().parse("GPL-2.0-only AND MIT"), + license_comment="license comment", + copyright_text="Copyright 2022 Jane Doe", + description="package description", + attribution_texts=["package attribution"], + primary_package_purpose=PackagePurpose.LIBRARY, + release_date=datetime(2015, 1, 1), + external_references=[ + ExternalPackageRef( + category=ExternalPackageRefCategory.OTHER, + reference_type="http://reference.type", + locator="reference/locator", + comment="external reference comment", + ) + ], +) + +# Now that we have a package defined, we can add it to the document's package property. +document.packages = [package] + +# A DESCRIBES relationship asserts that the document indeed describes the package. +describes_relationship = Relationship("SPDXRef-Document", RelationshipType.DESCRIBES, "SPDXRef-Package") +document.relationships = [describes_relationship] + +# Let's add two files. Have a look at the file class for all possible properties a file can have. +file1 = File( + name="./package/file1.py", + spdx_id="SPDXRef-File1", + file_types=[FileType.SOURCE], + checksums=[ + Checksum(ChecksumAlgorithm.SHA1, "d6a770ba38583ed4bb4525bd96e50461655d2758"), + Checksum(ChecksumAlgorithm.MD5, "624c1abb3664f4b35547e7c73864ad24"), + ], + license_concluded=get_spdx_licensing().parse("MIT"), + license_info_in_file=[get_spdx_licensing().parse("MIT")], + copyright_text="Copyright 2022 Jane Doe", +) +file2 = File( + name="./package/file2.py", + spdx_id="SPDXRef-File2", + checksums=[ + Checksum(ChecksumAlgorithm.SHA1, "d6a770ba38583ed4bb4525bd96e50461655d2759"), + ], + license_concluded=get_spdx_licensing().parse("GPL-2.0-only"), +) + +# Assuming the package contains those two files, we create two CONTAINS relationships. +contains_relationship1 = Relationship("SPDXRef-Package", RelationshipType.CONTAINS, "SPDXRef-File1") +contains_relationship2 = Relationship("SPDXRef-Package", RelationshipType.CONTAINS, "SPDXRef-File2") + +# This library uses run-time type checks when assigning properties. +# Because in-place alterations like .append() circumvent these checks, we don't use them here. +document.relationships += [contains_relationship1, contains_relationship2] + +# We now have created a document with basic creation information, describing a package that contains two files. +# You can also add Annotations, Snippets and ExtractedLicensingInfo to the document in an analogous manner to the above. +# Have a look at their respective classes if you are unsure about their properties. + + +# This library provides comprehensive validation against the SPDX specification. +# Note that details of the validation depend on the SPDX version of the document. +validation_messages: List[ValidationMessage] = validate_full_spdx_document(document) + +# You can have a look at each entry's message and context (like spdx_id, parent_id, full_element) +# which will help you pinpoint the location of the invalidity. +for message in validation_messages: + logging.warning(message.validation_message) + logging.warning(message.context) + +# If the document is valid, validation_messages will be empty. +assert validation_messages == [] + +# Finally, we can serialize the document to any of the five supported formats. +# Using the write_file() method from the write_anything module, +# the format will be determined by the file ending: .spdx (tag-value), .json, .xml, .yaml. or .rdf (or .rdf.xml) +write_file(document, "my_spdx_document.spdx.json")