Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 19 additions & 24 deletions liccheck/command_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
import collections
import os.path

from liccheck.requirements import parse_requirements, resolve, resolve_without_deps
from liccheck.requirements import parse_requirements, resolve

from configparser import ConfigParser, NoOptionError
import enum
import functools
import re
import textwrap
from packaging.requirements import Requirement
import sys
import semantic_version
import toml
Expand Down Expand Up @@ -138,10 +139,7 @@ class Reason(enum.Enum):


def get_packages_info(requirement_file, no_deps=False):
regex_license = re.compile(r"License(?:-Expression)?: (?P<license>.*)?$", re.M)
regex_classifier = re.compile(
r"Classifier: License(?: :: OSI Approved)?(?: :: (?P<classifier>.*))?$", re.M
)
regex_classifier = re.compile(r"^License(?: :: OSI Approved)?(?: :: (?P<classifier>.*))?$")

requirements = parse_requirements(requirement_file)

Expand All @@ -153,31 +151,27 @@ def transform(dist):
licenses = list(set([strip_license(l) for l in licenses]))

return {
"name": dist.project_name,
"name": dist.name,
"version": dist.version,
"location": dist.location,
"dependencies": [dependency.project_name for dependency in dist.requires()],
"dependencies": [Requirement(r).name for r in dist.requires] if dist.requires is not None else [],
"licenses": licenses,
}

def get_license(dist):
if dist.has_metadata(dist.PKG_INFO):
metadata = dist.get_metadata(dist.PKG_INFO)
match = regex_license.search(metadata)
if match:
license = match.group("license")
if license != "UNKNOWN": # Value when license not specified.
return [license]
if dist.metadata is not None:
license = dist.metadata.get('License-Expression') or dist.metadata.get('License')
if license is not None:
return [license]

return []

def get_licenses_from_classifiers(dist):
if dist.has_metadata(dist.PKG_INFO):
metadata = dist.get_metadata(dist.PKG_INFO)

# match might be found, but None if using the classifier:
# License :: OSI Approved
return [m for m in regex_classifier.findall(metadata) if m]
if dist.metadata is not None:
classifiers = dist.metadata.get_all('Classifier')
if classifiers is not None:
# match might be found, but None if using the classifier:
# License :: OSI Approved
return [l.group(1) for l in [regex_classifier.match(m) for m in classifiers] if l]

return []

Expand All @@ -191,11 +185,12 @@ def strip_license(license):
return license[: -len(" license")]
return license

resolve_func = resolve_without_deps if no_deps else resolve
packages = [transform(dist) for dist in resolve_func(requirements)]
packages = [transform(dist) for dist in resolve(requirements, without_deps=no_deps)]
# keep only unique values as there are maybe some duplicates
unique = []
[unique.append(item) for item in packages if item not in unique]
for item in packages:
if item not in unique:
unique.append(item)

return sorted(unique, key=(lambda item: item["name"].lower()))

Expand Down
28 changes: 11 additions & 17 deletions liccheck/requirements.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import pkg_resources
import importlib.metadata as ilm
from packaging.markers import Marker, default_environment
from packaging.requirements import Requirement

try:
from pip._internal.network.session import PipSession
Expand All @@ -24,29 +26,21 @@ def parse_requirements(requirement_file):
requirements = []
for req in pip_parse_requirements(requirement_file, session=PipSession()):
install_req = install_req_from_parsed_requirement(req)
if install_req.markers and not pkg_resources.evaluate_marker(str(install_req.markers)):
if install_req.markers is not None and not install_req.markers.evaluate():
# req should not installed due to env markers
continue
elif install_req.editable:
# skip editable req as they are failing in the resolve phase
continue
requirements.append(pkg_resources.Requirement.parse(str(install_req.req)))
requirements.append(install_req.req)
return requirements


def resolve_without_deps(requirements):
working_set = pkg_resources.working_set
def resolve(requirements, without_deps=False):
for req in requirements:
env = pkg_resources.Environment(working_set.entries)
dist = env.best_match(
req=req,
working_set=working_set,
installer=None,
replace_conflicting=False,
)
yield dist


def resolve(requirements):
for dist in pkg_resources.working_set.resolve(requirements):
dist = ilm.distribution(req.name)
yield dist
if not without_deps and dist.requires is not None:
requires = [Requirement(r) for r in dist.requires]
requires = [r for r in requires if r.marker is None or r.marker.evaluate()]
yield from resolve(requires)
12 changes: 3 additions & 9 deletions tests/test_get_packages_info.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import sys

import pkg_resources
import importlib.metadata as ilm
import pytest

from liccheck.command_line import get_packages_info
Expand All @@ -24,10 +24,7 @@ def test_license_strip_with_return_carriage(tmp_path, mocker):
tmpfh.write(b"Name: pip\r\n")
tmpfh.write(b"Version: 23.3.1\r\n")
tmpfh.write(b"Classifier: License :: OSI Approved :: MIT License\r\n")
metadata = pkg_resources.PathMetadata(tmp_path, tmp_path)
resolve.return_value = [
pkg_resources.Distribution(project_name="pip", metadata=metadata)
]
resolve.return_value = [ilm.Distribution.at(tmp_path)]
assert get_packages_info(req_path)[0]["licenses"] == ["MIT"]


Expand Down Expand Up @@ -86,8 +83,5 @@ def test_license_expression(tmp_path, mocker):
tmpfh.write("Name: Twisted\n")
tmpfh.write("Version: 23.8.0\n")
tmpfh.write("License-Expression: MIT\n")
metadata = pkg_resources.FileMetadata(pkg_info_path)
resolve.return_value = [
pkg_resources.Distribution(project_name="Twisted", metadata=metadata)
]
resolve.return_value = [ilm.Distribution.at(tmp_path)]
assert get_packages_info(req_path)[0]["licenses"] == ["MIT"]