FHPythonUtils/LicenseCheck

Question: Are the licenses of sub dependencies considered?

fhg-isi opened this issue · 6 comments

According to

https://itnext.io/how-to-detect-unwanted-licenses-in-your-python-project-c78ebdeb51df

"Let's say you want to avoid GPL, if you would just look at your requirements you might miss some. For example, Pyiotools is launched under an MIT license, but it has 4 dependencies with GPL. Meaning you will have to replace those 4 packages before you can use Pyiotools without GPL."

=> Does licensecheck consider the nested requirements or only the top level requirements?

Yes, if you have poetry installed on the host system we take the output of poetry show, which lists transitive dependencies

I see. So poetry needs to be installed and used in pyproject.toml. If I use hatchling and PEP631 it won't work.

a) Is there a way to determine the list of licenses with another tool, e.g. license_scanner and feed it to LicenseCheck?
Also see wagenrace/license_scanner#9

b) An alternative could be to ask the dependencies for their sub dependencies in getPackageInfoLocal:

pkgMetadata.get_all('Requires-Dist')

and use that to determine the requirements iteratively.

To a): Example on how to combine license_scanner with LicenseCheck:

from licensecheck import formatter, get_deps
from licensecheck import license_matrix, packageinfo
from licensecheck.types import JOINS, License, PackageInfo

from license_scanner import get_all_licenses

from sys import exit


def main():
    using = 'PEP631'
    ignore_packages = []
    fail_packages = []
    ignore_licenses = ['Apache Software License', 'Zope Public License']
    fail_licenses = []

    is_using_license_scanner = True
    if is_using_license_scanner:
        licenses_from_license_scanner = get_all_licenses()
        requirements = sum(licenses_from_license_scanner.values(), [])
    else:
        requirements = get_deps.getReqs(using)

    project_license, dependencies = _license_and_dependencies(
        using,
        ignore_packages,
        fail_packages,
        ignore_licenses,
        fail_licenses,
        requirements
    )

    simple_format = formatter.formatMap['simple']
    output = simple_format(project_license, sorted(dependencies))
    print(output)

    is_incompatible = any(not dependency.licenseCompat for dependency in dependencies)
    exit_code = 1 if is_incompatible else 0
    exit(exit_code)


def _license_and_dependencies(
    using: str,
    ignore_packages: list[str],
    fail_packages: list[str],
    ignore_licenses: list[str],
    fail_licenses: list[str],
    requirements: set[str] = None
) -> tuple[License, set[PackageInfo]]:
    if requirements is None:
        requirements = get_deps.getReqs(using)

    project_license = _project_license()
    packages = packageinfo.getPackages(requirements)

    for package in packages:
        _check_and_update_compatibility(
            package,
            project_license,
            fail_licenses,
            fail_packages,
            ignore_licenses,
            ignore_packages,
            )
    return project_license, packages


def _check_and_update_compatibility(
    package,
    project_license,
    fail_licenses,
    fail_packages,
    ignore_licenses,
    ignore_packages,
):
    package_name = package.name.lower()
    is_ignored_package = package_name in [package_name.lower() for package_name in ignore_packages]
    is_failing_package = package_name in [package_name.lower() for package_name in fail_packages]
    if is_ignored_package:
        package.licenseCompat = True
    elif is_failing_package:
        package.licenseCompat = False
    else:
        package.licenseCompat = license_matrix.depCompatWMyLice(
            project_license,
            license_matrix.licenseType(package.license),
            license_matrix.licenseType(JOINS.join(ignore_licenses)),
            license_matrix.licenseType(JOINS.join(fail_licenses)),
        )
    return package


def _project_license():
    project_license_text = packageinfo.getMyPackageLicense()
    project_license = license_matrix.licenseType(project_license_text)[0]
    return project_license


if __name__ == '__main__':
    main()

fixed in 2023.2

Could you please comment on how you fixed this?

https://github.com/FHPythonUtils/LicenseCheck/blob/master/licensecheck/get_deps.py Line 97 provides implementation details

Hope this helps ☺️