-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
174 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |