Created
March 26, 2020 20:04
-
-
Save kotfic/69f78a932f8e6cfa4e46487783dacb7c to your computer and use it in GitHub Desktop.
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
import sys | |
import click | |
from piplicenses import get_packages, create_parser | |
from pipdeptree import build_dist_index, construct_tree | |
try: | |
from pip._internal.utils.misc import get_installed_distributions | |
from pip._internal.operations.freeze import FrozenRequirement | |
except ImportError: | |
from pip import get_installed_distributions, FrozenRequirement | |
# Note: Using Click for argument parsing so we need to pass something | |
# to piplicenses.get_packages | |
class PipLicensesArgs(object): | |
format='plain' | |
format_confluence=False | |
format_html=False | |
format_json=False | |
format_markdown=False | |
format_rst=False | |
from_classifier=False | |
ignore_packages=[] | |
order='name' | |
summary=False | |
with_authors=False, | |
with_description=False | |
with_license_file=False | |
with_system=False | |
with_urls=False | |
class PipDepTreeArgs(object): | |
all=False | |
exclude=None | |
freeze=False | |
json=False | |
json_tree=False | |
local_only=False | |
output_format=None | |
packages=None | |
reverse=False | |
user_only=False | |
warn='suppress' | |
APPROVED_LICENSES = [ | |
'Apache 2.0', | |
'Apache License 2.0', | |
'Apache Software License 2.0', | |
'Apache', | |
'BSD License', | |
'BSD', | |
'BSD, Public Domain', | |
'BSD-3-Clause', | |
'MIT License', | |
'MIT license', | |
'MIT', | |
] | |
# These packages are ignored because they are required for the test | |
# infrastructure, but will not be installed in production. | |
IGNORED_PACKAGES = [ | |
'click', | |
'colorama', | |
'pip-licenses', | |
'pipdeptree', | |
'ptable', | |
] | |
KNOWN_PACKAGES = [ | |
# The following packages are reported as "UNKNOWN" by | |
# pip-licenses, their license was deteremined by looking at their | |
# setup.py/LICENSE file | |
('python-dateutil', 'Apache 2.0', 'Chris Kotfila', '2019-03-28', 'See LICENSE'), | |
('python-dotenv', 'OSI Approved :: BSD License', 'Chris Kotfila', '2019-03-28', 'See setup.py'), | |
# The following packages are are package dependencies that are not | |
# reported by pip-licenses because they are apart of distutils | |
('pip', 'MIT', 'Chris Kotfila', '2019-03-28', 'Part of Pythons package system'), | |
('setuptools', 'MIT', 'Chris Kotfila', '2019-03-28', 'Part of Pythons package system'), | |
('wheel', 'MIT', 'Chris Kotfila', '2019-03-28', 'Part of Pythons package system') | |
] | |
def _get_license_map(): | |
packages = get_packages(PipLicensesArgs()) | |
pkg_map = {pkg['name'].lower():pkg['license'] for pkg in packages} | |
# Handle known packages which are not reported by pip-licenses | |
for (p, l, _, _, _) in KNOWN_PACKAGES: | |
pkg_map[p] = l | |
return pkg_map | |
def _tree_formatter(license_map): | |
def _format_tree_item(dist, vs=None): | |
pkg_dict = dist.as_dict() | |
name = pkg_dict['package_name'].lower() | |
item = {name: { | |
'installed_version': pkg_dict['installed_version'], | |
'license': license_map[name] if name in license_map else 'ERROR' | |
}} | |
if vs is not None: | |
dependencies = {} | |
for v in vs: | |
dependencies.update(_format_tree_item(v)) | |
item[name]['dependencies'] = dependencies | |
return item | |
return _format_tree_item | |
def get_dependency_tree(): | |
args = PipDepTreeArgs() | |
tf = _tree_formatter(_get_license_map()) | |
pkgs = get_installed_distributions(local_only=args.local_only, | |
user_only=args.user_only) | |
dist_index = build_dist_index(pkgs) | |
dist_tree = construct_tree(dist_index) | |
tree = {} | |
for k, vs in dist_tree.items(): | |
if k.as_dict()['package_name'].lower() not in IGNORED_PACKAGES: | |
tree.update(tf(k, vs)) | |
return tree | |
def _get_acceptable_license(name, info): | |
if info['license'] not in APPROVED_LICENSES and \ | |
name not in [p[0] for p in KNOWN_PACKAGES]: | |
return False | |
else: | |
return True | |
def print_item(name, info, indent=''): | |
fmt = f'{indent}{name} [{info["installed_version"]}] {info["license"]}' | |
if not _get_acceptable_license(name, info): | |
click.echo(click.style(fmt, fg='red')) | |
else: | |
click.echo(click.style(fmt, fg='green')) | |
def print_tree(tree): | |
for name in sorted(tree): | |
info = tree[name] | |
print_item(name, info) | |
if len(info['dependencies']): | |
for n in sorted(info['dependencies']): | |
i = info['dependencies'][n] | |
print_item(n, i, ' ') | |
@click.group() | |
def main(): | |
pass | |
@main.command() | |
def tree(): | |
tree = get_dependency_tree() | |
print_tree(tree) | |
@main.command() | |
def test(): | |
tree = get_dependency_tree() | |
errors = [] | |
for name in sorted(tree): | |
info = tree[name] | |
if not _get_acceptable_license(name, info): | |
errors.append((name, info)) | |
print_tree(tree) | |
if len(errors): | |
click.echo(click.style('\nThe following license errors were found:', fg='red')) | |
for (n, i) in errors: | |
print_item(n, i, ' ') | |
click.echo(click.style('\n', fg='red')) | |
sys.exit(1) | |
sys.exit(0) | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment