Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Add support for dependencies.md like output #77

Open
3 tasks
Nicoretti opened this issue Jul 10, 2023 · 0 comments
Open
3 tasks

✨ Add support for dependencies.md like output #77

Nicoretti opened this issue Jul 10, 2023 · 0 comments
Labels
feature Product feature
Milestone

Comments

@Nicoretti
Copy link
Member

Nicoretti commented Jul 10, 2023

Summary

Implement/Add support for an depedencies.md file.

Details

Add support to output dependency information like dependencies for python projects.

Background & Context

References

Task(s)

Tasks

Preview Give feedback

Code Snippets

This code snippets or parts of them maybe can be reused for this task.

from collections import defaultdict
from dataclasses import dataclass
from typing import (
    Dict,
    List,
    Tuple,
)


@dataclass(frozen=True)
class Package:
    name: str
    license: str
    version: str


def _packages(package_info):
    for p in package_info:
        kwargs = {key.lower(): value for key, value in p.items()}
        yield Package(**kwargs)


def _normalize(license):
    def is_mulit_license(l):
        return ";" in l

    def select_most_permissive(l):
        licenses = [_normalize(l.strip()) for l in l.split(";")]
        priority = defaultdict(
            lambda: 9999,
            {"Unlicense": 0, "BSD": 1, "MIT": 2, "MPLv2": 3, "LGPLv2": 4, "GPLv2": 5},
        )
        priority_to_license = defaultdict(
            lambda: "Unknown", {v: k for k, v in priority.items()}
        )
        selected = min(*[priority[lic] for lic in licenses])
        return priority_to_license[selected]

    mapping = {
        "BSD License": "BSD",
        "MIT License": "MIT",
        "The Unlicense (Unlicense)": "Unlicense",
        "Mozilla Public License 2.0 (MPL 2.0)": "MPLv2",
        "GNU Lesser General Public License v2 (LGPLv2)": "LGPLv2",
        "GNU General Public License v2 (GPLv2)": "GPLv2",
    }
    if is_mulit_license(license):
        return select_most_permissive(license)

    if license not in mapping:
        return license

    return mapping[license]


def audit(
    licenses: List[Dict[str, str]], acceptable: List[str], exceptions: Dict[str, str]
) -> Tuple[List[Package], List[Package]]:
    """
    Audit package licenses.

    Args:
        licenses: a list of dictionaries containing license information for packages.
                  This information e.g. can be obtained by running `pip-licenses --format=json`.

            example: [{"License": "BSD License", "Name": "Babel", "Version": "2.12.1"}, ...]

        acceptable: A list of licenses which shall be accepted.
            example: ["BSD License", "MIT License", ...]

        exceptions: A dictionary containing package names and justifications for packages to ignore/skip.
            example: {'packagename': 'justification why this is/can be an exception'}

    Returns:
        Two lists containing found violations and ignored packages.
    """
    packages = list(_packages(licenses))
    acceptable = [_normalize(a) for a in acceptable]
    ignored = [p for p in packages if p.name in exceptions and exceptions[p.name]]
    violations = [
        p
        for p in packages
        if _normalize(p.license) not in acceptable and p not in ignored
    ]
    return violations, ignored
import pytest

from exasol.toolbox.license import audit


@pytest.fixture
def licenses():
    yield [
        {"License": "BSD License", "Name": "Babel", "Version": "2.12.1"},
        {"License": "MIT License", "Name": "PyYAML", "Version": "6.0"},
        {
            "License": "Apache Software License",
            "Name": "argcomplete",
            "Version": "2.1.2",
        },
        {
            "License": "GNU Lesser General Public License v2 (LGPLv2)",
            "Name": "astroid",
            "Version": "2.15.5",
        },
        {
            "License": "Mozilla Public License 2.0 (MPL 2.0)",
            "Name": "certifi",
            "Version": "2023.5.7",
        },
        {
            "License": "Python Software Foundation License",
            "Name": "distlib",
            "Version": "0.3.6",
        },
        {
            "License": "BSD License; GNU General Public License (GPL); Public Domain; Python Software Foundation License",
            "Name": "docutils",
            "Version": "0.19",
        },
        {
            "License": "The Unlicense (Unlicense)",
            "Name": "filelock",
            "Version": "3.12.2",
        },
        {
            "License": "Mozilla Public License 2.0 (MPL 2.0)",
            "Name": "pathspec",
            "Version": "0.11.1",
        },
        {
            "License": "GNU General Public License (GPL); GNU General Public License v2 or later (GPLv2+); Other/Proprietary License",
            "Name": "prysk",
            "Version": "0.15.1",
        },
        {
            "License": "GNU General Public License v2 (GPLv2)",
            "Name": "pylint",
            "Version": "2.17.4",
        },
    ]


def test_nothing_to_validate():
    licenses = []
    acceptable = []
    exceptions = []
    violations, exceptions = audit(licenses, acceptable, exceptions)
    assert set(violations) == set()
    assert set(exceptions) == set()


@pytest.mark.parametrize(
    "acceptable,exceptions,expected_violations,expected_exceptions",
    [
        ([], [], 11, 0),
        (["BSD License", "MIT License"], {}, 8, 0),
        (
            ["BSD License", "MIT License"],
            {"prysk": "Prysk is only a development dependency"},
            7,
            1,
        ),
    ],
)
def test_audit(
    licenses, acceptable, exceptions, expected_violations, expected_exceptions
):
    violations, exceptions = audit(
        licenses, acceptable=acceptable, exceptions=exceptions
    )
    assert len(violations) == expected_violations
    assert len(exceptions) == expected_exceptions
@Nicoretti Nicoretti added the feature Product feature label Jul 10, 2023
@Nicoretti Nicoretti added this to the 1.0.0 milestone Sep 12, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature Product feature
Projects
None yet
Development

No branches or pull requests

1 participant