Giter Club home page Giter Club logo

pip-licenses's Introduction

pip-licenses

Build Status PyPI - Python Version PyPI version GitHub Release Codecov GitHub contributors BSD License PyPI - Downloads

Dump the software license list of Python packages installed with pip.

Table of Contents

Description

pip-licenses is a CLI tool for checking the software license of installed Python packages with pip.

Implemented with the idea inspired by composer licenses command in Composer (a.k.a PHP package management tool).

https://getcomposer.org/doc/03-cli.md#licenses

Installation

Install it via PyPI using pip command.

# Install or Upgrade to newest available version
$ pip install -U pip-licenses

# If upgrading from pip-licenses 3.x, remove PTable
$ pip uninstall -y PTable

Note for Python 3.7 users: pip-licenses 4.x discontinued support earlier than the Python 3.7 EOL schedule. If you want to use it with Python 3.7, install pip-licenses 3.x.

# Using old version for the Python 3.7 environment
$ pip install 'pip-licenses<4.0'

Note: If you are still using Python 2.7, install version less than 2.0. No new features will be provided for version 1.x.

$ pip install 'pip-licenses<2.0'

Usage

Execute the command with your venv (or virtualenv) environment.

# Install packages in your venv environment
(venv) $ pip install Django pip-licenses

# Check the licenses with your venv environment
(venv) $ pip-licenses
 Name    Version  License
 Django  2.0.2    BSD
 pytz    2017.3   MIT

Command-Line Options

Common options

Option: python

By default, this tools finds the packages from the environment pip-licenses is launched from, by searching in current python's sys.path folders. In the case you want to search for packages in an other environment (e.g. if you want to run pip-licenses from its own isolated environment), you can specify a path to a python executable. The packages will be searched for in the given python's sys.path, free of pip-licenses dependencies.

(venv) $ pip-licenses --with-system | grep pip
 pip                       22.3.1       MIT License
 pip-licenses              4.1.0        MIT License
(venv) $ pip-licenses --python=</path/to/other/env>/bin/python --with-system | grep pip
 pip                       23.0.1       MIT License 

Option: from

By default, this tool finds the license from Trove Classifiers or package Metadata. Some Python packages declare their license only in Trove Classifiers.

(See also): Set license to MIT in setup.py by alisianoi ・ Pull Request #1058 ・ pypa/setuptools, PEP 314#License

For example, even if you check with the pip show command, the license is displayed as UNKNOWN.

(venv) $ pip show setuptools
Name: setuptools
Version: 38.5.0
Summary: Easily download, build, install, upgrade, and uninstall Python packages
Home-page: https://github.com/pypa/setuptools
Author: Python Packaging Authority
Author-email: [email protected]
License: UNKNOWN

The mixed mode (--from=mixed) of this tool works well and looks for licenses.

(venv) $ pip-licenses --from=mixed --with-system | grep setuptools
 setuptools    38.5.0   MIT License

In mixed mode, it first tries to look for licenses in the Trove Classifiers. When not found in the Trove Classifiers, the license declared in Metadata is displayed.

If you want to look only in metadata, use --from=meta. If you want to look only in Trove Classifiers, use --from=classifier.

To list license information from both metadata and classifier, use --from=all.

Note: If neither can find license information, please check with the with-authors and with-urls options and contact the software author.

  • The m keyword is prepared as alias of meta.
  • The c keyword is prepared as alias of classifier.
  • The mix keyword is prepared as alias of mixed.
    • Default behavior in this tool

Option: order

By default, it is ordered by package name.

If you give arguments to the --order option, you can output in other sorted order.

(venv) $ pip-licenses --order=license

Option: format

By default, it is output to the plain format.

Markdown

When executed with the --format=markdown option, you can output list in markdown format. The m md keyword is prepared as alias of markdown.

(venv) $ pip-licenses --format=markdown
| Name   | Version | License |
|--------|---------|---------|
| Django | 2.0.2   | BSD     |
| pytz   | 2017.3  | MIT     |

When inserted in a markdown document, it is rendered as follows:

Name Version License
Django 2.0.2 BSD
pytz 2017.3 MIT
reST

When executed with the --format=rst option, you can output list in "Grid tables" of reStructuredText format. The r rest keyword is prepared as alias of rst.

(venv) $ pip-licenses --format=rst
+--------+---------+---------+
| Name   | Version | License |
+--------+---------+---------+
| Django | 2.0.2   | BSD     |
+--------+---------+---------+
| pytz   | 2017.3  | MIT     |
+--------+---------+---------+
Confluence

When executed with the --format=confluence option, you can output list in Confluence (or JIRA) Wiki markup format. The c keyword is prepared as alias of confluence.

(venv) $ pip-licenses --format=confluence
| Name   | Version | License |
| Django | 2.0.2   | BSD     |
| pytz   | 2017.3  | MIT     |
HTML

When executed with the --format=html option, you can output list in HTML table format. The h keyword is prepared as alias of html.

(venv) $ pip-licenses --format=html
<table>
    <tr>
        <th>Name</th>
        <th>Version</th>
        <th>License</th>
    </tr>
    <tr>
        <td>Django</td>
        <td>2.0.2</td>
        <td>BSD</td>
    </tr>
    <tr>
        <td>pytz</td>
        <td>2017.3</td>
        <td>MIT</td>
    </tr>
</table>
JSON

When executed with the --format=json option, you can output list in JSON format easily allowing post-processing. The j keyword is prepared as alias of json.

[
  {
    "Author": "Django Software Foundation",
    "License": "BSD",
    "Name": "Django",
    "URL": "https://www.djangoproject.com/",
    "Version": "2.0.2"
  },
  {
    "Author": "Stuart Bishop",
    "License": "MIT",
    "Name": "pytz",
    "URL": "http://pythonhosted.org/pytz",
    "Version": "2017.3"
  }
]
JSON LicenseFinder

When executed with the --format=json-license-finder option, you can output list in JSON format that is identical to LicenseFinder. The jlf keyword is prepared as alias of jlf. This makes pip-licenses a drop-in replacement for LicenseFinder.

[
  {
    "licenses": ["BSD"],
    "name": "Django",
    "version": "2.0.2"
  },
  {
    "licenses": ["MIT"],
    "name": "pytz",
    "version": "2017.3"
  }
]
CSV

When executed with the --format=csv option, you can output list in quoted CSV format. Useful when you want to copy/paste the output to an Excel sheet.

(venv) $ pip-licenses --format=csv
"Name","Version","License"
"Django","2.0.2","BSD"
"pytz","2017.3","MIT"
Plain Vertical

When executed with the --format=plain-vertical option, you can output a simple plain vertical output that is similar to Angular CLI's --extractLicenses flag. This format minimizes rightward drift.

(venv) $ pip-licenses --format=plain-vertical --with-license-file --no-license-path
pytest
5.3.4
MIT license
The MIT License (MIT)

Copyright (c) 2004-2020 Holger Krekel and others

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

Option: summary

When executed with the --summary option, you can output a summary of each license.

(venv) $ pip-licenses --summary --from=classifier --with-system
 Count  License
 2      BSD License
 4      MIT License

Note: When using this option, only --order=count or --order=license has an effect for the --order option. And using --with-authors and --with-urls will be ignored.

Option: output-file

When executed with the --output-file option, write the result to the path specified by the argument.

(venv) $ pip-licenses --format=rst --output-file=/tmp/output.rst
created path: /tmp/output.rst

Option: ignore-packages

When executed with the --ignore-packages option, ignore the package specified by argument from list output.

(venv) $ pip-licenses --ignore-packages django
 Name  Version  License
 pytz  2017.3   MIT

Package names of arguments can be separated by spaces.

(venv) $ pip-licenses --with-system --ignore-packages django pip pip-licenses
 Name        Version  License
 prettytable 3.5.0    BSD License
 pytz        2017.3   MIT
 setuptools  38.5.0   UNKNOWN
 wcwidth     0.2.5    MIT License

Packages can also be specified with a version, only ignoring that specific version.

(venv) $ pip-licenses --with-system --ignore-packages django pytz:2017.3
 Name        Version  License
 prettytable 3.5.0    BSD License
 setuptools  38.5.0   UNKNOWN
 wcwidth     0.2.5    MIT License

Option: packages

When executed with the packages option, look at the package specified by argument from list output.

(venv) $ pip-licenses --packages django
 Name   Version  License
 Django 2.0.2    BSD

Package names of arguments can be separated by spaces.

(venv) $ pip-licenses --with-system --packages prettytable pytz
 Name        Version  License
 prettytable 3.5.0    BSD License
 pytz        2017.3   MIT

Format options

Option: with-system

By default, system packages such as pip and setuptools are ignored.

