Open In Colab

In [1]:
# SPDX-FileCopyrightText: 2025-present Arthit Suriyawongkul <suriyawa@tcd.ie>
# SPDX-FileType: SOURCE
# SPDX-License-Identifier: Apache-2.0

Using SPDX 3 in Python with spdx-python-model¶

  • This tutorial demonstrates how to use the spdx-python-model library to:

    • read the software bill of materials (SBOM) data in the System Package Data Exchange (SPDX) format from an SPDX 3 JSON file;
    • access SPDX objects and their relationships; and
    • create a simple SPDX object (also demonstrating the built-in data validation of the library)
  • The SBOM 3 JSON file examples are taken from AI Example 2 and Software Example 14 of the spdx-examples repository.

  • This documentation is a contribution to the NTIA Conformance Checker as part of the Google Summer of Code 2025 program (mentors: Gary O'Neall and John Speed Meyers).

by @bact - Arthit Suriyawongkul

Change log¶

  • Update: 21 May 2026 - fix handling of unresolved references (spdxId) (reported by @vargenau and suggested a fix by @benjarobin - thank you both!)
  • Update: 23 February 2026 - add a section on SPDX object creation and validation
  • First published: 7 July 2025

Install the spdx-python-model library¶

In [2]:
!pip install spdx-python-model
Requirement already satisfied: spdx-python-model in /usr/local/lib/python3.12/dist-packages (0.0.5)

Import and check library version¶

In [3]:
from spdx_python_model import VERSION, bindings

print(f"spdx-python-model library version: {VERSION}")

print("Available SPDX bindings in spdx_python_model module:")
for name in dir(bindings):
  if not name.startswith("__"):
    print(name)
spdx-python-model library version: 0.0.5
Available SPDX bindings in spdx_python_model module:
v3_0_1

To use a Python binding, import a specific version of the model.

For example, to import a binding for SPDX model version 3.0.1, import v3_0_1.

In [4]:
from spdx_python_model import v3_0_1

Or you may like to assign it to a versionless alias, so you can update the version easily later.

For example, this will set the import alias as spdx3.

In [5]:
from spdx_python_model import v3_0_1 as spdx3

If a new version (e.g., v3_1) is released, you only need to update the import line and can keep the rest of your code intact.

For example, from spdx_python_model import v3_1 as spdx3.

Once imported, we can now access SPDX 3 model.

Let's print some "named individuals" from the model.

In [6]:
list(spdx3.NAMED_INDIVIDUALS)[:8]
Out[6]:
['https://spdx.org/rdf/3.0.1/terms/Core/ExternalIdentifierType/email',
 'https://spdx.org/rdf/3.0.1/terms/Dataset/DatasetAvailabilityType/registration',
 'https://spdx.org/rdf/3.0.1/terms/Core/RelationshipType/hasDocumentation',
 'https://spdx.org/rdf/3.0.1/terms/Core/RelationshipType/usesTool',
 'https://spdx.org/rdf/3.0.1/terms/Dataset/DatasetType/audio',
 'https://spdx.org/rdf/3.0.1/terms/Software/SbomType/analyzed',
 'https://spdx.org/rdf/3.0.1/terms/Core/RelationshipType/hasDataFile',
 'https://spdx.org/rdf/3.0.1/terms/Core/ExternalIdentifierType/urlScheme']

Some of these could be entries in SPDX 3 vocabularies (enum).

Read SPDX data from SPDX 3 JSON file¶

The library offers a built-in JSON-LD deserializer. To use it, provide the path to your SPDX 3 JSON file and an instance of SHACLObjectSet. The deserializer will read the JSON file and populate the SHACLObjectSet.

SHACLObjectSet holds SHACLObjects, which are the primary data structures for interaction within this library. It also includes helpful utilities, such as a generator (iterator) for accessing objects by their type.

In this example, we will first download the JSON file from the spdx-examples repository.

In [7]:
from urllib.request import urlretrieve

# url = "https://raw.githubusercontent.com/spdx/spdx-examples/refs/heads/master/ai/example02/spdx3.0/sbom.spdx3.json"
url = "https://raw.githubusercontent.com/spdx/spdx-examples/refs/heads/master/software/example14/spdx3.0/examplemaven-0.0.1-enriched.spdx3.json"
filename = "sbom.spdx.json"
filepath, _ = urlretrieve(url, filename)  # download from url; get the filepath

object_set = spdx3.SHACLObjectSet()

with open(filepath, "r", encoding="utf-8") as f:
  spdx3.JSONLDDeserializer().read(f, object_set)

print(object_set)
<spdx_python_model.bindings.v3_0_1.model.SHACLObjectSet object at 0x78724e1985c0>

Some internal works of SHACLObjectSet¶

Internally SHACLObjectSet keeps objects in its objects set.

In [8]:
# see what is inside
print(list(object_set.objects)[:3])
[<spdx_python_model.bindings.v3_0_1.model.Relationship object at 0x78724e156800>, <spdx_python_model.bindings.v3_0_1.model.software_Package object at 0x78724e075030>, <spdx_python_model.bindings.v3_0_1.model.Relationship object at 0x78724e157040>]

Internally, for faster lookup, SHACLObjectSet maintains two indexes: obj_by_id and obj_by_type.

obj_by_id is a dictionary where:

  • Keys are the spdxId property values of the objects.
  • Values are the corresponding SHACLObject instances.

obj_by_type is a dictionary where:

  • Keys are the type property values of the objects. (e.g., "SpdxDocument", "software_Package", "simplelicensing_LicenseExpression"). Note: Core Profile types do not require a prefix, while other Profiles do (e.g., "software_").
  • Values are sets of tuples. Each tuple contains:
    1. A boolean indicating if the SHACLObject in this tuple is an exact type match (True) or a subclass match (False); and
    2. The SHACLObject instance itself.
In [9]:
any_license_infos = object_set.obj_by_type["simplelicensing_AnyLicenseInfo"]
print(any_license_infos)
{(False, <spdx_python_model.bindings.v3_0_1.model.simplelicensing_LicenseExpression object at 0x78724d6f80e0>), (False, <spdx_python_model.bindings.v3_0_1.model.simplelicensing_LicenseExpression object at 0x78724d713060>), (False, <spdx_python_model.bindings.v3_0_1.model.simplelicensing_LicenseExpression object at 0x78724d710fe0>), (False, <spdx_python_model.bindings.v3_0_1.model.simplelicensing_LicenseExpression object at 0x78724d6f8180>)}

Let's begin with a quick check.

For example, we can see how many SpdxDocument objects are present and if it is according to the specification.

See: https://spdx.github.io/spdx-spec/v3.0.1/serializations/#serialization-information

In [10]:
spdx_documents = list(object_set.obj_by_type["SpdxDocument"])

if len(spdx_documents) > 1:
  print("Warning: A serialization must not contain more than one SpdxDocument.")
else:
  print("Looks good.")
Looks good.
In [11]:
# Get all objects of type "Relationship", including its subclasses
relationships = object_set.obj_by_type["Relationship"]
print("Relationship found:", len(relationships))
for o in relationships:
  print(o)
print()

# Get all objects of type "LifecycleScopedRelationship", including its subclasses
lifecycle_scoped_relationships = object_set.obj_by_type["LifecycleScopedRelationship"]
print("LifecycleScopedRelationship found:", len(lifecycle_scoped_relationships))
for o in lifecycle_scoped_relationships:
  print(o)
print()

# Get all objects of type "Softare/Package", including its subclasses
packages = object_set.obj_by_type["software_Package"]
print("software_Package found:", len(packages))
for o in packages:
  print(o)

print()
Relationship found: 28
(True, <spdx_python_model.bindings.v3_0_1.model.Relationship object at 0x78724e060280>)
(True, <spdx_python_model.bindings.v3_0_1.model.Relationship object at 0x78724e157280>)
(True, <spdx_python_model.bindings.v3_0_1.model.Relationship object at 0x78724e060340>)
(True, <spdx_python_model.bindings.v3_0_1.model.Relationship object at 0x78724e157340>)
(True, <spdx_python_model.bindings.v3_0_1.model.Relationship object at 0x78724e156800>)
(True, <spdx_python_model.bindings.v3_0_1.model.Relationship object at 0x78724e157e80>)
(True, <spdx_python_model.bindings.v3_0_1.model.Relationship object at 0x78724e060400>)
(True, <spdx_python_model.bindings.v3_0_1.model.Relationship object at 0x78724e0604c0>)
(True, <spdx_python_model.bindings.v3_0_1.model.Relationship object at 0x78724e1574c0>)
(True, <spdx_python_model.bindings.v3_0_1.model.Relationship object at 0x78724e060580>)
(True, <spdx_python_model.bindings.v3_0_1.model.Relationship object at 0x78724e1553c0>)
(False, <spdx_python_model.bindings.v3_0_1.model.LifecycleScopedRelationship object at 0x78724e060a00>)
(True, <spdx_python_model.bindings.v3_0_1.model.Relationship object at 0x78724e060640>)
(True, <spdx_python_model.bindings.v3_0_1.model.Relationship object at 0x78724e157640>)
(True, <spdx_python_model.bindings.v3_0_1.model.Relationship object at 0x78724e060700>)
(True, <spdx_python_model.bindings.v3_0_1.model.Relationship object at 0x78724e156bc0>)
(True, <spdx_python_model.bindings.v3_0_1.model.Relationship object at 0x78724e156c80>)
(True, <spdx_python_model.bindings.v3_0_1.model.Relationship object at 0x78724e1562c0>)
(True, <spdx_python_model.bindings.v3_0_1.model.Relationship object at 0x78724e157940>)
(True, <spdx_python_model.bindings.v3_0_1.model.Relationship object at 0x78724e156ec0>)
(True, <spdx_python_model.bindings.v3_0_1.model.Relationship object at 0x78724e157a00>)
(True, <spdx_python_model.bindings.v3_0_1.model.Relationship object at 0x78724e156f80>)
(True, <spdx_python_model.bindings.v3_0_1.model.Relationship object at 0x78724e157040>)
(True, <spdx_python_model.bindings.v3_0_1.model.Relationship object at 0x78724e060100>)
(True, <spdx_python_model.bindings.v3_0_1.model.Relationship object at 0x78724e157100>)
(True, <spdx_python_model.bindings.v3_0_1.model.Relationship object at 0x78724e157c40>)
(True, <spdx_python_model.bindings.v3_0_1.model.Relationship object at 0x78724e0601c0>)
(True, <spdx_python_model.bindings.v3_0_1.model.Relationship object at 0x78724e1571c0>)

LifecycleScopedRelationship found: 1
(True, <spdx_python_model.bindings.v3_0_1.model.LifecycleScopedRelationship object at 0x78724e060a00>)

software_Package found: 6
(True, <spdx_python_model.bindings.v3_0_1.model.software_Package object at 0x78724e074590>)
(True, <spdx_python_model.bindings.v3_0_1.model.software_Package object at 0x78724e0748c0>)
(True, <spdx_python_model.bindings.v3_0_1.model.software_Package object at 0x78724e075030>)
(True, <spdx_python_model.bindings.v3_0_1.model.software_Package object at 0x78724e1d2680>)
(True, <spdx_python_model.bindings.v3_0_1.model.software_Package object at 0x78724e074040>)
(True, <spdx_python_model.bindings.v3_0_1.model.software_Package object at 0x78724e074ae0>)

A proper way to get access to SPDX objects¶

While direct access to obj_by_id and obj_by_type is possible, it is generally recommended to use the provided access methods for better encapsulation. These methods include find_by_id and foreach_type.

SHACLObjectSet offers the find_by_id method to retrieve a specific object using its unique spdxId.

In [12]:
obj = object_set.find_by_id("https://spdx.org/spdxdocs/Sbom1-b1b4ff24-5ada-4c22-a9c9-7bd7121978ff")
print(obj)
None

SHACLObjectSet also provides foreach_type, a generator method that iterates over all objects of a specified type.

By default, foreach_type will also return instances of subclasses.

In [13]:
for obj in object_set.foreach_type("Relationship"):
  print(obj)
Relationship(@id='http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd32')
Relationship(@id='http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd23')
Relationship(@id='http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd33')
Relationship(@id='http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd7')
Relationship(@id='http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd5')
Relationship(@id='http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd13')
Relationship(@id='http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd36')
Relationship(@id='http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd38')
Relationship(@id='http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd21')
Relationship(@id='http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd39')
Relationship(@id='http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd14')
LifecycleScopedRelationship(@id='http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd45')
Relationship(@id='http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd46')
Relationship(@id='http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd25')
Relationship(@id='http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd48')
Relationship(@id='http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd16')
Relationship(@id='http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd15')
Relationship(@id='http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd3')
Relationship(@id='http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd11')
Relationship(@id='http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd24')
Relationship(@id='http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd27')
Relationship(@id='http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd10')
Relationship(@id='http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd8')
Relationship(@id='http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd29')
Relationship(@id='http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd18')
Relationship(@id='http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd28')
Relationship(@id='http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd31')
Relationship(@id='http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd20')

Notice the presence of LifecycleScopedRelationship, which is a subclass of Relationship.

Set match_subclass argument to False, to exclude subclasses.

In [14]:
for obj in object_set.foreach_type("Relationship", match_subclass=False):
  print(obj)
Relationship(@id='http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd32')
Relationship(@id='http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd23')
Relationship(@id='http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd33')
Relationship(@id='http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd7')
Relationship(@id='http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd5')
Relationship(@id='http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd13')
Relationship(@id='http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd36')
Relationship(@id='http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd38')
Relationship(@id='http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd21')
Relationship(@id='http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd39')
Relationship(@id='http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd14')
Relationship(@id='http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd46')
Relationship(@id='http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd25')
Relationship(@id='http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd48')
Relationship(@id='http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd16')
Relationship(@id='http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd15')
Relationship(@id='http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd3')
Relationship(@id='http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd11')
Relationship(@id='http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd24')
Relationship(@id='http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd27')
Relationship(@id='http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd10')
Relationship(@id='http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd8')
Relationship(@id='http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd29')
Relationship(@id='http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd18')
Relationship(@id='http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd28')
Relationship(@id='http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd31')
Relationship(@id='http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd20')

Notice the absence of LifecycleScopedRelationship this time.

Because foreach_type is a generator, you can use it like this as well:

In [15]:
objs = set(object_set.foreach_type("Relationship", match_subclass=False))
print(len(objs))
27

Accessing SPDX properties¶

SPDX objects and their properties can be accessed directly from Python attributes.

For example, this SpdxDocument doc has an attribute creationInfo and the value of creationInfo is a CreationInfo object. We can directly access specVersion attribute of the CreationInfo object like this.

In [16]:
doc = list(object_set.foreach_type("SpdxDocument", match_subclass=False))[0]
print("SPDX Spec Version:", doc.creationInfo.specVersion)

root_element = doc.rootElement[0]  # assume we have only one root element
print("Root element ID:", root_element.spdxId)
SPDX Spec Version: 3.0.1
Root element ID: http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd4

The SPDX specification defines the classes and properties of SPDX models.

For example, the SpdxDocument class is detailed at: https://spdx.github.io/spdx-spec/v3.0.1/model/Core/Classes/SpdxDocument/

Put things together¶

We can now perform basic object access. Let's try to visualize an SPDX 3 SBOM.

The example below will iterate through all relationships and print relationship types between SPDX objects.

In [17]:
def print_object_info(obj, prefix=""):
  """helper function to print a box containing object info.
  obj can be either SHACLObject or just spdxId (string)."""
  print(f"{prefix}┏" + "━"*50 + "┅")
  if isinstance(obj, spdx3.SHACLObject):
    print(f"{prefix}┃ {obj.__class__.__name__}")
    print(f"{prefix}┃  - name: {getattr(obj, 'name', 'N/A')}")
    print(f"{prefix}┃  - spdxId: {obj.spdxId}")
  else: # assume it's an spdxId for an external object
    print(f"{prefix}┃ (Unresolved spdxId)")
    print(f"{prefix}┃  - spdxId: {obj}")
  print(f"{prefix}┗" + "━"*50 + "┅")

for rel in object_set.foreach_type("Relationship"):
  # spdx-python-model uses "from_" instead of "from" to avoid the Python keyword
  from_obj = getattr(rel, "from_")
  to_objs = getattr(rel, "to")
  # print only the type name, not the full URL, for conciseness
  rel_type = getattr(rel, "relationshipType").split("/")[-1]

  print_object_info(from_obj)

  print( "    │")
  print(f"  {rel_type}")
  print( "    ↓")

  for o in to_objs:
    print_object_info(o, prefix="  ")

  print()
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
┃ software_Package
┃  - name: Apache Log4j SLF4J Binding
┃  - spdxId: http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd30
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
    │
  hasDeclaredLicense
    ↓
  ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
  ┃ simplelicensing_LicenseExpression
  ┃  - name: None
  ┃  - spdxId: http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd19
  ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅

┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
┃ software_Package
┃  - name: Apache Log4j API
┃  - spdxId: http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd22
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
    │
  hasConcludedLicense
    ↓
  ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
  ┃ simplelicensing_LicenseExpression
  ┃  - name: None
  ┃  - spdxId: http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd19
  ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅

┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
┃ software_Package
┃  - name: examplemaven
┃  - spdxId: http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd4
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
    │
  hasConcludedLicense
    ↓
  ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
  ┃ simplelicensing_LicenseExpression
  ┃  - name: None
  ┃  - spdxId: http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd9
  ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅

┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
┃ software_File
┃  - name: ./src/main/java/org/spdx/examplemaven/App.java
┃  - spdxId: http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd6
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
    │
  generates
    ↓
  ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
  ┃ software_Package
  ┃  - name: examplemaven
  ┃  - spdxId: http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd4
  ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅

┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
┃ software_Package
┃  - name: examplemaven
┃  - spdxId: http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd4
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
    │
  contains
    ↓
  ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
  ┃ software_File
  ┃  - name: ./src/main/java/org/spdx/examplemaven/App.java
  ┃  - spdxId: http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd6
  ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅

┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
┃ software_Package
┃  - name: examplemaven
┃  - spdxId: http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd4
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
    │
  hasTestCase
    ↓
  ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
  ┃ software_File
  ┃  - name: ./src/test/java/org/spdx/examplemaven/AppTest.java
  ┃  - spdxId: http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd12
  ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅

┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
┃ software_Package
┃  - name: examplemaven
┃  - spdxId: http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd4
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
    │
  hasDistributionArtifact
    ↓
  ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
  ┃ software_File
  ┃  - name: examplemaven-0.0.1.jar
  ┃  - spdxId: http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd35
  ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅

┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
┃ software_Package
┃  - name: examplemaven
┃  - spdxId: http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd4
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
    │
  hasDeclaredLicense
    ↓
  ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
  ┃ simplelicensing_LicenseExpression
  ┃  - name: None
  ┃  - spdxId: http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd9
  ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅

┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
┃ software_Package
┃  - name: Apache Log4j API
┃  - spdxId: http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd22
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
    │
  hasDynamicLink
    ↓
  ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
  ┃ software_Package
  ┃  - name: examplemaven
  ┃  - spdxId: http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd4
  ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅

┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
┃ (Unresolved spdxId)
┃  - spdxId: http://spdx.org/documents/examplemaven-0.0.1#SPDXRef-DOCUMENT
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
    │
  amendedBy
    ↓
  ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
  ┃ SpdxDocument
  ┃  - name: examplemaven
  ┃  - spdxId: http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/document0
  ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅

┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
┃ software_File
┃  - name: ./src/test/java/org/spdx/examplemaven/AppTest.java
┃  - spdxId: http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd12
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
    │
  hasConcludedLicense
    ↓
  ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
  ┃ simplelicensing_LicenseExpression
  ┃  - name: None
  ┃  - spdxId: http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd9
  ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅

┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
┃ software_Package
┃  - name: examplemaven
┃  - spdxId: http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd4
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
    │
  dependsOn
    ↓
  ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
  ┃ software_Package
  ┃  - name: JUnit
  ┃  - spdxId: http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd44
  ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅

┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
┃ software_Package
┃  - name: JUnit
┃  - spdxId: http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd44
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
    │
  hasConcludedLicense
    ↓
  ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
  ┃ simplelicensing_LicenseExpression
  ┃  - name: None
  ┃  - spdxId: http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd19
  ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅

┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
┃ software_Package
┃  - name: SLF4J API Module
┃  - spdxId: http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd26
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
    │
  hasDynamicLink
    ↓
  ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
  ┃ software_Package
  ┃  - name: examplemaven
  ┃  - spdxId: http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd4
  ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅

┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
┃ software_Package
┃  - name: JUnit
┃  - spdxId: http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd44
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
    │
  hasDeclaredLicense
    ↓
  ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
  ┃ simplelicensing_LicenseExpression
  ┃  - name: None
  ┃  - spdxId: http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd49
  ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅

┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
┃ software_Package
┃  - name: Apache Log4j Core
┃  - spdxId: http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd17
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
    │
  hasDynamicLink
    ↓
  ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
  ┃ software_Package
  ┃  - name: examplemaven
  ┃  - spdxId: http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd4
  ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅

┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
┃ software_File
┃  - name: ./src/test/java/org/spdx/examplemaven/AppTest.java
┃  - spdxId: http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd12
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
    │
  hasDeclaredLicense
    ↓
  ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
  ┃ simplelicensing_LicenseExpression
  ┃  - name: None
  ┃  - spdxId: http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd9
  ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅

┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
┃ SpdxDocument
┃  - name: examplemaven
┃  - spdxId: http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/document0
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
    │
  describes
    ↓
  ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
  ┃ software_Package
  ┃  - name: examplemaven
  ┃  - spdxId: http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd4
  ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅

┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
┃ software_Package
┃  - name: examplemaven
┃  - spdxId: http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd4
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
    │
  contains
    ↓
  ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
  ┃ software_File
  ┃  - name: ./src/test/java/org/spdx/examplemaven/AppTest.java
  ┃  - spdxId: http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd12
  ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅

┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
┃ software_Package
┃  - name: Apache Log4j API
┃  - spdxId: http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd22
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
    │
  hasDeclaredLicense
    ↓
  ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
  ┃ simplelicensing_LicenseExpression
  ┃  - name: None
  ┃  - spdxId: http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd19
  ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅

┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
┃ software_Package
┃  - name: SLF4J API Module
┃  - spdxId: http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd26
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
    │
  hasConcludedLicense
    ↓
  ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
  ┃ simplelicensing_LicenseExpression
  ┃  - name: None
  ┃  - spdxId: http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd19
  ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅

┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
┃ software_File
┃  - name: ./src/main/java/org/spdx/examplemaven/App.java
┃  - spdxId: http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd6
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
    │
  hasDeclaredLicense
    ↓
  ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
  ┃ simplelicensing_LicenseExpression
  ┃  - name: None
  ┃  - spdxId: http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd9
  ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅

┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
┃ software_File
┃  - name: ./src/main/java/org/spdx/examplemaven/App.java
┃  - spdxId: http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd6
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
    │
  hasConcludedLicense
    ↓
  ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
  ┃ simplelicensing_LicenseExpression
  ┃  - name: None
  ┃  - spdxId: http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd9
  ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅

┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
┃ software_Package
┃  - name: Apache Log4j SLF4J Binding
┃  - spdxId: http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd30
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
    │
  hasDynamicLink
    ↓
  ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
  ┃ software_Package
  ┃  - name: examplemaven
  ┃  - spdxId: http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd4
  ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅

┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
┃ software_Package
┃  - name: Apache Log4j Core
┃  - spdxId: http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd17
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
    │
  hasConcludedLicense
    ↓
  ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
  ┃ simplelicensing_LicenseExpression
  ┃  - name: None
  ┃  - spdxId: http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd19
  ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅

┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
┃ software_Package
┃  - name: SLF4J API Module
┃  - spdxId: http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd26
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
    │
  hasDeclaredLicense
    ↓
  ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
  ┃ simplelicensing_LicenseExpression
  ┃  - name: None
  ┃  - spdxId: http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd19
  ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅

┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
┃ software_Package
┃  - name: Apache Log4j SLF4J Binding
┃  - spdxId: http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd30
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
    │
  hasConcludedLicense
    ↓
  ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
  ┃ simplelicensing_LicenseExpression
  ┃  - name: None
  ┃  - spdxId: http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd19
  ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅

┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
┃ software_Package
┃  - name: Apache Log4j Core
┃  - spdxId: http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd17
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
    │
  hasDeclaredLicense
    ↓
  ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅
  ┃ simplelicensing_LicenseExpression
  ┃  - name: None
  ┃  - spdxId: http://spdx.org/documents/examplemaven-0.0.1/enriched-specv3/SPDXRef-gnrtd19
  ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┅

SPDX object creation and built-in validation¶

To create an SPDX object, simply use the standard constructor as you would with any native Python class.

In [18]:
package = spdx3.ai_AIPackage()
package
Out[18]:
<spdx_python_model.bindings.v3_0_1.model.ai_AIPackage at 0x78725ec69df0>

The spdx-python-model library features built-in SHACL-based validation derived directly from the SPDX 3 model. This validation ensures data integrity by rejecting invalid types at runtime.

For example, the domain property of /AI/AIPackage has a cardinality of 0..* (minimum count: 0, maximum count: unbounded). This requires the value to be a list; if a different type is provided, the library will raise a TypeError.

In [19]:
package.ai_domain = "nlp"
\^[[0;31m---------------------------------------------------------------------------\^[[0m
\^[[0;31mTypeError\^[[0m                                 Traceback (most recent call last)
\^[[0;32m/tmp/ipykernel_20264/179762956.py\^[[0m in \^[[0;36m<cell line: 0>\^[[0;34m()\^[[0m
\^[[0;32m----> 1\^[[0;31m \^[[0mpackage\^[[0m\^[[0;34m.\^[[0m\^[[0mai_domain\^[[0m \^[[0;34m=\^[[0m \^[[0;34m"nlp"\^[[0m\^[[0;34m\^[[0m\^[[0;34m\^[[0m\^[[0m
\^[[0m
\^[[0;32m/usr/local/lib/python3.12/dist-packages/spdx_python_model/bindings/v3_0_1/model.py\^[[0m in \^[[0;36m__setattr__\^[[0;34m(self, name, value)\^[[0m
\^[[1;32m   1061\^[[0m             \^[[0;32mreturn\^[[0m\^[[0;34m\^[[0m\^[[0;34m\^[[0m\^[[0m
\^[[1;32m   1062\^[[0m \^[[0;34m\^[[0m\^[[0m
\^[[0;32m-> 1063\^[[0;31m         \^[[0mself\^[[0m\^[[0;34m.\^[[0m\^[[0m__set\^[[0m\^[[0;34m(\^[[0m\^[[0mself\^[[0m\^[[0;34m.\^[[0m\^[[0m__get_attr\^[[0m\^[[0;34m(\^[[0m\^[[0mname\^[[0m\^[[0;34m)\^[[0m\^[[0;34m,\^[[0m \^[[0mvalue\^[[0m\^[[0;34m)\^[[0m\^[[0;34m\^[[0m\^[[0;34m\^[[0m\^[[0m
\^[[0m\^[[1;32m   1064\^[[0m \^[[0;34m\^[[0m\^[[0m
\^[[1;32m   1065\^[[0m     \^[[0;32mdef\^[[0m \^[[0m__getattr__\^[[0m\^[[0;34m(\^[[0m\^[[0mself\^[[0m\^[[0;34m,\^[[0m \^[[0mname\^[[0m\^[[0;34m:\^[[0m \^[[0mstr\^[[0m\^[[0;34m)\^[[0m \^[[0;34m->\^[[0m \^[[0mAny\^[[0m\^[[0;34m:\^[[0m\^[[0;34m\^[[0m\^[[0;34m\^[[0m\^[[0m

\^[[0;32m/usr/local/lib/python3.12/dist-packages/spdx_python_model/bindings/v3_0_1/model.py\^[[0m in \^[[0;36m__set\^[[0;34m(self, p, value)\^[[0m
\^[[1;32m   1034\^[[0m                     )
\^[[1;32m   1035\^[[0m \^[[0;34m\^[[0m\^[[0m
\^[[0;32m-> 1036\^[[0;31m         \^[[0mp\^[[0m\^[[0;34m.\^[[0m\^[[0mprop\^[[0m\^[[0;34m.\^[[0m\^[[0mvalidate\^[[0m\^[[0;34m(\^[[0m\^[[0mvalue\^[[0m\^[[0;34m)\^[[0m\^[[0;34m\^[[0m\^[[0;34m\^[[0m\^[[0m
\^[[0m\^[[1;32m   1037\^[[0m         \^[[0;32mif\^[[0m \^[[0mp\^[[0m\^[[0;34m.\^[[0m\^[[0mdeprecated\^[[0m\^[[0;34m:\^[[0m\^[[0;34m\^[[0m\^[[0;34m\^[[0m\^[[0m
\^[[1;32m   1038\^[[0m             warnings.warn(

\^[[0;32m/usr/local/lib/python3.12/dist-packages/spdx_python_model/bindings/v3_0_1/model.py\^[[0m in \^[[0;36mvalidate\^[[0;34m(self, value)\^[[0m
\^[[1;32m    568\^[[0m \^[[0;34m\^[[0m\^[[0m
\^[[1;32m    569\^[[0m     \^[[0;32mdef\^[[0m \^[[0mvalidate\^[[0m\^[[0;34m(\^[[0m\^[[0mself\^[[0m\^[[0;34m,\^[[0m \^[[0mvalue\^[[0m\^[[0;34m:\^[[0m \^[[0mAny\^[[0m\^[[0;34m)\^[[0m \^[[0;34m->\^[[0m \^[[0;32mNone\^[[0m\^[[0;34m:\^[[0m\^[[0;34m\^[[0m\^[[0;34m\^[[0m\^[[0m
\^[[0;32m--> 570\^[[0;31m         \^[[0msuper\^[[0m\^[[0;34m(\^[[0m\^[[0;34m)\^[[0m\^[[0;34m.\^[[0m\^[[0mvalidate\^[[0m\^[[0;34m(\^[[0m\^[[0mvalue\^[[0m\^[[0;34m)\^[[0m\^[[0;34m\^[[0m\^[[0;34m\^[[0m\^[[0m
\^[[0m\^[[1;32m    571\^[[0m \^[[0;34m\^[[0m\^[[0m
\^[[1;32m    572\^[[0m         \^[[0;32mfor\^[[0m \^[[0mi\^[[0m \^[[0;32min\^[[0m \^[[0mvalue\^[[0m\^[[0;34m:\^[[0m\^[[0;34m\^[[0m\^[[0;34m\^[[0m\^[[0m

\^[[0;32m/usr/local/lib/python3.12/dist-packages/spdx_python_model/bindings/v3_0_1/model.py\^[[0m in \^[[0;36mvalidate\^[[0;34m(self, value)\^[[0m
\^[[1;32m     76\^[[0m \^[[0;34m\^[[0m\^[[0m
\^[[1;32m     77\^[[0m     \^[[0;32mdef\^[[0m \^[[0mvalidate\^[[0m\^[[0;34m(\^[[0m\^[[0mself\^[[0m\^[[0;34m,\^[[0m \^[[0mvalue\^[[0m\^[[0;34m:\^[[0m \^[[0mAny\^[[0m\^[[0;34m)\^[[0m \^[[0;34m->\^[[0m \^[[0;32mNone\^[[0m\^[[0;34m:\^[[0m\^[[0;34m\^[[0m\^[[0;34m\^[[0m\^[[0m
\^[[0;32m---> 78\^[[0;31m         \^[[0mcheck_type\^[[0m\^[[0;34m(\^[[0m\^[[0mvalue\^[[0m\^[[0;34m,\^[[0m \^[[0mself\^[[0m\^[[0;34m.\^[[0m\^[[0mVALID_TYPES\^[[0m\^[[0;34m)\^[[0m\^[[0;34m\^[[0m\^[[0;34m\^[[0m\^[[0m
\^[[0m\^[[1;32m     79\^[[0m         if self.pattern is not None and not re.search(
\^[[1;32m     80\^[[0m             \^[[0mself\^[[0m\^[[0;34m.\^[[0m\^[[0mpattern\^[[0m\^[[0;34m,\^[[0m \^[[0mself\^[[0m\^[[0;34m.\^[[0m\^[[0mto_string\^[[0m\^[[0;34m(\^[[0m\^[[0mvalue\^[[0m\^[[0;34m)\^[[0m\^[[0;34m\^[[0m\^[[0;34m\^[[0m\^[[0m

\^[[0;32m/usr/local/lib/python3.12/dist-packages/spdx_python_model/bindings/v3_0_1/model.py\^[[0m in \^[[0;36mcheck_type\^[[0;34m(obj, types)\^[[0m
\^[[1;32m     51\^[[0m     \^[[0;32mif\^[[0m \^[[0;32mnot\^[[0m \^[[0misinstance\^[[0m\^[[0;34m(\^[[0m\^[[0mobj\^[[0m\^[[0;34m,\^[[0m \^[[0mtypes\^[[0m\^[[0;34m)\^[[0m\^[[0;34m:\^[[0m\^[[0;34m\^[[0m\^[[0;34m\^[[0m\^[[0m
\^[[1;32m     52\^[[0m         \^[[0;32mif\^[[0m \^[[0misinstance\^[[0m\^[[0;34m(\^[[0m\^[[0mtypes\^[[0m\^[[0;34m,\^[[0m \^[[0;34m(\^[[0m\^[[0mlist\^[[0m\^[[0;34m,\^[[0m \^[[0mtuple\^[[0m\^[[0;34m)\^[[0m\^[[0;34m)\^[[0m\^[[0;34m:\^[[0m\^[[0;34m\^[[0m\^[[0;34m\^[[0m\^[[0m
\^[[0;32m---> 53\^[[0;31m             raise TypeError(
\^[[0m\^[[1;32m     54\^[[0m                 \^[[0;34mf"Value must be one of type: {', '.join(t.__name__ for t in types)}. Got {type(obj).__name__}"\^[[0m\^[[0;34m\^[[0m\^[[0;34m\^[[0m\^[[0m
\^[[1;32m     55\^[[0m             )

\^[[0;31mTypeError\^[[0m: Value must be one of type: list, ListProxy. Got str

Even if there is only a single entry, you must provide it as a single-element list to satisfy the property's cardinality:

In [20]:
package.ai_domain = ["nlp"]
package.ai_domain
Out[20]:
['nlp']

Refer to the SPDX specification for detailed information on property types and cardinality.

That's it¶

That's it for now! Hope you enjoyed the tutorial.

Special thanks to Joshua Watt for the spdx-python-model library, making SPDX 3 more accessible for Python developers.

Find more examples of how to use the library:

  • spdx-python-model tests: https://github.com/spdx/spdx-python-model/blob/main/tests/test_import.py
  • shacl2code (which generates spdx-python-model's Python binding) tests: https://github.com/JPEWdev/shacl2code/blob/main/tests/test_python.py

There are many ways to get involved with the SPDX community. If you have questions about using SPDX, feel free to join one of our online meetings!

--

Check out my current project, Pitloom, an SBOM generator for Python projects and AI models. It extracts metadata from fastText, GGUF, ONNX, PyTorch, and Safetensors models, and builds SBOMs directly from Hugging Face URLs. It also includes native Hatchling build-hook support and works with setuptools and Poetry.