Skip to content

Instantly share code, notes, and snippets.

@kotfic
Created March 26, 2020 20:04
Show Gist options
  • Save kotfic/69f78a932f8e6cfa4e46487783dacb7c to your computer and use it in GitHub Desktop.
Save kotfic/69f78a932f8e6cfa4e46487783dacb7c to your computer and use it in GitHub Desktop.
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