Module continuous_delivery_scripts.spdx_report.spdx_summary

Summary generators.

Functions

def generate_file_based_on_template(output_dir: pathlib.Path,
template_name: str,
template_args: dict,
suffix: str | None = None) ‑> None
Expand source code
def generate_file_based_on_template(
    output_dir: Path,
    template_name: str,
    template_args: dict,
    suffix: Optional[str] = None,
) -> None:
    """Write file based on template and arguments."""
    logger.info("Loading template '%s'.", template_name)
    template = _get_jinja2_env().get_template(template_name)
    filename = Path(template_name.rsplit(".", 1)[0])
    if suffix:
        filename = Path(
            "{0}_{2}{1}".format(
                *(
                    str(filename.name),
                    str(filename.suffix),
                    str(suffix.replace(".", "_").replace("-", "_")),
                )
            )
        )
    output_filename = output_dir.joinpath(filename)
    rendered = template.render(**template_args)
    logger.info("Writing to '%s'.", output_filename)
    output_filename.write_text(rendered, encoding="utf8")

Write file based on template and arguments.

Classes

class SummaryGenerator (project_package: SpdxPackage,
dependencies_documents: List[SpdxPackage],
missing_dependencies: List[str] | None = None,
licence_assessments: Dict[str, LicenceAssessmentResult] | None = None)
Expand source code
class SummaryGenerator:
    """Licensing summary generator."""

    def __init__(
        self,
        project_package: SpdxPackage,
        dependencies_documents: List[SpdxPackage],
        missing_dependencies: Optional[List[str]] = None,
        licence_assessments: Optional[Dict[str, LicenceAssessmentResult]] = None,
    ) -> None:
        """Initialiser."""
        self.project = project_package
        self.all_packages = list(dependencies_documents)
        self.all_packages.append(self.project)
        self.missing_dependencies = sorted(set(missing_dependencies or []))
        if licence_assessments is None:
            assessor = LicenceAssessor(LicenceAssessmentPolicy.from_config())
            licence_assessments = {
                dependency.name: assessor.assess(
                    project_package.main_licence,
                    dependency.licence,
                    dependency.name,
                    dependency.version,
                    project_package.metadata.has_unknown_licence,
                    dependency.metadata.has_unknown_licence or dependency.main_licence == UNKNOWN_LICENCE.identifier,
                    get_package_manual_licence(dependency.name),
                )
                for dependency in dependencies_documents
            }
        self.licence_assessments = licence_assessments
        self._template_arguments: Optional[dict] = None

    def _generate_template_arguments(self) -> Dict[str, Any]:
        arguments: Dict[str, Any] = dict()

        global_compliance, description_list = self._generate_packages_description()
        compliance_points = (
            [
                "The project and all assessed dependencies meet the configured licence policy, "
                "including any documented manual reviews.",
            ]
            if global_compliance
            else [
                "The project or one or more dependencies do not meet the configured licence policy.",
                "Review the package results below for details.",
            ]
        )
        arguments["project"] = {
            "name": self.project.name,
            "licence": self.project.main_licence,
            "licence_href": _project_licence_href(),
            "accepted_licences": _configured_licence_policy(),
            "compliance": global_compliance,
            "compliance_points": compliance_points,
            "compliance_details": " ".join(
                [f"The project is licensed under the {self.project.main_licence} licence.", *compliance_points]
            ),
        }
        arguments["packages"] = description_list
        arguments["licence_assessment_counts"] = {
            status.value: sum(result.status is status for result in self.licence_assessments.values())
            for status in LicenceAssessment
        }
        arguments["missing_dependencies"] = self.missing_dependencies
        arguments["unknown_licences"] = sorted(
            p.name
            for p in self.all_packages
            if p.metadata.has_unknown_licence or p.main_licence == UNKNOWN_LICENCE.identifier
        )
        arguments["unreviewed_licences"] = [
            name for name in arguments["unknown_licences"] if not get_package_manual_check(name)[1]
        ]
        arguments["undocumented_exemptions"] = sorted(
            p.name
            for p in self.all_packages
            if get_package_manual_check(p.name)[0] and not get_package_manual_check(p.name)[1]
        )
        arguments["project"]["complete"] = not (
            self.missing_dependencies or arguments["unreviewed_licences"] or arguments["undocumented_exemptions"]
        )
        if not arguments["project"]["complete"]:
            compliance_points = [
                "The licence audit is incomplete.",
                "Review missing dependencies, unknown licences and undocumented exemptions before using this report.",
            ]
            arguments["project"]["compliance_points"] = compliance_points
            arguments["project"]["compliance_details"] = " ".join(
                [f"The project is licensed under the {self.project.main_licence} licence.", *compliance_points]
            )
        arguments["render_time"] = datetime.datetime.now()
        return arguments

    def _generate_packages_description(self) -> Tuple[bool, dict]:
        description_list = dict()
        global_compliance = True
        for p in self.all_packages:
            main_licence_valid = p.is_main_licence_accepted
            actual_licence_valid = p.is_licence_accepted
            package_manually_checked, manual_check_details = get_package_manual_check(p.name)
            is_licence_compliant = main_licence_valid and actual_licence_valid
            is_compliant = is_licence_compliant or package_manually_checked
            if not is_compliant:
                global_compliance = False
            description_list[p.name] = self._generate_description_for_one_package(
                is_compliant,
                is_licence_compliant,
                package_manually_checked,
                manual_check_details,
                p,
            )

        return global_compliance, description_list

    def _generate_description_for_one_package(
        self,
        is_compliant: bool,
        is_licence_compliant: bool,
        package_manually_checked: bool,
        manual_check_details: Optional[str],
        p: SpdxPackage,
    ) -> dict:
        description = {
            "name": p.name,
            "anchor": f"package-{generate_uuid_based_on_str(p.name)}",
            "is_dependency": p.is_dependency,
            "url": p.url,
            "licence": p.licence,
            "version": p.version,
            "licence_source": p.metadata.licence_source,
            "declared_licence": p.metadata.declared_licence,
            "licence_classifiers": p.metadata.licence_classifiers,
            "licence_candidates": p.metadata.licence_candidates,
            "licence_evidence": p.metadata.licence_evidence,
            "manual_check": {"checked": package_manually_checked, "reason": manual_check_details or ""},
            "is_compliant": is_compliant,
            "mark_as_problematic": not is_licence_compliant,
            "licence_compliance_details": (
                "Automatic licence assessment meets the configured policy."
                if is_licence_compliant
                else (
                    "Automatic licence assessment does not meet the configured policy; "
                    f"accepted after manual review: {manual_check_details or 'no reason recorded'}."
                    if package_manually_checked
                    else "Automatic licence assessment does not meet the configured policy."
                )
            ),
        }
        if p.is_dependency:
            description["licence_assessment"] = self.licence_assessments[p.name].as_report()
        return description

    @property
    def template_arguments(self) -> dict:
        """Gets template arguments."""
        if not self._template_arguments:
            self._template_arguments = self._generate_template_arguments()
        return self._template_arguments

    def generate_summary(self, dir: Path) -> None:
        """Generates a licensing summary into the specified directory.

        Args:
            dir: output directory
        """
        for t in JINJA_TEMPLATES:
            generate_file_based_on_template(dir, t, self.template_arguments)
        arguments = dict(self.template_arguments)
        arguments["render_time"] = arguments["render_time"].isoformat()
        dir.joinpath("third_party_IP_report.json").write_text(
            json.dumps(arguments, indent=2, sort_keys=True) + "\n", encoding="utf8"
        )
        _link_report_from_index(dir)

Licensing summary generator.

Initialiser.

Instance variables

prop template_arguments : dict
Expand source code
@property
def template_arguments(self) -> dict:
    """Gets template arguments."""
    if not self._template_arguments:
        self._template_arguments = self._generate_template_arguments()
    return self._template_arguments

Gets template arguments.

Methods

def generate_summary(self, dir: pathlib.Path) ‑> None
Expand source code
def generate_summary(self, dir: Path) -> None:
    """Generates a licensing summary into the specified directory.

    Args:
        dir: output directory
    """
    for t in JINJA_TEMPLATES:
        generate_file_based_on_template(dir, t, self.template_arguments)
    arguments = dict(self.template_arguments)
    arguments["render_time"] = arguments["render_time"].isoformat()
    dir.joinpath("third_party_IP_report.json").write_text(
        json.dumps(arguments, indent=2, sort_keys=True) + "\n", encoding="utf8"
    )
    _link_report_from_index(dir)

Generates a licensing summary into the specified directory.

Args
-----=
dir
output directory