Module continuous_delivery_scripts.spdx_report.spdx_package

Definition of an SPDX Package.

Expand source code
#
# Copyright (C) 2020-2026 Arm Limited or its affiliates and Contributors. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
"""Definition of an SPDX Package."""

from dataclasses import dataclass
from pathlib import Path
from typing import TYPE_CHECKING, List, Optional

from continuous_delivery_scripts.spdx_report.spdx_file import SpdxFile
from continuous_delivery_scripts.spdx_report.spdx_helpers import (
    determine_spdx_value,
    list_project_files_for_licensing,
)
from continuous_delivery_scripts.utils.definitions import UNKNOWN
from continuous_delivery_scripts.utils.python.package_helpers import PackageMetadata
from continuous_delivery_scripts.utils.third_party_licences import (
    UNKNOWN_LICENCE,
    cleanse_licence_expression,
    is_licence_accepted,
    determine_licence_compound,
)

if TYPE_CHECKING:
    from spdx.package import Package


@dataclass(frozen=True, order=True)
class PackageInfo:
    """Definition of a Python package.

    Attributes:
        metadata: metadata about a package from files generated from setup.py.
        root_dir: project root directory.
        source_dir: directory where package's sources are.
        uuid: unique identifier of the package.
    """

    metadata: PackageMetadata
    root_dir: Path
    source_dir: Path
    uuid: str


def _set_package_copyright(file: SpdxFile, package: "Package") -> None:
    """Sets the copyright field of a package based on file copyright."""
    if file.copyright:
        package.cr_text = determine_spdx_value(file.copyright)


