Created
October 30, 2018 10:21
-
-
Save snopoke/1e77eb78ad158c4c65208148d9eb7b89 to your computer and use it in GitHub Desktop.
Print licenses of packages in requirements file.
This file contains hidden or 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
# based on https://github.com/dhatim/python-license-check | |
import argparse | |
try: | |
from configparser import ConfigParser, NoOptionError | |
except ImportError: | |
from ConfigParser import ConfigParser, NoOptionError | |
import re | |
import sys | |
import pkg_resources | |
try: | |
from pip._internal.download import PipSession | |
except ImportError: | |
from pip.download import PipSession | |
try: | |
from pip._internal.req import parse_requirements | |
except ImportError: | |
from pip.req import parse_requirements | |
def get_packages_info(requirement_file): | |
regex_license = re.compile(r'License: (?P<license>[^\r\n]+)\r?\n') | |
regex_classifier = re.compile(r'Classifier: License :: OSI Approved :: (?P<classifier>[^\r\n]+)\r?\n') | |
requirements = [pkg_resources.Requirement.parse(str(req.req)) for req | |
in parse_requirements(requirement_file, session=PipSession()) if req.req is not None] | |
def transform(dist): | |
licenses = get_license(dist) + get_license_OSI_classifiers(dist) | |
# Strip the useless "License" suffix and uniquify | |
licenses = list(set([strip_license(l) for l in licenses])) | |
return { | |
'name': dist.project_name, | |
'version': dist.version, | |
'location': dist.location, | |
'dependencies': [dependency.project_name for dependency in dist.requires()], | |
'licenses': licenses, | |
} | |
def get_license(dist): | |
if dist.has_metadata(dist.PKG_INFO): | |
metadata = dist.get_metadata(dist.PKG_INFO) | |
license = regex_license.search(metadata).group('license') | |
if license != "UNKNOWN": # Value when license not specified. | |
return [license] | |
return [] | |
def get_license_OSI_classifiers(dist): | |
if dist.has_metadata(dist.PKG_INFO): | |
metadata = dist.get_metadata(dist.PKG_INFO) | |
return regex_classifier.findall(metadata) | |
return [] | |
def strip_license(license): | |
if license.lower().endswith(" license"): | |
return license[:-len(" license")] | |
return license | |
packages = [transform(dist) for dist in pkg_resources.working_set.resolve(requirements)] | |
# keep only unique values as there are maybe some duplicates | |
unique = [] | |
[unique.append(item) for item in packages if item not in unique] | |
return sorted(unique, key=(lambda item: item['name'].lower())) | |
def find_parents(package, all): | |
parents = [p['name'] for p in all if package in p['dependencies']] | |
if len(parents) == 0: | |
return [package] | |
dependency_trees = [] | |
for parent in parents: | |
for dependencies in find_parents(parent, all): | |
dependency_trees.append(package + " << " + dependencies) | |
return dependency_trees | |
def write_package(package, all, show_all, show_parents): | |
dependency_branches = find_parents(package['name'], all) | |
licenses = package['licenses'] or ['UNKNOWN'] | |
has_parents = len(dependency_branches) > 1 or (len(dependency_branches) == 1 and dependency_branches[0] != package['name']) | |
if show_all or not has_parents: | |
print(' {} ({}): {}'.format(package['name'], package['version'], licenses)) | |
if show_parents: | |
print(' dependenc{}:'.format('y' if len(dependency_branches) <= 1 else 'ies')) | |
for dependency_branch in dependency_branches: | |
print(' {}'.format(dependency_branch)) | |
def write_packages(packages, show_all, show_parents): | |
for package in packages: | |
write_package(package, packages, show_all, show_parents) | |
def process(requirement_file, show_all, show_parents): | |
print('gathering licenses...') | |
pkg_info = get_packages_info(requirement_file) | |
all = list(pkg_info) | |
print('{} package{} and dependencies.'.format(len(pkg_info), '' if len(pkg_info) <= 1 else 's')) | |
write_packages(all, show_all, show_parents) | |
def parse_args(args): | |
parser = argparse.ArgumentParser( | |
description='Print license of packages and there dependencies.', | |
formatter_class=argparse.RawTextHelpFormatter) | |
parser.add_argument( | |
'-r', '--rfile', dest='requirement_txt_file', | |
help='path/to/requirement.txt file', nargs='?', | |
default='./requirements.txt') | |
parser.add_argument('-a', '--show-all', dest='all', action='store_true', help='Show all packages, not just top level.') | |
parser.add_argument('-p', '--show-parents', dest='parents', action='store_true', help='Show package parents.') | |
return parser.parse_args(args) | |
def run(args): | |
return process(args.requirement_txt_file, args.all, args.parents) | |
def main(): | |
args = parse_args(sys.argv[1:]) | |
sys.exit(run(args)) | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment