Skip to content

Commit

Permalink
Add license module
Browse files Browse the repository at this point in the history
  • Loading branch information
Nicoretti committed Jul 6, 2023
1 parent da05dd9 commit 41698b8
Show file tree
Hide file tree
Showing 2 changed files with 174 additions and 0 deletions.
86 changes: 86 additions & 0 deletions exasol/toolbox/license.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
from collections import defaultdict
from dataclasses import dataclass
from typing import (
Dict,
Iterable,
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
88 changes: 88 additions & 0 deletions test/unit/license_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
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

0 comments on commit 41698b8

Please sign in to comment.