class SpdxPackage:
    """SPDX package.

    See https://spdx.org/spdx-specification-21-web-version#h.4i7ojhp
    """

    def __init__(self, package_info: PackageInfo, is_dependency: bool = False) -> None:
        """Constructor."""
        self._is_dependency = is_dependency
        self._package_info = package_info
        self._file_list: Optional[List[Path]] = None
        self._actual_licence: Optional[str] = None
        self._main_licence: Optional[str] = None

    @property
    def files(self) -> Optional[List[Path]]:
        """Gets package's files.

        Returns:
            list of file path or None if a dependency.
        """
        if self._is_dependency:
            return None
        if not self._file_list:
            self._file_list = [p for p in list_project_files_for_licensing(self._package_info.source_dir)]
        return self._file_list

    @property
    def id(self) -> str:
        """Gets Package's identifier.

        Returns:
            An ID
        """
        return self.name if self._is_dependency else self._package_info.uuid

    @property
    def is_dependency(self) -> bool:
        """States whether the package is a dependency or not."""
        return self._is_dependency

    @property
    def name(self) -> str:
        """Gets Package's name.

        See https://spdx.org/spdx-specification-21-web-version#h.2xcytpi

        Returns:
            corresponding string
        """
        return str(self._package_info.metadata.name)

    @property
    def version(self) -> str:
        """Gets package's version.

        Returns:
            package version
        """
        return str(self._package_info.metadata.version)

    @property
    def main_licence(self) -> str:
        """Gets the package's official licence.

        Returns:
            project's licence
        """
        if not self._main_licence:
            package_licence = self._package_info.metadata.licence
            self._main_licence = (
                cleanse_licence_expression(package_licence) if package_licence else UNKNOWN_LICENCE.identifier
            )
        return self._main_licence

    @property
    def is_main_licence_accepted(self) -> bool:
        """States whether the main licence of the package is part of the accepted licence list."""
        return bool(is_licence_accepted(self.main_licence))

    @property
    def licence(self) -> str:
        """Gets the actual package's licence based on the files it contains.

        Returns:
            project's licence
        """
        if not self._actual_licence:
            files = self.get_spdx_files()
            self._actual_licence = (
                determine_licence_compound(self.main_licence, [f.licence for f in files])
                if files
                else self.main_licence
            )
        return self._actual_licence

    @property
    def is_licence_accepted(self) -> bool:
        """States whether the actual package's licence of the package is part of the accepted licence list."""
        return bool(is_licence_accepted(self.licence))

    @property
    def author(self) -> str:
        """Gets the document's author.

        Returns:
            document's author
        """
        return str(self._package_info.metadata.author)

    @property
    def author_email(self) -> str:
        """Gets the document author's email.

        Returns:
            document author's email
        """
        return str(self._package_info.metadata.author_email)

    @property
    def url(self) -> str:
        """Gets the package source URL.

        Returns:
            the package homepage
        """
        return str(self._package_info.metadata.url)

    @property
    def description(self) -> str:
        """Gets the package description.

        Returns:
            some description
        """
        return str(self._package_info.metadata.description)

    def get_spdx_files(self) -> Optional[List[SpdxFile]]:
        """Gets package's files SPDX description.

        Returns:
            list of file descriptions or None if a dependency.
        """
        if not self.files:
            return None
        return [SpdxFile(p, self._package_info.root_dir, self.main_licence) for p in self.files]

    def generate_spdx_package(self) -> "Package":
        """Generates the SPDX package.

        Example of a SPDX package:
        PackageName: eduVPN
        DataFormat: SPDXRef-1
        PackageSupplier: Organization: The Commons Conservancy eduVPN Programme
        PackageHomePage: https://eduvpn.org
        PackageLicenseDeclared: GPL-3.0+
        PackageCopyrightText: 2017, The Commons Conservancy eduVPN Programme
        PackageSummary: <text>EduVPN is designed to allow users to connect
        securely and encrypted to the Internet from any standard device.
                        </text>
        PackageComment: <text>The package includes the following libraries; see
        Relationship information.
                        </text>
        Created: 2017-06-06T09:00:00Z
        PackageDownloadLocation: git://github.com/eduVPN/reponame
        PackageDownloadLocation: git+https://github.com/eduVPN/reponame.git
        PackageDownloadLocation: git+ssh://github.com/eduVPN/reponame.git
        Creator: Person: Jane Doe

        Returns:
            the corresponding package
        """
        from spdx.checksum import Algorithm
        from spdx.creationinfo import Person
        from spdx.document import License
        from spdx.package import Package
        from spdx.utils import NoAssert

        package = Package(
            name=determine_spdx_value(self.name),
            spdx_id=f"SPDXRef-{self.id}",
            download_location=determine_spdx_value(None),
            version=determine_spdx_value(self.version),
            file_name=determine_spdx_value(self.name),
            supplier=None,
            originator=Person(
                determine_spdx_value(self.author),
                determine_spdx_value(self.author_email),
            ),
        )
        package.check_sum = Algorithm("SHA1", str(NoAssert()))
        package.cr_text = NoAssert()
        package.homepage = determine_spdx_value(self.url)
        package.license_declared = License.from_identifier(str(determine_spdx_value(self.main_licence)))
        package.conc_lics = License.from_identifier(str(determine_spdx_value(self.licence)))
        package.summary = determine_spdx_value(self.description)
        package.description = NoAssert()
        files = self.get_spdx_files()
        if files:
            package.files_analyzed = True
            for file in files:
                package.add_file(file.generate_spdx_file())
                package.add_lics_from_file(License.from_identifier(str(determine_spdx_value(file.licence))))
                _set_package_copyright(file, package)
            package.verif_code = determine_spdx_value(package.calc_verif_code())
        else:
            # Has to generate a dummy file because of the following rule in SDK:
            # - Package must have at least one file
            dummy_file = SpdxFile(Path(UNKNOWN), self._package_info.root_dir, self.main_licence)
            package.verif_code = NoAssert()
            package.add_file(dummy_file.generate_spdx_file())
            package.add_lics_from_file(License.from_identifier(str(determine_spdx_value(dummy_file.licence))))
        return package

Classes

class PackageInfo (metadata: PackageMetadata, root_dir: pathlib.Path, source_dir: pathlib.Path, uuid: str)

Definition of a Python package.

Attributes

metadata
metadata about a package from files generated from setup.py.
root_dir
project root directory.
source_dir
directory where package's sources are.
uuid
unique identifier of the package.
Expand source code
@dataclass(frozen=True, order=True)
class PackageInfo:
    """Definition of a Python package.

    Attributes:
        metadata: metadata about a package from files generated from setup.py.
        root_dir: project root directory.
        source_dir: directory where package's sources are.
        uuid: unique identifier of the package.
    """

    metadata: PackageMetadata
    root_dir: Path
    source_dir: Path
    uuid: str

Class variables