And pip-licenses and the implicit dependency prettytable and wcwidth will also be ignored.

If you want to output all including system package, use the --with-system option.

(venv) $ pip-licenses --with-system
 Name          Version  License
 Django        2.0.2    BSD
 pip           9.0.1    MIT
 pip-licenses  1.0.0    MIT License
 prettytable   3.5.0    BSD License
 pytz          2017.3   MIT
 setuptools    38.5.0   UNKNOWN
 wcwidth       0.2.5    MIT License

Option: with-authors

When executed with the --with-authors option, output with author of the package.

(venv) $ pip-licenses --with-authors
 Name    Version  License  Author
 Django  2.0.2    BSD      Django Software Foundation
 pytz    2017.3   MIT      Stuart Bishop

Option: with-maintainers

When executed with the --with-maintainers option, output with maintainer of the package.

Note: This option is available for users who want information about the maintainer as well as the author. See #144

Option: with-urls

For packages without Metadata, the license is output as UNKNOWN. To get more package information, use the --with-urls option.

(venv) $ pip-licenses --with-urls
 Name    Version  License  URL
 Django  2.0.2    BSD      https://www.djangoproject.com/
 pytz    2017.3   MIT      http://pythonhosted.org/pytz

Option: with-description

When executed with the --with-description option, output with short description of the package.

(venv) $ pip-licenses --with-description
 Name    Version  License  Description
 Django  2.0.2    BSD      A high-level Python Web framework that encourages rapid development and clean, pragmatic design.
 pytz    2017.3   MIT      World timezone definitions, modern and historical

Option: no-version

When executed with the --no-version option, output without the version number.

(venv) $ pip-licenses --no-version
 Name    License
 Django  BSD
 pytz    MIT

Option: with-license-file

When executed with the --with-license-file option, output the location of the package's license file on disk and the full contents of that file. Due to the length of these fields, this option is best paired with --format=json.

If you also want to output the file NOTICE distributed under Apache License etc., specify the --with-notice-file option additionally.

Note: If you want to keep the license file path secret, specify --no-license-path option together.

Option: filter-strings

Some package data contains Unicode characters which might cause problems for certain output formats (in particular ReST tables). If this filter is enabled, all characters which cannot be encoded with a given code page (see --filter-code-page) will be removed from any input strings (e.g. package name, description).

Option: filter-code-page

If the input strings are filtered (see --filter-strings), you can specify the applied code page (default latin-1). A list of all available code pages can be found codecs module document.

Verify options

Option: fail-on

Fail (exit with code 1) on the first occurrence of the licenses of the semicolon-separated list. The license name matching is case-insensitive.

If --from=all, the option will apply to the metadata license field.

(venv) $ pip-licenses --fail-on="MIT License;BSD License"

Note: Packages with multiple licenses will fail if at least one license is included in the fail-on list. For example:

# keyring library has 2 licenses
$ pip-licenses --package keyring
 Name     Version  License
 keyring  23.0.1   MIT License; Python Software Foundation License

# If just "Python Software Foundation License" is specified, it will fail.
$ pip-licenses --package keyring --fail-on="Python Software Foundation License;"
$ echo $?
1

# Matching is case-insensitive. Following check will fail:
$ pip-licenses --fail-on="mit license"

Option: allow-only

Fail (exit with code 1) if none of the package licenses are in the semicolon-separated list. The license name matching is case-insensitive.

If --from=all, the option will apply to the metadata license field.

(venv) $ pip-licenses --allow-only="MIT License;BSD License"

Note: Packages with multiple licenses will only be allowed if at least one license is included in the allow-only list. For example:

# keyring library has 2 licenses
$ pip-licenses --package keyring
 Name     Version  License
 keyring  23.0.1   MIT License; Python Software Foundation License

# One or both licenses must be specified (order and case does not matter). Following checks will pass:
$ pip-licenses --package keyring --allow-only="MIT License"
$ pip-licenses --package keyring --allow-only="mit License"
$ pip-licenses --package keyring --allow-only="BSD License;MIT License"
$ pip-licenses --package keyring --allow-only="Python Software Foundation License"
$ pip-licenses --package keyring --allow-only="Python Software Foundation License;MIT License"

# If none of the license in the allow list match, the check will fail.
$ pip-licenses --package keyring  --allow-only="BSD License"
$ echo $?
1

Option: partial-match

If set, enables partial (substring) matching for --fail-on or --allow-only. Default is unset (False).

Usage:

(venv) $ pip-licenses --partial-match --allow-only="MIT License;BSD License"
(venv) $ pip-licenses --partial-match --fail-on="MIT License;BSD License"

Note: Semantics are the same as with --fail-on or --allow-only. This only enables substring matching.

# keyring library has 2 licenses
$ pip-licenses --package keyring
 Name     Version  License
 keyring  23.0.1   MIT License; Python Software Foundation License

# One or both licenses must be specified (order and case does not matter). Following checks will pass:
$ pip-licenses --package keyring --allow-only="MIT License"
$ pip-licenses --package keyring --allow-only="mit License"
$ pip-licenses --package keyring --allow-only="BSD License;MIT License"
$ pip-licenses --package keyring --allow-only="Python Software Foundation License"
$ pip-licenses --package keyring --allow-only="Python Software Foundation License;MIT License"

# These won't pass, as they're not a full match against one of the licenses
$ pip-licenses --package keyring --allow-only="MIT"
$ echo $?
1
$ pip-licenses --package keyring --allow-only="mit"
$ echo $?
1

# with --partial-match, they pass
$ pip-licenses --package keyring --partial-match --allow-only="MIT"
$ echo $?
0
$ pip-licenses --package keyring --partial-match --allow-only="mit"
$ echo $?
0

More Information

Other, please make sure to execute the --help option.

Dockerfile

You can check the package license used by your app in the isolated Docker environment.

# Clone this repository to local
$ git clone https://github.com/raimon49/pip-licenses.git
$ cd pip-licenses

# Create your app's requirements.txt file
# Other ways, pip freeze > docker/requirements.txt
$ echo "Flask" > docker/requirements.txt

# Build docker image
$ docker build . -t myapp-licenses

# Check the package license in container
$ docker run --rm myapp-licenses
 Name          Version  License
 Click         7.0      BSD License
 Flask         1.0.2    BSD License
 Jinja2        2.10     BSD License
 MarkupSafe    1.1.1    BSD License
 Werkzeug      0.15.2   BSD License
 itsdangerous  1.1.0    BSD License

# Check with options
$ docker run --rm myapp-licenses --summary
 Count  License
 4      BSD
 2      BSD-3-Clause

# When you need help
$ docker run --rm myapp-licenses --help

Note: This Docker image can not check package licenses with C and C ++ Extensions. It only works with pure Python package dependencies.

If you want to resolve build environment issues, try using not slim image and more.

diff --git a/Dockerfile b/Dockerfile
index bfc4edc..175e968 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,4 @@
-FROM python:3.11-slim-bullseye
+FROM python:3.11-bullseye

About UnicodeEncodeError

If a UnicodeEncodeError occurs, check your environment variables LANG and LC_TYPE. Additionally, you can set PYTHONIOENCODING to override the encoding used for stdout.

Often occurs in isolated environments such as Docker and tox.

See useful reports:

License

MIT License

Dependencies

  • prettytable by Luke Maurits and maintainer of fork version Jazzband team under the BSD-3-Clause License
    • Note: This package implicitly requires wcwidth.

pip-licenses has been implemented in the policy to minimize the dependence on external package.

Uninstallation

Uninstall package and dependent package with pip command.

$ pip uninstall pip-licenses prettytable wcwidth

Contributing

See contribution guidelines.

pip-licenses's People

Contributors

adamchainz avatar akarys42 avatar b-kamphorst avatar cdce8p avatar clementpinard avatar costasd avatar cpeel avatar dennybiasiolli avatar dependabot[bot] avatar dkarp0 avatar dtatarkin avatar g3n35i5 avatar imrehg avatar jaimemf avatar jameslamb avatar jayvdb avatar jnhmcknight avatar johnthagen avatar khatkar avatar maldo avatar raimon49 avatar rofafor avatar slavaskvortsov avatar thejcannon avatar tracteurblinde avatar vnagendra avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

pip-licenses's Issues

`--format=json` broken with `prettytable`

Hi,

I was using this tool sucessfully until I added prettytable to my project (for other uses), then it broke. It seems that previously it was using the Ptable 'fallback' but once prettytable was installed it preferentially used that, and errored.

pip-licenses V3.5.3
prettytable V2.5.0
poetry V1.1.8
Python V3.9

Traceback

➜ poetry run pip-licenses --format=json
Traceback (most recent call last):
  File "/Users/me/Library/Caches/pypoetry/virtualenvs/project-fJIZ19Z2-py3.9/bin/pip-licenses", line 8, in <module>
    sys.exit(main())
  File "/Users/me/Library/Caches/pypoetry/virtualenvs/project-fJIZ19Z2-py3.9/lib/python3.9/site-packages/piplicenses.py", line 893, in main
    output_string = create_output_string(args)
  File "/Users/me/Library/Caches/pypoetry/virtualenvs/project-fJIZ19Z2-py3.9/lib/python3.9/site-packages/piplicenses.py", line 558, in create_output_string
    return table.get_string(fields=output_fields, sortby=sortby)
  File "/Users/me/Library/Caches/pypoetry/virtualenvs/project-fJIZ19Z2-py3.9/lib/python3.9/site-packages/piplicenses.py", line 349, in get_string
    formatted_rows = self._format_rows(rows, options)
TypeError: _format_rows() takes 2 positional arguments but 3 were given

Packages with multiple LICENSES files not shown in --with-license-file

$ pip install -U pip pip-licenses
$ pip install opencv-python-headless==4.2.0.34
$ pip-licenses --from=mixed --with-license-report

The opencv-python-headless wheels include two license files, LICENSE.txt and LICENSE-3RD-PARTY.txt, but only LICENSE-3RD-PARTY.txt is displayed. Could support be added to display both license files since some packages like opencv have multiple files covering different parts of the library.


Format LicensePath LicenseText
Plain/Plain Vertical \n \n\n
JSON JSON List [] JSON List []
CSV ;
HTML

Consider using trove/mixed as default license information source

I've learned that trove classifiers are the preferred way to express license type information. See this note for explanation:

