Skip to content

Instantly share code, notes, and snippets.

@snopoke
Created October 30, 2018 10:21
Show Gist options
  • Save snopoke/1e77eb78ad158c4c65208148d9eb7b89 to your computer and use it in GitHub Desktop.
Save snopoke/1e77eb78ad158c4c65208148d9eb7b89 to your computer and use it in GitHub Desktop.
Print licenses of packages in requirements file.
# 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