var metadataPackageMetadata
var root_dir : pathlib.Path
var source_dir : pathlib.Path
var uuid : str
class SpdxPackage (package_info: PackageInfo, is_dependency: bool = False)
Expand source code
class SpdxPackage:
    """SPDX package.

    See https://spdx.org/spdx-specification-21-web-version#h.4i7ojhp
    """

    def __init__(self, package_info: PackageInfo, is_dependency: bool = False) -> None:
        """Constructor."""
        self._is_dependency = is_dependency
        self._package_info = package_info
        self._file_list: Optional[List[Path]] = None
        self._actual_licence: Optional[str] = None
        self._main_licence: Optional[str] = None

    @property
    def files(self) -> Optional[List[Path]]:
        """Gets package's files.

        Returns:
            list of file path or None if a dependency.
        """
        if self._is_dependency:
            return None
        if not self._file_list:
            self._file_list = [p for p in list_project_files_for_licensing(self._package_info.source_dir)]
        return self._file_list

    @property
    def id(self) -> str:
        """Gets Package's identifier.

        Returns:
            An ID
        """
        return self.name if self._is_dependency else self._package_info.uuid

    @property
    def is_dependency(self) -> bool:
        """States whether the package is a dependency or not."""
        return self._is_dependency

    @property
    def name(self) -> str:
        """Gets Package's name.

        See https://spdx.org/spdx-specification-21-web-version#h.2xcytpi

        Returns:
            corresponding string
        """
        return str(self._package_info.metadata.name)

    @property
    def version(self) -> str:
        """Gets package's version.

        Returns:
            package version
        """
        return str(self._package_info.metadata.version)

    @property
    def main_licence(self) -> str:
        """Gets the package's official licence.

        Returns:
            project's licence
        """
        if not self._main_licence:
            package_licence = self._package_info.metadata.licence
            self._main_licence = (
                cleanse_licence_expression(package_licence) if package_licence else UNKNOWN_LICENCE.identifier
            )
        return self._main_licence

    @property
    def is_main_licence_accepted(self) -> bool:
        """States whether the main licence of the package is part of the accepted licence list."""
        return bool(is_licence_accepted(self.main_licence))

    @property
    def licence(self) -> str:
        """Gets the actual package's licence based on the files it contains.

        Returns:
            project's licence
        """
        if not self._actual_licence:
            files = self.get_spdx_files()
            self._actual_licence = (
                determine_licence_compound(self.main_licence, [f.licence for f in files])
                if files
                else self.main_licence
            )
        return self._actual_licence

    @property
    def is_licence_accepted(self) -> bool:
        """States whether the actual package's licence of the package is part of the accepted licence list."""
        return bool(is_licence_accepted(self.licence))

    @property
    def author(self) -> str:
        """Gets the document's author.

        Returns:
            document's author
        """
        return str(self._package_info.metadata.author)

    @property
    def author_email(self) -> str:
        """Gets the document author's email.

        Returns:
            document author's email
        """
        return str(self._package_info.metadata.author_email)

    @property
    def url(self) -> str:
        """Gets the package source URL.

        Returns:
            the package homepage
        """
        return str(self._package_info.metadata.url)

    @property
    def description(self) -> str:
        """Gets the package description.

        Returns:
            some description
        """
        return str(self._package_info.metadata.description)

    def get_spdx_files(self) -> Optional[List[SpdxFile]]:
        """Gets package's files SPDX description.

        Returns:
            list of file descriptions or None if a dependency.
        """
        if not self.files:
            return None
        return [SpdxFile(p, self._package_info.root_dir, self.main_licence) for p in self.files]

    def generate_spdx_package(self) -> "Package":
        """Generates the SPDX package.

        Example of a SPDX package:
        PackageName: eduVPN
        DataFormat: SPDXRef-1
        PackageSupplier: Organization: The Commons Conservancy eduVPN Programme
        PackageHomePage: https://eduvpn.org
        PackageLicenseDeclared: GPL-3.0+
        PackageCopyrightText: 2017, The Commons Conservancy eduVPN Programme
        PackageSummary: <text>EduVPN is designed to allow users to connect
        securely and encrypted to the Internet from any standard device.
                        </text>
        PackageComment: <text>The package includes the following libraries; see
        Relationship information.
                        </text>
        Created: 2017-06-06T09:00:00Z
        PackageDownloadLocation: git://github.com/eduVPN/reponame
        PackageDownloadLocation: git+https://github.com/eduVPN/reponame.git
        PackageDownloadLocation: git+ssh://github.com/eduVPN/reponame.git
        Creator: Person: Jane Doe

        Returns:
            the corresponding package
        """
        from spdx.checksum import Algorithm
        from spdx.creationinfo import Person
        from spdx.document import License
        from spdx.package import Package
        from spdx.utils import NoAssert

        package = Package(
            name=determine_spdx_value(self.name),
            spdx_id=f"SPDXRef-{self.id}",
            download_location=determine_spdx_value(None),
            version=determine_spdx_value(self.version),
            file_name=determine_spdx_value(self.name),
            supplier=None,
            originator=Person(
                determine_spdx_value(self.author),
                determine_spdx_value(self.author_email),
            ),
        )
        package.check_sum = Algorithm("SHA1", str(NoAssert()))
        package.cr_text = NoAssert()
        package.homepage = determine_spdx_value(self.url)
        package.license_declared = License.from_identifier(str(determine_spdx_value(self.main_licence)))
        package.conc_lics = License.from_identifier(str(determine_spdx_value(self.licence)))
        package.summary = determine_spdx_value(self.description)
        package.description = NoAssert()
        files = self.get_spdx_files()
        if files:
            package.files_analyzed = True
            for file in files:
                package.add_file(file.generate_spdx_file())
                package.add_lics_from_file(License.from_identifier(str(determine_spdx_value(file.licence))))
                _set_package_copyright(file, package)
            package.verif_code = determine_spdx_value(package.calc_verif_code())
        else:
            # Has to generate a dummy file because of the following rule in SDK:
            # - Package must have at least one file
            dummy_file = SpdxFile(Path(UNKNOWN), self._package_info.root_dir, self.main_licence)
            package.verif_code = NoAssert()
            package.add_file(dummy_file.generate_spdx_file())
            package.add_lics_from_file(License.from_identifier(str(determine_spdx_value(dummy_file.licence))))
        return package

