Skip to content

Commit c1f0b1f

Browse files
[issue-593] add spdx2_document_from_scratch example
Signed-off-by: Armin Tänzer <armin.taenzer@tngtech.com>
1 parent 8d7d7b7 commit c1f0b1f

File tree

2 files changed

+150
-5
lines changed

2 files changed

+150
-5
lines changed

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ instead of `bin`.
9494

9595
## Library usage
9696
1. **DATA MODEL**
97-
* 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).
97+
* 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).
9898
* SPDX objects are implemented via `@dataclass_with_properties`, a custom extension of `@dataclass`.
9999
* 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.
100100
* 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`.
117117
Caution: Only valid documents can be serialized reliably; serialization of invalid documents is not supported.
118118

119119
## Example
120-
Here are some examples of possible use cases to quickly get you started with the spdx-tools:
120+
Here are some examples of possible use cases to quickly get you started with the spdx-tools.
121+
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).
121122
```python
122123
import logging
123124

124125
from license_expression import get_spdx_licensing
125126

126-
from spdx_tools.spdx.model import Checksum, ChecksumAlgorithm
127-
from spdx_tools.spdx.model import File, FileType
128-
from spdx_tools.spdx.model import Relationship, RelationshipType
127+
from spdx_tools.spdx.model import (Checksum, ChecksumAlgorithm, File,
128+
FileType, Relationship, RelationshipType)
129129
from spdx_tools.spdx.parser.parse_anything import parse_file
130130
from spdx_tools.spdx.validation.document_validator import validate_full_spdx_document
131131
from spdx_tools.spdx.writer.write_anything import write_file
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
# SPDX-FileCopyrightText: 2023 spdx contributors
2+
#
3+
# SPDX-License-Identifier: Apache-2.0
4+
import logging
5+
from datetime import datetime
6+
from typing import List
7+
8+
from license_expression import get_spdx_licensing
9+
10+
from spdx_tools.spdx.model import (
11+
Actor,
12+
ActorType,
13+
Checksum,
14+
ChecksumAlgorithm,
15+
CreationInfo,
16+
Document,
17+
ExternalPackageRef,
18+
ExternalPackageRefCategory,
19+
File,
20+
FileType,
21+
Package,
22+
PackagePurpose,
23+
PackageVerificationCode,
24+
Relationship,
25+
RelationshipType,
26+
)
27+
from spdx_tools.spdx.validation.document_validator import validate_full_spdx_document
28+
from spdx_tools.spdx.validation.validation_message import ValidationMessage
29+
from spdx_tools.spdx.writer.write_anything import write_file
30+
31+
# This example shows how to use the spdx-tools to create an SPDX document from scratch,
32+
# validate it and write it to a file.
33+
34+
# First up, we need general information about the creation of the document, summarised by the CreationInfo class.
35+
creation_info = CreationInfo(
36+
spdx_version="SPDX-2.3",
37+
spdx_id="SPDXRef-DOCUMENT",
38+
name="document name",
39+
data_license="CC0-1.0",
40+
document_namespace="https://some.namespace",
41+
creators=[Actor(ActorType.PERSON, "Jane Doe", "jane.doe@example.com")],
42+
created=datetime(2022, 1, 1),
43+
)
44+
45+
# creation_info is the only required property of the Document class (have a look there!), the rest are optional lists.
46+
# So, we are set up to create a new document instance.
47+
document = Document(creation_info)
48+
49+
# The document currently does not describe anything. Let's create a package that we can add to it.
50+
# The Package class has quite a few properties (have a look there!),
51+
# but only name, spdx_id and download_location are mandatory in SPDX v2.3.
52+
package = Package(
53+
name="package name",
54+
spdx_id="SPDXRef-Package",
55+
download_location="https://download.com",
56+
version="2.2.1",
57+
file_name="./foo.bar",
58+
supplier=Actor(ActorType.PERSON, "Jane Doe", "jane.doe@example.com"),
59+
originator=Actor(ActorType.ORGANIZATION, "some organization", "contact@example.com"),
60+
files_analyzed=True,
61+
verification_code=PackageVerificationCode(
62+
value="d6a770ba38583ed4bb4525bd96e50461655d2758", excluded_files=["./some.file"]
63+
),
64+
checksums=[
65+
Checksum(ChecksumAlgorithm.SHA1, "d6a770ba38583ed4bb4525bd96e50461655d2758"),
66+
Checksum(ChecksumAlgorithm.MD5, "624c1abb3664f4b35547e7c73864ad24"),
67+
],
68+
license_concluded=get_spdx_licensing().parse("GPL-2.0-only OR MIT"),
69+
license_info_from_files=[get_spdx_licensing().parse("GPL-2.0-only"), get_spdx_licensing().parse("MIT")],
70+
license_declared=get_spdx_licensing().parse("GPL-2.0-only AND MIT"),
71+
license_comment="license comment",
72+
copyright_text="Copyright 2022 Jane Doe",
73+
description="package description",
74+
attribution_texts=["package attribution"],
75+
primary_package_purpose=PackagePurpose.LIBRARY,
76+
release_date=datetime(2015, 1, 1),
77+
external_references=[
78+
ExternalPackageRef(
79+
category=ExternalPackageRefCategory.OTHER,
80+
reference_type="http://reference.type",
81+
locator="reference/locator",
82+
comment="external reference comment",
83+
)
84+
],
85+
)
86+
87+
# Now that we have a package defined, we can add it to the document's package property.
88+
document.packages = [package]
89+
90+
# A DESCRIBES relationship asserts that the document indeed describes the package.
91+
describes_relationship = Relationship("SPDXRef-Document", RelationshipType.DESCRIBES, "SPDXRef-Package")
92+
document.relationships = [describes_relationship]
93+
94+
# Let's add two files. Have a look at the file class for all possible properties a file can have.
95+
file1 = File(
96+
name="./package/file1.py",
97+
spdx_id="SPDXRef-File1",
98+
file_types=[FileType.SOURCE],
99+
checksums=[
100+
Checksum(ChecksumAlgorithm.SHA1, "d6a770ba38583ed4bb4525bd96e50461655d2758"),
101+
Checksum(ChecksumAlgorithm.MD5, "624c1abb3664f4b35547e7c73864ad24"),
102+
],
103+
license_concluded=get_spdx_licensing().parse("MIT"),
104+
license_info_in_file=[get_spdx_licensing().parse("MIT")],
105+
copyright_text="Copyright 2022 Jane Doe",
106+
)
107+
file2 = File(
108+
name="./package/file2.py",
109+
spdx_id="SPDXRef-File2",
110+
checksums=[
111+
Checksum(ChecksumAlgorithm.SHA1, "d6a770ba38583ed4bb4525bd96e50461655d2759"),
112+
],
113+
license_concluded=get_spdx_licensing().parse("GPL-2.0-only"),
114+
)
115+
116+
# Assuming the package contains those two files, we create two CONTAINS relationships.
117+
contains_relationship1 = Relationship("SPDXRef-Package", RelationshipType.CONTAINS, "SPDXRef-File1")
118+
contains_relationship2 = Relationship("SPDXRef-Package", RelationshipType.CONTAINS, "SPDXRef-File2")
119+
120+
# This library uses run-time type checks when assigning properties.
121+
# Because in-place alterations like .append() circumvent these checks, we don't use them here.
122+
document.relationships += [contains_relationship1, contains_relationship2]
123+
124+
# We now have created a document with basic creation information, describing a package that contains two files.
125+
# You can also add Annotations, Snippets and ExtractedLicensingInfo to the document in an analogous manner to the above.
126+
# Have a look at their respective classes if you are unsure about their properties.
127+
128+
129+
# This library provides comprehensive validation against the SPDX specification.
130+
# Note that details of the validation depend on the SPDX version of the document.
131+
validation_messages: List[ValidationMessage] = validate_full_spdx_document(document)
132+
133+
# You can have a look at each entry's message and context (like spdx_id, parent_id, full_element)
134+
# which will help you pinpoint the location of the invalidity.
135+
for message in validation_messages:
136+
logging.warning(message.validation_message)
137+
logging.warning(message.context)
138+
139+
# If the document is valid, validation_messages will be empty.
140+
assert validation_messages == []
141+
142+
# Finally, we can serialize the document to any of the five supported formats.
143+
# Using the write_file() method from the write_anything module,
144+
# the format will be determined by the file ending: .spdx (tag-value), .json, .xml, .yaml. or .rdf (or .rdf.xml)
145+
write_file(document, "my_spdx_document.spdx.json")

0 commit comments

Comments
 (0)