I believe that the current default for pip-licenses --from is not ideal (basically it always needs to be changed to get the best license information.

Would you consider changing the default to either trove or mixed? I personally feel mixed is the best default because it will provide the most comprehensive license information and prefer the correct source of truth (the troves).

I find this particularly compelling:

The license field is a text indicating the license covering the package where the license is not a selection from the “License” Trove classifiers. See the Classifier field. Notice that there’s a licence distribution option which is deprecated but still acts as an alias for license.

Feature Request: Print information about different venv environments.

[Feature Request] Print information about different venv environments.

Details:

  • Print python module information for venvs in different paths in separate scripts.
    (ex- If you pass the path of another venv, you can print it.)

Reasons needed:

  • It is necessary to print each python module for multiple venv environments in CI/CD environment.

(Frozen release plans) Progress to 4.0

This issue should track the progress to the next major release and provide a place to have discussions about features.

Features

  • Improve argparse #87
  • Move to package structure #88

Ideas

  • Add support for PEP 639 -> SPDX license tags
  • Update PrettyTable dependency #82
  • Move from cli only application to cli + lib #81
  • Add typing + type checking (mypy)
  • Improve CI test -> add environment caching
  • Add ability to specify options in setup.cfg
  • Improve usage for CI license validation -> add some sort of license pinning
  • Add support for importlib.metadata (Python 3.8 and up). Replaces get_installed_distributions
  • Add authors file
  • Add settings files for VS Code
  • Support multiple license files per package

The ideas are subject to change and might not all get implemented. I'll move ideas to the feature column once a PR is created.

Discussions

Add flake8, pylint
Those checks are relatively easy to add and might at some code quality improvements. At least the provide useful guides when used with IDEs (eg. VS Code)

Require Python 3.7
I'm not completely sure about this one. But some good arguments in favor

  • Python 3.6 will be deprecated at the end of the year
  • The switch to 3.7 would allow the use of Data classes and Postponed evaluation of annotations, the last being very helpful (but not required) for type annotations.
  • The move to 4.0 will already be a breaking change, so it would be easy to add it on.
  • Most users will most likely support Python 3.7 and up, given it's already three years old
  • Users that require Python 3.6 can still use the current version which works perfectly fine. They will probably upgrade soon anyway.

Non-deterministic order of multiple licenses using pip-licenses 3.5.0

E.g. in a project only having tqdm after running pip-licenses --from=mixed --format=csv multiple times I get either of the two outputs:

"tqdm","4.61.1","Mozilla Public License 2.0 (MPL 2.0); MIT License"
"tqdm","4.61.1","MIT License; Mozilla Public License 2.0 (MPL 2.0)"

I believe this could be due to the change in #102 using set operations which are not guaranteed to have deterministic outputs.

This impacts the way we use pip-licenses on CI where we check if there are any differences between the current set of licenses and the latest licenses, the check will fail now due to the random order multi-licenses are returned.

Update to pip-21.3 breaks pip-licenses

/mnt/user$ pip-licenses -uf json

Traceback (most recent call last):
  File "/home/user/.local/lib/python3.8/site-packages/piplicenses.py", line 42, in <module>
    from pip._internal.utils.misc import get_installed_distributions
ImportError: cannot import name 'get_installed_distributions' from 'pip._internal.utils.misc' (/home/user/.local/lib/python3.8/site-packages/pip/_internal/utils/misc.py)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/user/.local/bin/pip-licenses", line 5, in <module>
    from piplicenses import main
  File "/home/user/.local/lib/python3.8/site-packages/piplicenses.py", line 44, in <module>
    from pip import get_installed_distributions
ImportError: cannot import name 'get_installed_distributions' from 'pip' (/home/user/.local/lib/python3.8/site-packages/pip/__init__.py)

feature_request(path): Custom packages path support

1. Summary

It would be nice, if pip-licenses will support check licenses from custom path.

2. Argumentation

Example: I use Pelican — static site generator and plugins for it, written by Python. Pelican plugins must installed to custom folder — usually pelican-plugins, not to default site-packages. I don't know, how possible check licenses from pelican-plugins folder.

3. Example of expected behavior

If:

pip-licenses --path output/pelican-plugins

pip-licenses will check licenses from output/pelican-plugins folder recursively.

4. Not helped

I don't find already existing packages, that solve this problem. For example, I try:

Thanks.

pip-licenses broken in python 3.11

pip-licenses is broken in python 3.11 for pip>=22.1.

The issue is roughly the same as in #113. Log:

$ pip-licenses
Traceback (most recent call last):
  File "/builds/gitlab/.../.venv/bin/pip-licenses", line 8, in <module>
    sys.exit(main())
             ^^^^^^
  File "/builds/gitlab/.../stubs/.venv/lib/python3.11/site-packages/piplicenses.py", line 900, in main
    output_string = create_output_string(args)
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/builds/gitlab/.../stubs/.venv/lib/python3.11/site-packages/piplicenses.py", line 558, in create_output_string
    table = create_licenses_table(args, output_fields)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/builds/gitlab/.../stubs/.venv/lib/python3.11/site-packages/piplicenses.py", line 303, in create_licenses_table
    for pkg in get_packages(args):
  File "/builds/gitlab/.../stubs/.venv/lib/python3.11/site-packages/piplicenses.py", line 2[54](...), in get_packages
    pkg_name = pkg.project_name
               ^^^^^^^^^^^^^^^^
AttributeError: 'PathDistribution' object has no attribute 'project_name'

pip-licenses scan NOTICE.* file as well

We are using pip-licenses to collect LICENSES of third party dependencies, which works well. Is it possible to add NOTICE files as well so both LICENSE and NOTICE are returned if available.

Feature Request <> License information from PyPi

In using pip-licenses, I've found that some packages which do non-standard things with their license information are categorized as UNKNOWN even when they use mainstream OSS licenses.

PyPi exposes a /json endpoint that allows you to get a JSON representation of the metadata for a package.

One example I've seen is pytest-cache.

import requests

res = requests.get('https://pypi.org/pypi/pytest-cache/json').json()['info']
print(res)

license = res['license']

>>> res['license']
'MIT License'

Running this through the current pip-licenses setup yields UNKNOWN (running in non-Docker mode because I don't want to do the "only from trove classifiers" version)

echo "pytest-cache" > docker/requirements.txt
pip install pytest-cache
pip-licenses | grep pytest

 pytest                       3.6.3       MIT license
 pytest-cache                 1.0         UNKNOWN
 pytest-cov                   2.6.1       MIT

@raimon49 would you be open to a contribution that adds a method to get package metadata from PyPi? I would propose wrapping it in a try-catch that fails gracefully so we don't add a new restriction "must be connected to the internet to run pip-licenses".

If you are open to this idea, I'd like to try implementing and PR-ing in this feature.

Thanks for considering it!

Decoding error on license file on macOS

Same issue as #35 and #37 but on macOS with a standard Python 3 install, not in a docker container.

Tested on 1.16.1.

$ pip-licenses --from=mixed --with-system --format=rst --with-license-file
Traceback (most recent call last):
  File "/Users/user/proj/.tox/licenses/bin/pip-licenses", line 8, in <module>
    sys.exit(main())
  File "/Users/user/proj/.tox/licenses/site-packages/piplicenses.py", line 638, in main
    print(output_string)
  File "/Users/user/proj/.tox/licenses/lib-python/3/encodings/ascii.py", line 22, in encode
    return codecs.ascii_encode(input, self.errors)[0]
UnicodeEncodeError: 'ascii' codec can't encode character '\xa9' in position 167881: ordinal not in range(128)

Python prompt for reference:

$ python
Python 3.6.9 (5da45ced70e5, Oct 09 2019, 19:13:07)
[PyPy 7.2.0 with GCC 4.2.1 Compatible Apple LLVM 10.0.0 (clang-1000.11.45.5)] on darwin
Type "help", "copyright", "credits" or "license" for more information.

Inconsistent default behavior of --summary option

Consider the following virtual environment:

$ python -m pip list
Package                       Version
----------------------------- ----------
alabaster                     0.7.12
appdirs                       1.4.4
atomicwrites                  1.4.0
attrs                         20.2.0
Babel                         2.8.0
black                         20.8b1
certifi                       2020.6.20
chardet                       3.0.4
click                         7.1.2
colorama                      0.4.4
coverage                      5.3
distlib                       0.3.1
docutils                      0.16
exitstatus                    2.0.1
filelock                      3.0.12
flake8                        3.8.4
flake8-polyfill               1.0.2
idna                          2.10
imagesize                     1.2.0
iniconfig                     1.1.1
isort                         5.6.4
Jinja2                        2.11.2
MarkupSafe                    1.1.1
mccabe                        0.6.1
mypy                          0.790
mypy-extensions               0.4.3
packaging                     20.4
pathspec                      0.8.0
pep8-naming                   0.11.1
pip                           21.0.1
pip-licenses                  3.3.0
pip-tools                     5.3.1
pluggy                        0.13.1
PTable                        0.9.2
py                            1.9.0
pycodestyle                   2.6.0
pyflakes                      2.2.0
Pygments                      2.7.2
pyparsing                     2.4.7
pytest                        6.1.2
pytest-cov                    2.10.1
pytz                          2020.1
regex                         2020.10.28
requests                      2.24.0
setuptools                    50.3.0
six                           1.15.0
snowballstemmer               2.0.0
Sphinx                        3.2.1
sphinxcontrib-applehelp       1.0.2
sphinxcontrib-devhelp         1.0.2
sphinxcontrib-htmlhelp        1.0.3
sphinxcontrib-jsmath          1.0.1
sphinxcontrib-qthelp          1.0.3
sphinxcontrib-serializinghtml 1.1.4
toml                          0.10.1
tox                           3.20.1
typed-ast                     1.4.1
typing-extensions             3.7.4.3
urllib3                       1.25.11
virtualenv                    20.1.0

Running pip-licenses produces pleasant output:

$ pip-licenses
 Name                           Version     License
 Babel                          2.8.0       BSD License
 Jinja2                         2.11.2      BSD License
 MarkupSafe                     1.1.1       BSD License
 Pygments                       2.7.2       BSD License
 Sphinx                         3.2.1       BSD License
 alabaster                      0.7.12      BSD License
 appdirs                        1.4.4       MIT License
 atomicwrites                   1.4.0       MIT License
 attrs                          20.2.0      MIT License
 black                          20.8b1      MIT License
 certifi                        2020.6.20   Mozilla Public License 2.0 (MPL 2.0)
 chardet                        3.0.4       GNU Library or Lesser General Public License (LGPL)
 click                          7.1.2       BSD License
 colorama                       0.4.4       BSD License
 coverage                       5.3         Apache Software License
 distlib                        0.3.1       Python Software Foundation License
 docutils                       0.16        Public Domain, Python Software Foundation License, BSD License, GNU General Public License (GPL)
 exitstatus                     2.0.1       MIT License
 filelock                       3.0.12      Public Domain
 flake8                         3.8.4       MIT License
 flake8-polyfill                1.0.2       MIT License
 idna                           2.10        BSD License
 imagesize                      1.2.0       MIT License
 iniconfig                      1.1.1       MIT License
 isort                          5.6.4       MIT License
 mccabe                         0.6.1       MIT License
 mypy                           0.790       MIT License
 mypy-extensions                0.4.3       MIT License
 packaging                      20.4        Apache Software License, BSD License
 pathspec                       0.8.0       Mozilla Public License 2.0 (MPL 2.0)
 pep8-naming                    0.11.1      MIT License
 pip-tools                      5.3.1       BSD License
 pluggy                         0.13.1      MIT License
 py                             1.9.0       MIT License
 pycodestyle                    2.6.0       MIT License
 pyflakes                       2.2.0       MIT License
 pyparsing                      2.4.7       MIT License
 pytest                         6.1.2       MIT License
 pytest-cov                     2.10.1      BSD License
 pytz                           2020.1      MIT License
 regex                          2020.10.28  Apache Software License
 requests                       2.24.0      Apache Software License
 six                            1.15.0      MIT License
 snowballstemmer                2.0.0       BSD License
 sphinxcontrib-applehelp        1.0.2       BSD License
 sphinxcontrib-devhelp          1.0.2       BSD License
 sphinxcontrib-htmlhelp         1.0.3       BSD License
 sphinxcontrib-jsmath           1.0.1       BSD License
 sphinxcontrib-qthelp           1.0.3       BSD License
 sphinxcontrib-serializinghtml  1.1.4       BSD License
 toml                           0.10.1      MIT License
 tox                            3.20.1      MIT License
 typed-ast                      1.4.1       Apache License 2.0
 typing-extensions              3.7.4.3     Python Software Foundation License
 urllib3                        1.25.11     MIT License
 virtualenv                     20.1.0      MIT License

But the --summary option seems to generate a different set of license string names (almost as if --from=mixed isn't used (#60)):

$ pip-licenses --summary
 Count  License
 2      Apache 2.0
 1      Apache License 2.0
 1      Apache Software License
 10     BSD
 1      BSD License
 1      BSD-2-Clause or Apache-2.0
 4      BSD-3-Clause
 1      BSD-like
 3      Expat license
 1      LGPL
 18     MIT
 4      MIT License
 2      MIT license
 1      MPL 2.0
 1      MPL-2.0
 1      PSF
 1      Public Domain <http://unlicense.org>
 1      Python license
 1      UNKNOWN
 1      public domain, Python, 2-Clause BSD, GPL 3 (see COPYING.txt)

I would expect pip-licenses --summary to output an aggregated list of licenses using the license string names shown in pip-licenses. This has the advantage that the --from=mixed names are much more uniform, and thus group more nicely.

Order of glob depends on environment

The way that pip-licenses finds license objects in a repo, is by globbing LICENSE*, LICENCE* and COPYING*. However, the output of glob.glob in Python seems to be dependant on the environment.

pip-licenses/piplicenses.py

Lines 131 to 151 in 9c83259

def get_pkg_included_file(pkg, file_names):
"""
Attempt to find the package's included file on disk and return the
tuple (included_file_path, included_file_contents).
"""
included_file = LICENSE_UNKNOWN
included_text = LICENSE_UNKNOWN
pkg_dirname = "{}-{}.dist-info".format(
pkg.project_name.replace("-", "_"), pkg.version)
patterns = []
[patterns.extend(glob.glob(os.path.join(pkg.location,
pkg_dirname,
f))) for f in file_names]
for test_file in patterns:
if os.path.exists(test_file):
included_file = test_file
with open(test_file, encoding='utf-8',
errors='backslashreplace') as included_file_handle:
included_text = included_file_handle.read()
break
return (included_file, included_text)

This becomes an issue in repositories like cryptography, where you have 3 different license files: LICENSE.APACHE, LICENSE.BSD and LICENSE.PSF. Therefore, depending on the environment, pip-licenses will choose one or another.

Suggested solution

One way to fix this would be to sort the output of glob.glob(), so that it's consistent regardless of the environment.

Better handling of licenses of pep-0621 based libraries

Some libs (like scipy ) switched to a pyproject.toml-File according to PEP-0621.

This currently results in a strange situation. The licenses-Metadata no longer contains the short (possibly SPDX-conform) licenses Identifier but the full licenses text.

Example: https://pypi.org/project/scipy/1.9.3/ vs https://pypi.org/project/scipy/1.9.1/

I don't see an easy way to "fix" this with pip-licenses.
But maybe someone here has an idea about how to handle this.

Options --ignore-packages and --allow-only together not running as expected

Consider the follow example:

export ALLOWED_PACKAGES="wrapt typing-extensions s3transfer"
export ALLOWED_LICENSES="MIT License;Apache Software License"

When I run:
pip-licenses --allow-only=${ALLOWED_LICENSES} --ignore-packages="${ALLOWED_PACKAGES}

I get:
license BSD License not in allow-only licenses was found for package wrapt

When I expected is the package wrapt (or any package in --ignore-packages option) to be completely ignored regardless of the license type.

Docker workflow is broken for packages that require compiling source distributions of C code

Today I ran this:

echo "pandas" > docker/requirements.txt
docker build -t myapp-licenses .

This resulted in a long stacktrace ending in the following:

        cmd_obj.run()
      File "/tmp/easy_install-egl5coo_/numpy-1.16.2/numpy/distutils/command/build_src.py", line 148, in run
      File "/tmp/easy_install-egl5coo_/numpy-1.16.2/numpy/distutils/command/build_src.py", line 159, in build_sources
      File "/tmp/easy_install-egl5coo_/numpy-1.16.2/numpy/distutils/command/build_src.py", line 292, in build_library_sources
      File "/tmp/easy_install-egl5coo_/numpy-1.16.2/numpy/distutils/command/build_src.py", line 375, in generate_sources
      File "numpy/core/setup.py", line 667, in get_mathlib_info
        }
    RuntimeError: Broken toolchain: cannot link a simple C program
Full log ``` Sending build context to Docker daemon 260.6kB Step 1/9 : FROM python:3.7.2-alpine ---> bb1ccaa5880c Step 2/9 : LABEL maintainer="raimon " ---> Using cache ---> 3cdaa5189f9b Step 3/9 : ARG APPDIR=/opt/piplicenses ---> Using cache ---> ed8dba54bf41 Step 4/9 : WORKDIR ${APPDIR} ---&gt; Using cache ---&gt; 20e7835f3c04 Step 5/9 : COPY ./docker/requirements.txt ${APPDIR} ---> Using cache ---> c9cb8a93ad11 Step 6/9 : RUN python3 -m venv ${APPDIR}/myapp && source ${APPDIR}/myapp/bin/activate ---> Using cache ---> 6d95570cbb70 Step 7/9 : RUN pip3 install -U pip && pip3 install -r ${APPDIR}/requirements.txt && pip3 install -U pip-licenses ---> Running in 54535904b617 Requirement already up-to-date: pip in /usr/local/lib/python3.7/site-packages (19.0.3) Collecting pandas (from -r /opt/piplicenses/requirements.txt (line 1)) Downloading https://files.pythonhosted.org/packages/b2/4c/b6f966ac91c5670ba4ef0b0b5613b5379e3c7abdfad4e7b89a87d73bae13/pandas-0.24.2.tar.gz (11.8MB) Complete output from command python setup.py egg_info: /bin/sh: svnversion: not found non-existing path in 'numpy/distutils': 'site.cfg' Could not locate executable gfortran Could not locate executable f95 Could not locate executable ifort Could not locate executable ifc Could not locate executable lf95 Could not locate executable pgfortran Could not locate executable f90 Could not locate executable f77 Could not locate executable fort Could not locate executable efort Could not locate executable efc Could not locate executable g77 Could not locate executable g95 Could not locate executable pathf95 Could not locate executable nagfor don't know how to compile Fortran code on platform 'posix' Running from numpy source directory. /tmp/easy_install-warv5lo9/numpy-1.16.2/setup.py:390: UserWarning: Unrecognized setuptools command, proceeding with generating Cython sources and expanding templates run_build = parse_setuppy_commands() /tmp/easy_install-warv5lo9/numpy-1.16.2/numpy/distutils/system_info.py:638: UserWarning: Atlas (http://math-atlas.sourceforge.net/) libraries not found. Directories to search for the libraries can be specified in the numpy/distutils/site.cfg file (section [atlas]) or by setting the ATLAS environment variable. self.calc_info() /tmp/easy_install-warv5lo9/numpy-1.16.2/numpy/distutils/system_info.py:638: UserWarning: Blas (http://www.netlib.org/blas/) libraries not found. Directories to search for the libraries can be specified in the numpy/distutils/site.cfg file (section [blas]) or by setting the BLAS environment variable. self.calc_info() /tmp/easy_install-warv5lo9/numpy-1.16.2/numpy/distutils/system_info.py:638: UserWarning: Blas (http://www.netlib.org/blas/) sources not found. Directories to search for the sources can be specified in the numpy/distutils/site.cfg file (section [blas_src]) or by setting the BLAS_SRC environment variable. self.calc_info() /tmp/easy_install-warv5lo9/numpy-1.16.2/numpy/distutils/system_info.py:638: UserWarning: Lapack (http://www.netlib.org/lapack/) libraries not found. Directories to search for the libraries can be specified in the numpy/distutils/site.cfg file (section [lapack]) or by setting the LAPACK environment variable. self.calc_info() /tmp/easy_install-warv5lo9/numpy-1.16.2/numpy/distutils/system_info.py:638: UserWarning: Lapack (http://www.netlib.org/lapack/) sources not found. Directories to search for the sources can be specified in the numpy/distutils/site.cfg file (section [lapack_src]) or by setting the LAPACK_SRC environment variable. self.calc_info() /usr/local/lib/python3.7/distutils/dist.py:274: UserWarning: Unknown distribution option: 'define_macros' warnings.warn(msg) Traceback (most recent call last): File "/usr/local/lib/python3.7/site-packages/setuptools/sandbox.py", line 154, in save_modules yield saved File "/usr/local/lib/python3.7/site-packages/setuptools/sandbox.py", line 195, in setup_context yield File "/usr/local/lib/python3.7/site-packages/setuptools/sandbox.py", line 250, in run_setup _execfile(setup_script, ns) File "/usr/local/lib/python3.7/site-packages/setuptools/sandbox.py", line 45, in _execfile exec(code, globals, locals) File "/tmp/easy_install-warv5lo9/numpy-1.16.2/setup.py", line 415, in
  File "/tmp/easy_install-warv5lo9/numpy-1.16.2/setup.py", line 407, in setup_package
    cmdclass['cython'] = CythonCommand
  File "/tmp/easy_install-warv5lo9/numpy-1.16.2/numpy/distutils/core.py", line 171, in setup
  File "/usr/local/lib/python3.7/site-packages/setuptools/__init__.py", line 145, in setup
    return distutils.core.setup(**attrs)
  File "/usr/local/lib/python3.7/distutils/core.py", line 148, in setup
    dist.run_commands()
  File "/usr/local/lib/python3.7/distutils/dist.py", line 966, in run_commands
    self.run_command(cmd)
  File "/usr/local/lib/python3.7/distutils/dist.py", line 985, in run_command
    cmd_obj.run()
  File "/usr/local/lib/python3.7/site-packages/setuptools/command/bdist_egg.py", line 163, in run
    self.run_command("egg_info")
  File "/usr/local/lib/python3.7/distutils/cmd.py", line 313, in run_command
    self.distribution.run_command(command)
  File "/usr/local/lib/python3.7/distutils/dist.py", line 985, in run_command
    cmd_obj.run()
  File "/tmp/easy_install-warv5lo9/numpy-1.16.2/numpy/distutils/command/egg_info.py", line 26, in run
  File "/usr/local/lib/python3.7/distutils/cmd.py", line 313, in run_command
    self.distribution.run_command(command)
  File "/usr/local/lib/python3.7/distutils/dist.py", line 985, in run_command
    cmd_obj.run()
  File "/tmp/easy_install-warv5lo9/numpy-1.16.2/numpy/distutils/command/build_src.py", line 148, in run
  File "/tmp/easy_install-warv5lo9/numpy-1.16.2/numpy/distutils/command/build_src.py", line 159, in build_sources
  File "/tmp/easy_install-warv5lo9/numpy-1.16.2/numpy/distutils/command/build_src.py", line 292, in build_library_sources
  File "/tmp/easy_install-warv5lo9/numpy-1.16.2/numpy/distutils/command/build_src.py", line 375, in generate_sources
  File "numpy/core/setup.py", line 667, in get_mathlib_info
    }
RuntimeError: Broken toolchain: cannot link a simple C program

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/tmp/pip-install-vi1uiug3/pandas/setup.py", line 746, in <module>
    **setuptools_kwargs)
  File "/usr/local/lib/python3.7/site-packages/setuptools/__init__.py", line 144, in setup
    _install_setup_requires(attrs)
  File "/usr/local/lib/python3.7/site-packages/setuptools/__init__.py", line 139, in _install_setup_requires
    dist.fetch_build_eggs(dist.setup_requires)
  File "/usr/local/lib/python3.7/site-packages/setuptools/dist.py", line 724, in fetch_build_eggs
    replace_conflicting=True,
  File "/usr/local/lib/python3.7/site-packages/pkg_resources/__init__.py", line 782, in resolve
    replace_conflicting=replace_conflicting
  File "/usr/local/lib/python3.7/site-packages/pkg_resources/__init__.py", line 1065, in best_match
    return self.obtain(req, installer)
  File "/usr/local/lib/python3.7/site-packages/pkg_resources/__init__.py", line 1077, in obtain
    return installer(requirement)
  File "/usr/local/lib/python3.7/site-packages/setuptools/dist.py", line 791, in fetch_build_egg
    return cmd.easy_install(req)
  File "/usr/local/lib/python3.7/site-packages/setuptools/command/easy_install.py", line 679, in easy_install
    return self.install_item(spec, dist.location, tmpdir, deps)
  File "/usr/local/lib/python3.7/site-packages/setuptools/command/easy_install.py", line 705, in install_item
    dists = self.install_eggs(spec, download, tmpdir)
  File "/usr/local/lib/python3.7/site-packages/setuptools/command/easy_install.py", line 890, in install_eggs
    return self.build_and_install(setup_script, setup_base)
  File "/usr/local/lib/python3.7/site-packages/setuptools/command/easy_install.py", line 1158, in build_and_install
    self.run_setup(setup_script, setup_base, args)
  File "/usr/local/lib/python3.7/site-packages/setuptools/command/easy_install.py", line 1144, in run_setup
    run_setup(setup_script, args)
  File "/usr/local/lib/python3.7/site-packages/setuptools/sandbox.py", line 253, in run_setup
    raise
  File "/usr/local/lib/python3.7/contextlib.py", line 130, in __exit__
    self.gen.throw(type, value, traceback)
  File "/usr/local/lib/python3.7/site-packages/setuptools/sandbox.py", line 195, in setup_context
    yield
  File "/usr/local/lib/python3.7/contextlib.py", line 130, in __exit__
    self.gen.throw(type, value, traceback)
  File "/usr/local/lib/python3.7/site-packages/setuptools/sandbox.py", line 166, in save_modules
    saved_exc.resume()
  File "/usr/local/lib/python3.7/site-packages/setuptools/sandbox.py", line 141, in resume
    six.reraise(type, exc, self._tb)
  File "/usr/local/lib/python3.7/site-packages/setuptools/_vendor/six.py", line 685, in reraise
    raise value.with_traceback(tb)
  File "/usr/local/lib/python3.7/site-packages/setuptools/sandbox.py", line 154, in save_modules
    yield saved
  File "/usr/local/lib/python3.7/site-packages/setuptools/sandbox.py", line 195, in setup_context
    yield
  File "/usr/local/lib/python3.7/site-packages/setuptools/sandbox.py", line 250, in run_setup
    _execfile(setup_script, ns)
  File "/usr/local/lib/python3.7/site-packages/setuptools/sandbox.py", line 45, in _execfile
    exec(code, globals, locals)
  File "/tmp/easy_install-warv5lo9/numpy-1.16.2/setup.py", line 415, in <module>

  File "/tmp/easy_install-warv5lo9/numpy-1.16.2/setup.py", line 407, in setup_package
    cmdclass['cython'] = CythonCommand
  File "/tmp/easy_install-warv5lo9/numpy-1.16.2/numpy/distutils/core.py", line 171, in setup
  File "/usr/local/lib/python3.7/site-packages/setuptools/__init__.py", line 145, in setup
    return distutils.core.setup(**attrs)
  File "/usr/local/lib/python3.7/distutils/core.py", line 148, in setup
    dist.run_commands()
  File "/usr/local/lib/python3.7/distutils/dist.py", line 966, in run_commands
    self.run_command(cmd)
  File "/usr/local/lib/python3.7/distutils/dist.py", line 985, in run_command
    cmd_obj.run()
  File "/usr/local/lib/python3.7/site-packages/setuptools/command/bdist_egg.py", line 163, in run
    self.run_command("egg_info")
  File "/usr/local/lib/python3.7/distutils/cmd.py", line 313, in run_command
    self.distribution.run_command(command)
  File "/usr/local/lib/python3.7/distutils/dist.py", line 985, in run_command
    cmd_obj.run()
  File "/tmp/easy_install-warv5lo9/numpy-1.16.2/numpy/distutils/command/egg_info.py", line 26, in run
  File "/usr/local/lib/python3.7/distutils/cmd.py", line 313, in run_command
    self.distribution.run_command(command)
  File "/usr/local/lib/python3.7/distutils/dist.py", line 985, in run_command
    cmd_obj.run()
  File "/tmp/easy_install-warv5lo9/numpy-1.16.2/numpy/distutils/command/build_src.py", line 148, in run
  File "/tmp/easy_install-warv5lo9/numpy-1.16.2/numpy/distutils/command/build_src.py", line 159, in build_sources
  File "/tmp/easy_install-warv5lo9/numpy-1.16.2/numpy/distutils/command/build_src.py", line 292, in build_library_sources
  File "/tmp/easy_install-warv5lo9/numpy-1.16.2/numpy/distutils/command/build_src.py", line 375, in generate_sources
  File "numpy/core/setup.py", line 667, in get_mathlib_info
    }
RuntimeError: Broken toolchain: cannot link a simple C program

----------------------------------------

Command "python setup.py egg_info" failed with error code 1 in /tmp/pip-install-vi1uiug3/pandas/
The command '/bin/sh -c pip3 install -U pip && pip3 install -r ${APPDIR}/requirements.txt && pip3 install -U pip-licenses' returned a non-zero code: 1

</details>

I got similar issues with other packages that include source distros of C/C++ which need to be compiled on install. For example

echo "xgboost" > docker/requirements.txt
docker build -t myapp-licenses .

Question: Why is pip-licenses considered to be part of system packages?

I have noticed that pip-licenses (as well as PTable) is considered to be part of the system packages, at least the --with-system argument is needed to show corresponding license information.

I am not sure I understand the reason for this?
For me it is a tool that I have added to my requirements file as many other tools. Then when I am trying to gather license information, I definitely would like it to show up in the normal(non system) list.

Thanks for providing a very useful tool
Regards Fredrik

`--format=json` outputs only license, name, and version

From the documentation, it looks like pip-licenses --format=json should output a json with 5 keys:

  {
    "Author": "Django Software Foundation",
    "License": "BSD",
    "Name": "Django",
    "URL": "https://www.djangoproject.com/",
    "Version": "2.0.2"
  },

But when I use it there are only 3 keys - this is one item in the list I get:

  {
    "License": "MIT",
    "Name": "pytest",
    "Version": "6.2.4"
  },

I checked also previous versions and it doesn't change.

Is the documentation not updated? Or that it is another problem?

Decoding Error

Same issue as:

  • Decoding Error when reading license file. #35
    but setting LANG in windows appears to do nothing - meaning you can't run pip-licenses!

Can we get a command line argument to override the default encoding please?

Scope to packages inside a conda environment

@raimon49 thanks for this awesome project!

I'd like to request a feature...could you make it possible to exclude globally-visible packages if you are inside a conda environment?

For my use case, I'd like to use pip-licenses in CI to catch dependencies with problematic licenses. However, right now I have to manually exclude a lot of stuff because things installed in my local site-packages are making it through.

Basically, I'm asking if pip-licenses could replicate whatever pip freeze --local does.

License files with carriage returns (\r) cause formatting issues

django-cors-headers uses \r carriage returns instead of \n newlines in its license. This causes pip-licenses's column formatting to break.

$ pip install pip-licenses django-cors-headers
$ pip-licenses --with-license-file --no-license-path
...
 django-cors-headers  3.2.0    MIT License  Copyright 2017 Otto Yiu and other contributors
http://ottoyiu.com

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
...

Perhaps \r should be converted to \n before being output to ensure that the column formatting is not disrupted?

Error when a package stores the license file in a subdirectory

My project started pulling version 2.5.2 for platformdirs. This platformdirs version started storing the LICENSE.txt file in a subdir (.\venv\Lib\site-packages\platformdirs-2.5.2.dist-info\license_files).

On line 171, (piplicense.py) pip-licenses tries to open the path (that it's a dir) as a "regular" file and it fails with a "PermissionError: [Errno 13] Permission denied" error:

Traceback (most recent call last): File "c:\pablo\python\python38\lib\runpy.py", line 194, in _run_module_as_main return _run_code(code, main_globals, None, File "c:\pablo\python\python38\lib\runpy.py", line 87, in _run_code exec(code, run_globals) File "C:\Proyectos\AttackIQ\agent\aiq_agent_go\attackgraph\venv\Scripts\pip-licenses.exe\__main__.py", line 7, in <module> File "C:\Proyectos\AttackIQ\agent\aiq_agent_go\attackgraph\venv\lib\site-packages\piplicenses.py", line 893, in main output_string = create_output_string(args) File "C:\Proyectos\AttackIQ\agent\aiq_agent_go\attackgraph\venv\lib\site-packages\piplicenses.py", line 551, in create_output_string table = create_licenses_table(args, output_fields) File "C:\Proyectos\AttackIQ\agent\aiq_agent_go\attackgraph\venv\lib\site-packages\piplicenses.py", line 296, in create_licenses_table for pkg in get_packages(args): File "C:\Proyectos\AttackIQ\agent\aiq_agent_go\attackgraph\venv\lib\site-packages\piplicenses.py", line 258, in get_packages pkg_info = get_pkg_info(pkg) File "C:\Proyectos\AttackIQ\agent\aiq_agent_go\attackgraph\venv\lib\site-packages\piplicenses.py", line 178, in get_pkg_info (license_file, license_text) = get_pkg_included_file( File "C:\Proyectos\AttackIQ\agent\aiq_agent_go\attackgraph\venv\lib\site-packages\piplicenses.py", line 171, in get_pkg_included_file with open(test_file, encoding='utf-8', PermissionError: [Errno 13] Permission denied: 'c:\\proyectos\\attackiq\\agent\\aiq_agent_go\\attackgraph\\venv\\lib\\site-packages\\platformdirs-2.5.2.dist-info\\license_files'

Failing tests test_format_csv, test_format_json, test_format_json_license_manager, and test_from_all

While packaging this for openSUSE, these four tests fail:

[    6s] =================================== FAILURES ===================================
[    6s] _______________________ TestGetLicenses.test_format_csv ________________________
[    6s]
[    6s] self = <test_piplicenses.TestGetLicenses testMethod=test_format_csv>
[    6s]
[    6s]     def test_format_csv(self):
[    6s]         format_csv_args = ['--format=csv', '--with-authors']
[    6s]         args = self.parser.parse_args(format_csv_args)
[    6s] >       output_string = create_output_string(args)
[    6s]
[    6s] test_piplicenses.py:455:
[    6s] _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
[    6s] piplicenses.py:533: in create_output_string
[    6s]     return table.get_string(fields=output_fields, sortby=sortby)
[    6s] _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
[    6s]
[    6s] self = <[TypeError('_format_rows() takes 2 positional arguments but 3 were given') raised in repr()] CSVPrettyTable object at 0x7f018d2e9f40>
[    6s] kwargs = {'fields': ['Name', 'Version', 'License', 'Author'], 'sortby': 'Name'}
[    6s] options = {'align': {'Author': 'c', 'License': 'c', 'Name': 'c', 'Version': 'c'}, 'attributes': {}, 'border': True, 'bottom_junction_char': '+', ...}
[    6s] rows = [['apipkg', '2.1.0', 'MIT License', 'holger krekel'], ['appdirs', '1.4.4', 'MIT License', 'Trent Mick'], ['attrs', '21...Software License', 'Jason R. Coombs'], ['iniconfig', '0.0.0', 'MIT License', 'Ronny Pfannschmidt, Holger Krekel'], ...]
[    6s]
[    6s]     def get_string(self, **kwargs):
[    6s]
[    6s]         def esc_quotes(val):
[    6s]             """
[    6s]             Meta-escaping double quotes
[    6s]             https://tools.ietf.org/html/rfc4180
[    6s]             """
[    6s]             try:
[    6s]                 return val.replace('"', '""')
[    6s]             except UnicodeDecodeError:  # pragma: no cover
[    6s]                 return val.decode('utf-8').replace('"', '""')
[    6s]             except UnicodeEncodeError:  # pragma: no cover
[    6s]                 return val.encode('unicode_escape').replace('"', '""')
[    6s]
[    6s]         options = self._get_options(kwargs)
[    6s]         rows = self._get_rows(options)
[    6s] >       formatted_rows = self._format_rows(rows, options)
[    6s] E       TypeError: _format_rows() takes 2 positional arguments but 3 were given
[    6s]
[    6s] piplicenses.py:384: TypeError
[    6s] _______________________ TestGetLicenses.test_format_json _______________________
[    6s]
[    6s] self = <test_piplicenses.TestGetLicenses testMethod=test_format_json>
[    6s]
[    6s]     def test_format_json(self):
[    6s]         format_json_args = ['--format=json', '--with-authors']
[    6s]         args = self.parser.parse_args(format_json_args)
[    6s] >       output_string = create_output_string(args)
[    6s]
[    6s] test_piplicenses.py:437:
[    6s] _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
[    6s] piplicenses.py:533: in create_output_string
[    6s]     return table.get_string(fields=output_fields, sortby=sortby)
[    6s] _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
[    6s]
[    6s] self = <[TypeError('_format_rows() takes 2 positional arguments but 3 were given') raised in repr()] JsonPrettyTable object at 0x7f018d2aab20>
[    6s] kwargs = {'fields': ['Name', 'Version', 'License', 'Author'], 'sortby': 'Name'}
[    6s] json = <module 'json' from '/usr/lib64/python3.9/json/__init__.py'>
[    6s] options = {'align': {'Author': 'c', 'License': 'c', 'Name': 'c', 'Version': 'c'}, 'attributes': {}, 'border': True, 'bottom_junction_char': '+', ...}
[    6s] rows = [['apipkg', '2.1.0', 'MIT License', 'holger krekel'], ['appdirs', '1.4.4', 'MIT License', 'Trent Mick'], ['attrs', '21...Software License', 'Jason R. Coombs'], ['iniconfig', '0.0.0', 'MIT License', 'Ronny Pfannschmidt, Holger Krekel'], ...]
[    6s]
[    6s]     def get_string(self, **kwargs):
[    6s]         # import included here in order to limit dependencies
[    6s]         # if not interested in JSON output,
[    6s]         # then the dependency is not required
[    6s]         import json
[    6s]
[    6s]         options = self._get_options(kwargs)
[    6s]         rows = self._get_rows(options)
[    6s] >       formatted_rows = self._format_rows(rows, options)
[    6s] E       TypeError: _format_rows() takes 2 positional arguments but 3 were given
[    6s]
[    6s] piplicenses.py:324: TypeError
[    6s] _______________ TestGetLicenses.test_format_json_license_manager _______________
[    6s]
[    6s] self = <test_piplicenses.TestGetLicenses testMethod=test_format_json_license_manager>
[    6s]
[    6s]     def test_format_json_license_manager(self):
[    6s]         format_json_args = ['--format=json-license-finder']
[    6s]         args = self.parser.parse_args(format_json_args)
[    6s] >       output_string = create_output_string(args)
[    6s]
[    6s] test_piplicenses.py:445:
[    6s] _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
[    6s] piplicenses.py:533: in create_output_string
[    6s]     return table.get_string(fields=output_fields, sortby=sortby)
[    6s] _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
[    6s]
[    6s] self = <[TypeError('_format_rows() takes 2 positional arguments but 3 were given') raised in repr()] JsonLicenseFinderTable object at 0x7f018d05aac0>
[    6s] kwargs = {'fields': ['Name', 'Version', 'License'], 'sortby': 'Name'}
[    6s] json = <module 'json' from '/usr/lib64/python3.9/json/__init__.py'>
[    6s] options = {'align': {'License': 'c', 'Name': 'c', 'Version': 'c'}, 'attributes': {}, 'border': True, 'bottom_junction_char': '+', ...}
[    6s] rows = [['apipkg', '2.1.0', 'MIT License'], ['appdirs', '1.4.4', 'MIT License'], ['attrs', '21.4.0', 'MIT License'], ['docuti...ion License'], ['importlib-metadata', '4.10.1', 'Apache Software License'], ['iniconfig', '0.0.0', 'MIT License'], ...]
[    6s]
[    6s]     def get_string(self, **kwargs):
[    6s]         # import included here in order to limit dependencies
[    6s]         # if not interested in JSON output,
[    6s]         # then the dependency is not required
[    6s]         import json
[    6s]
[    6s]         options = self._get_options(kwargs)
[    6s]         rows = self._get_rows(options)
[    6s] >       formatted_rows = self._format_rows(rows, options)
[    6s] E       TypeError: _format_rows() takes 2 positional arguments but 3 were given
[    6s]
[    6s] piplicenses.py:356: TypeError
[    6s] ________________________ TestGetLicenses.test_from_all _________________________
[    6s]
[    6s] self = <test_piplicenses.TestGetLicenses testMethod=test_from_all>
[    6s]
[    6s]     def test_from_all(self):
[    6s]         from_args = ['--from=all']
[    6s]         args = self.parser.parse_args(from_args)
[    6s]         output_fields = get_output_fields(args)
[    6s]         table = create_licenses_table(args, output_fields)
[    6s]
[    6s]         self.assertIn('License-Metadata', output_fields)
[    6s]         self.assertIn('License-Classifier', output_fields)
[    6s]
[    6s]         index_license_meta = output_fields.index('License-Metadata')
[    6s]         license_meta = []
[    6s]         for row in table._rows:
[    6s]             license_meta.append(row[index_license_meta])
[    6s]
[    6s]         index_license_classifier = output_fields.index('License-Classifier')
[    6s]         license_classifier = []
[    6s]         for row in table._rows:
[    6s]             license_classifier.append(row[index_license_classifier])
[    6s]
[    6s]         for license in ('BSD', 'MIT', 'Apache 2.0'):
[    6s] >           self.assertIn(license, license_meta)
[    6s] E           AssertionError: 'BSD' not found in ['UNKNOWN', 'MIT', 'MIT', 'MIT', 'MIT License', 'MIT license', 'MIT', 'BSD-2-Clause or Apache-2.0', 'MIT-LICENSE', 'MIT', 'MIT License', 'UNKNOWN', 'public domain, Python, 2-Clause BSD, GPL 3 (see COPYING.txt)', 'MIT', 'MIT', 'MIT']
[    6s]
[    6s] test_piplicenses.py:158: AssertionError
[    6s] =========================== short test summary info ============================
[    6s] FAILED test_piplicenses.py::TestGetLicenses::test_format_csv - TypeError: _fo...
[    6s] FAILED test_piplicenses.py::TestGetLicenses::test_format_json - TypeError: _f...
[    6s] FAILED test_piplicenses.py::TestGetLicenses::test_format_json_license_manager
[    6s] FAILED test_piplicenses.py::TestGetLicenses::test_from_all - AssertionError: ...
[    6s] ================== 4 failed, 40 passed, 2 deselected in 0.59s ==================

Complete build log shows all packages used and all steps taken to get here.

Show licenses of app/lib runtime dependencies only?

I'm using pipenv (Pipfile, Pipfile.lock) to manage Python package dependencies. In the venv during development there are included all dependencies including the --dev dependencies. Would be great if one could easily check the licenses of the packages used during app/library runtime only.

Add ability to suppress license file path

Currently if the user adds --with-license-file the full absolute path of the license file is included in the output. If the user wants to distribute this license information file with their software, paths that include their user name on the system where pip-licenses was run are leaked into the output.

It would be very helpful if the path to the license file path could be suppressed from the output to avoid this.

Calling from within Python

The documentation currently does not explain how to call pip-licenses from within Python, only how to call it from a command line.

.egg directories not considered in get_pkg_included_file

Hi All,

I noticed that some license files are not correctly identified. This seems to happen because only .dist-info directories are considered and .egg are not tried.

This specifically assumes data will reside in .dist-info which is not always true.

pkg_dirname = "{}-{}.dist-info".format(
            pkg.project_name.replace("-", "_"), pkg.version)

In my .venv i have numpy-1.20.1-py3.9-win-amd64.egg which is not detected and skipped. Similarly others

regards
Philip

Adopt option for SPDX-License-Identifiers output

Great project! I handle open source compliance for a large multinational corporation and just found this while coding in my spare time. I'll probably start pointing my developers to this project for python license identification from their dependencies.

It'd be nice if there was an option to use SPDX-License-Identifiers for the output for licenses. This is the identifier based on this list and is intended to normalize identification of licenses.

These identifiers are actually being used in multiple projects now to identify the license (e.g., Linux kernel).

This may complicate how this tool works since it looks like you're just passing the meta data through, but I think more python projects may adopt the SPDX-License-Identifier format.

Summary table and raw results are inconsistent

While working with pip-licenses today, I noticed an inconsistency between raw and summary output.

Steps to reproduce:

echo "awscli" > docker/requirements.txt
docker build -t myapp-licenses -f .

Output of docker run --rm myapp-licenses:

Name             Version   License
 PyYAML           3.13      MIT License
 awscli           1.16.140  Apache Software License
 botocore         1.12.130  Apache Software License
 colorama         0.3.9     BSD License
 docutils         0.14      Public Domain, Python Software Foundation License, BSD License, GNU General Public License (GPL)
 jmespath         0.9.4     MIT License
 pyasn1           0.4.5     BSD License
 python-dateutil  2.8.0     BSD License, Apache Software License
 rsa              3.4.2     Apache Software License
 s3transfer       0.2.0     Apache Software License
 six              1.12.0    MIT License
 urllib3          1.24.1    MIT License

Output of docker run --rm myapp-licenses --summary

 Count  License
 1      ASL 2
 3      Apache License 2.0
 2      BSD
 1      Dual License
 4      MIT
 1      public domain, Python, 2-Clause BSD, GPL 3 (see COPYING.txt)

Looking at the non-summary results, I'd expected the results from --summary to be

 Count  License
 4     Apache Software License
 1      BSD License, Apache Software License
 2      BSD
 4      MIT
 1      public domain, Python, 2-Clause BSD, GPL 3 (see COPYING.txt)

I cannot figure out from the source code of piplicenses.py where things like converting "BSD License, Apache Software License" to "Dual License" are happening, but I propose that this inconsistency is problematic. If you use pip-licenses to scan for dependency licenses that your organization wants to avoid (as many orgs do with GPL code), not all "Dual License" projects are equally worthy of attention.

pip-licenses does not support packages with license files named "COPYING"

It is somewhat common (as I've found trying to ensure that all of my packages have license files in a pip-licenses report) for packages to use COPYING as their license file name.

See this reference in a setuptools issue: pypa/setuptools#1636 (comment)

In the same way that README files are automatically grafted into the source distribution, it would be nice if LICENSE / COPYING files were also included by default

Currently pip-licenses cannot find these license files due to this code:

license_file_base = os.path.join(pkg.location, pkg_dirname, 'LICENSE*')

Could this be expanded to include COPYING* as well?

Example:

Support display multiple license

Only one license of software distributed under dual license is displayed.

$ pip-licenses
 Name            Version  License
 helga           1.7.6    MIT/GPLv3


$ pip-licenses --from-classifier
 helga           1.7.6    MIT License

Add --format=plain-vertical format

We used pip-licenses --with-license-file --format=plain to try to generate something like Angular CLI's https://angular.io/3rdpartylicenses.txt format.

The issue with pip-licenses's plain format is that it still tries to line things up horizontally, which can make it difficult to read in a browser. Also we noticed that some license files with special characters broke pip-licenses attempt to properly align the licenses.

Could a new format by added called --format=plain-vertical that puts the information in a vertical format similar to Angular CLI's generated output? This would be very useful.

--output-file option

pip-licenses is really great, thanks!

Would it be possible to add a command line argument that wrote the resulting output to a file instead of being printed to the screen? Terminal redirection is not always available (such as in a tox environment).

Example:

$ pip-licenses --format=rst --output-file=my_output.rst

Decoding Error when reading license file.

I ran pip-licenses inside a docker container of mine, and got the following exception:

Traceback (most recent call last):
  File "/usr/local/bin/pip-licenses", line 11, in <module>
    sys.exit(main())
  File "/usr/local/lib/python3.5/dist-packages/piplicenses.py", line 598, in main
    output_string = create_output_string(args)
  File "/usr/local/lib/python3.5/dist-packages/piplicenses.py", line 386, in create_output_string
    table = create_licenses_table(args, output_fields)
  File "/usr/local/lib/python3.5/dist-packages/piplicenses.py", line 200, in create_licenses_table
    for pkg in get_packages(args):
  File "/usr/local/lib/python3.5/dist-packages/piplicenses.py", line 185, in get_packages
    pkg_info = get_pkg_info(pkg)
  File "/usr/local/lib/python3.5/dist-packages/piplicenses.py", line 141, in get_pkg_info
    (license_file, license_text) = get_pkg_license_file(pkg)
  File "/usr/local/lib/python3.5/dist-packages/piplicenses.py", line 128, in get_pkg_license_file
    file_lines = license_file_handle.readlines()
  File "/usr/lib/python3.5/encodings/ascii.py", line 26, in decode
    return codecs.ascii_decode(input, self.errors)[0]
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc2 in position 10: ordinal not in range(128)

The offending license file was:
/usr/local/lib/python3.5/dist-packages/Werkzeug-0.14.1.dist-info/LICENSE.txt

I was able to resolve the issue by editing line 127 in piplicenses.py:

with open(test_file, encoding='utf-8', errors='ignore') as license_file_handle:

Not sure always assuming utf-8 is a good idea, though.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.