Instance variables

var author : str

Gets the document's author.

Returns

document's author

Expand source code
@property
def author(self) -> str:
    """Gets the document's author.

    Returns:
        document's author
    """
    return str(self._package_info.metadata.author)
var author_email : str

Gets the document author's email.

Returns

document author's email

Expand source code
@property
def author_email(self) -> str:
    """Gets the document author's email.

    Returns:
        document author's email
    """
    return str(self._package_info.metadata.author_email)
var description : str

Gets the package description.

Returns

some description

Expand source code
@property
def description(self) -> str:
    """Gets the package description.

    Returns:
        some description
    """
    return str(self._package_info.metadata.description)
var files : Optional[List[pathlib.Path]]

Gets package's files.

Returns

list of file path or None if a dependency.

Expand source code
@property
def files(self) -> Optional[List[Path]]:
    """Gets package's files.

    Returns:
        list of file path or None if a dependency.
    """
    if self._is_dependency:
        return None
    if not self._file_list:
        self._file_list = [p for p in list_project_files_for_licensing(self._package_info.source_dir)]
    return self._file_list
var id : str

Gets Package's identifier.

Returns

An ID

Expand source code
@property
def id(self) -> str:
    """Gets Package's identifier.

    Returns:
        An ID
    """
    return self.name if self._is_dependency else self._package_info.uuid
var is_dependency : bool

States whether the package is a dependency or not.

Expand source code
@property
def is_dependency(self) -> bool:
    """States whether the package is a dependency or not."""
    return self._is_dependency
var is_licence_accepted : bool

States whether the actual package's licence of the package is part of the accepted licence list.

Expand source code
@property
def is_licence_accepted(self) -> bool:
    """States whether the actual package's licence of the package is part of the accepted licence list."""
    return bool(is_licence_accepted(self.licence))
var is_main_licence_accepted : bool

States whether the main licence of the package is part of the accepted licence list.

Expand source code
@property
def is_main_licence_accepted(self) -> bool:
    """States whether the main licence of the package is part of the accepted licence list."""
    return bool(is_licence_accepted(self.main_licence))
var licence : str

Gets the actual package's licence based on the files it contains.

Returns

project's licence

Expand source code
@property
def licence(self) -> str:
    """Gets the actual package's licence based on the files it contains.

    Returns:
        project's licence
    """
    if not self._actual_licence:
        files = self.get_spdx_files()
        self._actual_licence = (
            determine_licence_compound(self.main_licence, [f.licence for f in files])
            if files
            else self.main_licence
        )
    return self._actual_licence
var main_licence : str

Gets the package's official licence.

Returns

project's licence

Expand source code
@property
def main_licence(self) -> str:
    """Gets the package's official licence.

    Returns:
        project's licence
    """
    if not self._main_licence:
        package_licence = self._package_info.metadata.licence
        self._main_licence = (
            cleanse_licence_expression(package_licence) if package_licence else UNKNOWN_LICENCE.identifier
        )
    return self._main_licence
var name : str

Gets Package's name.

See https://spdx.org/spdx-specification-21-web-version#h.2xcytpi

Returns

corresponding string

Expand source code
@property
def name(self) -> str:
    """Gets Package's name.

    See https://spdx.org/spdx-specification-21-web-version#h.2xcytpi

    Returns:
        corresponding string
    """
    return str(self._package_info.metadata.name)
var url : str

Gets the package source URL.

