Skip to content

Commit

Permalink
[issue-593] add spdx2_document_from_scratch example
Browse files Browse the repository at this point in the history
Signed-off-by: Armin Tänzer <armin.taenzer@tngtech.com>
  • Loading branch information
armintaenzertng committed Apr 19, 2023
1 parent 8d7d7b7 commit 3a4f8a4
Show file tree
Hide file tree
Showing 2 changed files with 150 additions and 5 deletions.
10 changes: 5 additions & 5 deletions README.md
Expand Up @@ -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
Expand All @@ -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
Expand Down
145 changes: 145 additions & 0 deletions 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")

0 comments on commit 3a4f8a4

Please sign in to comment.