Returns

the package homepage

Expand source code
@property
def url(self) -> str:
    """Gets the package source URL.

    Returns:
        the package homepage
    """
    return str(self._package_info.metadata.url)
var version : str

Gets package's version.

Returns

package version

Expand source code
@property
def version(self) -> str:
    """Gets package's version.

    Returns:
        package version
    """
    return str(self._package_info.metadata.version)

Methods

def generate_spdx_package(self) ‑> Package

Generates the SPDX package.

Example of a SPDX package: PackageName: eduVPN DataFormat: SPDXRef-1 PackageSupplier: Organization: The Commons Conservancy eduVPN Programme PackageHomePage: https://eduvpn.org PackageLicenseDeclared: GPL-3.0+ PackageCopyrightText: 2017, The Commons Conservancy eduVPN Programme PackageSummary: EduVPN is designed to allow users to connect securely and encrypted to the Internet from any standard device. PackageComment: The package includes the following libraries; see Relationship information. Created: 2017-06-06T09:00:00Z PackageDownloadLocation: git://github.com/eduVPN/reponame PackageDownloadLocation: git+https://github.com/eduVPN/reponame.git PackageDownloadLocation: git+ssh://github.com/eduVPN/reponame.git Creator: Person: Jane Doe

Returns

the corresponding package

Expand source code
def generate_spdx_package(self) -> "Package":
    """Generates the SPDX package.

    Example of a SPDX package:
    PackageName: eduVPN
    DataFormat: SPDXRef-1
    PackageSupplier: Organization: The Commons Conservancy eduVPN Programme
    PackageHomePage: https://eduvpn.org
    PackageLicenseDeclared: GPL-3.0+
    PackageCopyrightText: 2017, The Commons Conservancy eduVPN Programme
    PackageSummary: <text>EduVPN is designed to allow users to connect
    securely and encrypted to the Internet from any standard device.
                    </text>
    PackageComment: <text>The package includes the following libraries; see
    Relationship information.
                    </text>
    Created: 2017-06-06T09:00:00Z
    PackageDownloadLocation: git://github.com/eduVPN/reponame
    PackageDownloadLocation: git+https://github.com/eduVPN/reponame.git
    PackageDownloadLocation: git+ssh://github.com/eduVPN/reponame.git
    Creator: Person: Jane Doe

    Returns:
        the corresponding package
    """
    from spdx.checksum import Algorithm
    from spdx.creationinfo import Person
    from spdx.document import License
    from spdx.package import Package
    from spdx.utils import NoAssert

    package = Package(
        name=determine_spdx_value(self.name),
        spdx_id=f"SPDXRef-{self.id}",
        download_location=determine_spdx_value(None),
        version=determine_spdx_value(self.version),
        file_name=determine_spdx_value(self.name),
        supplier=None,
        originator=Person(
            determine_spdx_value(self.author),
            determine_spdx_value(self.author_email),
        ),
    )
    package.check_sum = Algorithm("SHA1", str(NoAssert()))
    package.cr_text = NoAssert()
    package.homepage = determine_spdx_value(self.url)
    package.license_declared = License.from_identifier(str(determine_spdx_value(self.main_licence)))
    package.conc_lics = License.from_identifier(str(determine_spdx_value(self.licence)))
    package.summary = determine_spdx_value(self.description)
    package.description = NoAssert()
    files = self.get_spdx_files()
    if files:
        package.files_analyzed = True
        for file in files:
            package.add_file(file.generate_spdx_file())
            package.add_lics_from_file(License.from_identifier(str(determine_spdx_value(file.licence))))
            _set_package_copyright(file, package)
        package.verif_code = determine_spdx_value(package.calc_verif_code())
    else:
        # Has to generate a dummy file because of the following rule in SDK:
        # - Package must have at least one file
        dummy_file = SpdxFile(Path(UNKNOWN), self._package_info.root_dir, self.main_licence)
        package.verif_code = NoAssert()
        package.add_file(dummy_file.generate_spdx_file())
        package.add_lics_from_file(License.from_identifier(str(determine_spdx_value(dummy_file.licence))))
    return package
def get_spdx_files(self) ‑> Optional[List[SpdxFile]]

Gets package's files SPDX description.

Returns

list of file descriptions or None if a dependency.

Expand source code
def get_spdx_files(self) -> Optional[List[SpdxFile]]:
    """Gets package's files SPDX description.

    Returns:
        list of file descriptions or None if a dependency.
    """
    if not self.files:
        return None
    return [SpdxFile(p, self._package_info.root_dir, self.main_licence) for p in self.files]