Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-pip-licenses for
openSUSE:Factory checked in at 2023-04-12 15:48:22
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-pip-licenses (Old)
and /work/SRC/openSUSE:Factory/.python-pip-licenses.new.19717 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-pip-licenses"
Wed Apr 12 15:48:22 2023 rev:10 rq:1078727 version:4.2.0
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-pip-licenses/python-pip-licenses.changes
2023-02-06 14:15:35.924625021 +0100
+++
/work/SRC/openSUSE:Factory/.python-pip-licenses.new.19717/python-pip-licenses.changes
2023-04-12 15:48:23.034752858 +0200
@@ -1,0 +2,15 @@
+Wed Apr 12 13:26:16 UTC 2023 - Matej Cepl <[email protected]>
+
+- Update to 4.2.0:
+ - Implement new option --with-maintainers
+ - Implement new option --python
+ - Allow version spec in --ignore-packages parameters
+ - When the Author field is UNKNOWN, the output is automatically
+ completed from Author-email
+ - When the home-page field is UNKNOWN, the output is
+ automatically completed from Project-URL
+- Update to 4.1.0:
+ - Support case-insensitive license name matching around
+ --fail-on and --allow-only parameters
+
+-------------------------------------------------------------------
Old:
----
pip-licenses-4.0.3.tar.gz
New:
----
pip-licenses-4.2.0.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-pip-licenses.spec ++++++
--- /var/tmp/diff_new_pack.TDq5gS/_old 2023-04-12 15:48:23.522755719 +0200
+++ /var/tmp/diff_new_pack.TDq5gS/_new 2023-04-12 15:48:23.526755742 +0200
@@ -18,7 +18,7 @@
%define skip_python2 1
Name: python-pip-licenses
-Version: 4.0.3
+Version: 4.2.0
Release: 0
Summary: Python packages license list
License: MIT
@@ -65,7 +65,8 @@
%check
export LANG=en_US.UTF-8
# gh#raimon49/pip-licenses#120 for test_from_all
-%pytest -k 'not test_from_all'
+# gh#raimon49/pip-licenses#156 for test_different_python
+%pytest -k 'not (test_from_all or test_different_python)'
%python_expand PYTHONPATH=%{buildroot}%{$python_sitelib}
%{buildroot}%{_bindir}/pip-licenses-%{$python_bin_suffix} -s
%post
++++++ pip-licenses-4.0.3.tar.gz -> pip-licenses-4.2.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pip-licenses-4.0.3/CHANGELOG.md
new/pip-licenses-4.2.0/CHANGELOG.md
--- old/pip-licenses-4.0.3/CHANGELOG.md 2022-12-08 12:58:08.000000000 +0100
+++ new/pip-licenses-4.2.0/CHANGELOG.md 2023-04-12 11:58:29.000000000 +0200
@@ -1,5 +1,17 @@
## CHANGELOG
+### 4.2.0
+
+* Implement new option `--with-maintainers`
+* Implement new option `--python`
+* Allow version spec in `--ignore-packages` parameters
+* When the `Author` field is `UNKNOWN`, the output is automatically completed
from `Author-email`
+* When the `home-page` field is `UNKNOWN`, the output is automatically
completed from `Project-URL`
+
+### 4.1.0
+
+* Support case-insensitive license name matching around `--fail-on` and
`--allow-only` parameters
+
### 4.0.3
* Escape unicode output (to e.g. `{`) in the html output
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pip-licenses-4.0.3/PKG-INFO
new/pip-licenses-4.2.0/PKG-INFO
--- old/pip-licenses-4.0.3/PKG-INFO 2022-12-08 13:02:14.390000000 +0100
+++ new/pip-licenses-4.2.0/PKG-INFO 2023-04-12 13:24:35.490549600 +0200
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: pip-licenses
-Version: 4.0.3
+Version: 4.2.0
Summary: Dump the software license list of Python packages installed with pip.
Home-page: https://github.com/raimon49/pip-licenses
Author: raimon
@@ -44,6 +44,7 @@
- `Common options <#common-options>`__
+ - `Option: python <#option-python>`__
- `Option: from <#option-from>`__
- `Option: order <#option-order>`__
- `Option: format <#option-format>`__
@@ -66,6 +67,7 @@
- `Option: with-system <#option-with-system>`__
- `Option: with-authors <#option-with-authors>`__
+ - `Option: with-maintainers <#option-with-maintainers>`__
- `Option: with-urls <#option-with-urls>`__
- `Option: with-description <#option-with-description>`__
- `Option: with-license-file <#option-with-license-file>`__
@@ -150,6 +152,28 @@
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.
+
+.. code:: bash
+
+ (venv) $ pip-licenses --with-system | grep pip
+ pip 22.3.1 MIT License
+ pip-licenses 4.1.0 MIT License
+
+.. code:: bash
+
+ (venv) $ pip-licenses --python=</path/to/other/env>/bin/python
--with-system | grep pip
+ pip 23.0.1 MIT License
+
Option: from
^^^^^^^^^^^^
@@ -458,6 +482,17 @@
setuptools 38.5.0 UNKNOWN
wcwidth 0.2.5 MIT License
+Packages can also be specified with a version, only ignoring that
+specific version.
+
+.. code:: bash
+
+ (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
^^^^^^^^^^^^^^^^
@@ -519,6 +554,16 @@
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 <https://github.com/raimon49/pip-licenses/issues/144>`__
+
Option: with-urls
^^^^^^^^^^^^^^^^^
@@ -583,7 +628,7 @@
^^^^^^^^^^^^^^^
Fail (exit with code 1) on the first occurrence of the licenses of the
-semicolon-separated list
+semicolon-separated list. The license name matching is case-insensitive.
If ``--from=all``, the option will apply to the metadata license field.
@@ -597,19 +642,23 @@
::
# keyring library has 2 licenses
- $ pip-licenses | grep keyring
- keyring 21.4.0 Python Software Foundation License, MIT
License
+ $ 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 --fail-on="Python Software Foundation License;"
+ $ 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
+semicolon-separated list. The license name matching is case-insensitive.
If ``--from=all``, the option will apply to the metadata license field.
@@ -624,11 +673,12 @@
# keyring library has 2 licenses
$ pip-licenses --package keyring
- Name Version License
+ Name Version License
keyring 23.0.1 MIT License; Python Software Foundation License
- # One or both licenses must be specified (order does not matter). Following
checks will pass:
+ # 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"
@@ -765,6 +815,27 @@
CHANGELOG
---------
+.. _420:
+
+4.2.0
+~~~~~
+
+- Implement new option ``--with-maintainers``
+- Implement new option ``--python``
+- Allow version spec in ``--ignore-packages`` parameters
+- When the ``Author`` field is ``UNKNOWN``, the output is automatically
+ completed from ``Author-email``
+- When the ``home-page`` field is ``UNKNOWN``, the output is
+ automatically completed from ``Project-URL``
+
+.. _410:
+
+4.1.0
+~~~~~
+
+- Support case-insensitive license name matching around ``--fail-on``
+ and ``--allow-only`` parameters
+
.. _403:
4.0.3
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pip-licenses-4.0.3/README.md
new/pip-licenses-4.2.0/README.md
--- old/pip-licenses-4.0.3/README.md 2022-11-06 04:18:47.000000000 +0100
+++ new/pip-licenses-4.2.0/README.md 2023-04-12 11:58:26.000000000 +0200
@@ -11,6 +11,7 @@
* [Usage](#usage)
* [Command\-Line Options](#command-line-options)
* [Common options](#common-options)
+ * [Option: python](#option-python)
* [Option: from](#option-from)
* [Option: order](#option-order)
* [Option: format](#option-format)
@@ -29,6 +30,7 @@
* [Format options](#format-options)
* [Option: with\-system](#option-with-system)
* [Option: with\-authors](#option-with-authors)
+ * [Option: with\-maintainers](#option-with-maintainers)
* [Option: with\-urls](#option-with-urls)
* [Option: with\-description](#option-with-description)
* [Option: with\-license\-file](#option-with-license-file)
@@ -97,6 +99,21 @@
### 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.
+
+```bash
+(venv) $ pip-licenses --with-system | grep pip
+ pip 22.3.1 MIT License
+ pip-licenses 4.1.0 MIT License
+```
+
+```bash
+(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](https://pypi.org/classifiers/) or package Metadata. Some Python
packages declare their license only in Trove Classifiers.
@@ -351,6 +368,16 @@
wcwidth 0.2.5 MIT License
```
+Packages can also be specified with a version, only ignoring that specific
version.
+
+```bash
+(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.
@@ -403,6 +430,12 @@
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](https://github.com/raimon49/pip-licenses/issues/144)
+
#### Option: with-urls
For packages without Metadata, the license is output as `UNKNOWN`. To get more
package information, use the `--with-urls` option.
@@ -446,7 +479,8 @@
#### Option: fail\-on
-Fail (exit with code 1) on the first occurrence of the licenses of the
semicolon-separated list
+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.
@@ -456,18 +490,23 @@
**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 | grep keyring
- keyring 21.4.0 Python Software Foundation License, MIT License
+$ 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 --fail-on="Python Software Foundation License;"
+$ 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
+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.
@@ -478,11 +517,12 @@
```
# keyring library has 2 licenses
$ pip-licenses --package keyring
- Name Version License
+ Name Version License
keyring 23.0.1 MIT License; Python Software Foundation License
-# One or both licenses must be specified (order does not matter). Following
checks will pass:
+# 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"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pip-licenses-4.0.3/pip_licenses.egg-info/PKG-INFO
new/pip-licenses-4.2.0/pip_licenses.egg-info/PKG-INFO
--- old/pip-licenses-4.0.3/pip_licenses.egg-info/PKG-INFO 2022-12-08
13:02:14.000000000 +0100
+++ new/pip-licenses-4.2.0/pip_licenses.egg-info/PKG-INFO 2023-04-12
13:24:35.000000000 +0200
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: pip-licenses
-Version: 4.0.3
+Version: 4.2.0
Summary: Dump the software license list of Python packages installed with pip.
Home-page: https://github.com/raimon49/pip-licenses
Author: raimon
@@ -44,6 +44,7 @@
- `Common options <#common-options>`__
+ - `Option: python <#option-python>`__
- `Option: from <#option-from>`__
- `Option: order <#option-order>`__
- `Option: format <#option-format>`__
@@ -66,6 +67,7 @@
- `Option: with-system <#option-with-system>`__
- `Option: with-authors <#option-with-authors>`__
+ - `Option: with-maintainers <#option-with-maintainers>`__
- `Option: with-urls <#option-with-urls>`__
- `Option: with-description <#option-with-description>`__
- `Option: with-license-file <#option-with-license-file>`__
@@ -150,6 +152,28 @@
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.
+
+.. code:: bash
+
+ (venv) $ pip-licenses --with-system | grep pip
+ pip 22.3.1 MIT License
+ pip-licenses 4.1.0 MIT License
+
+.. code:: bash
+
+ (venv) $ pip-licenses --python=</path/to/other/env>/bin/python
--with-system | grep pip
+ pip 23.0.1 MIT License
+
Option: from
^^^^^^^^^^^^
@@ -458,6 +482,17 @@
setuptools 38.5.0 UNKNOWN
wcwidth 0.2.5 MIT License
+Packages can also be specified with a version, only ignoring that
+specific version.
+
+.. code:: bash
+
+ (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
^^^^^^^^^^^^^^^^
@@ -519,6 +554,16 @@
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 <https://github.com/raimon49/pip-licenses/issues/144>`__
+
Option: with-urls
^^^^^^^^^^^^^^^^^
@@ -583,7 +628,7 @@
^^^^^^^^^^^^^^^
Fail (exit with code 1) on the first occurrence of the licenses of the
-semicolon-separated list
+semicolon-separated list. The license name matching is case-insensitive.
If ``--from=all``, the option will apply to the metadata license field.
@@ -597,19 +642,23 @@
::
# keyring library has 2 licenses
- $ pip-licenses | grep keyring
- keyring 21.4.0 Python Software Foundation License, MIT
License
+ $ 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 --fail-on="Python Software Foundation License;"
+ $ 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
+semicolon-separated list. The license name matching is case-insensitive.
If ``--from=all``, the option will apply to the metadata license field.
@@ -624,11 +673,12 @@
# keyring library has 2 licenses
$ pip-licenses --package keyring
- Name Version License
+ Name Version License
keyring 23.0.1 MIT License; Python Software Foundation License
- # One or both licenses must be specified (order does not matter). Following
checks will pass:
+ # 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"
@@ -765,6 +815,27 @@
CHANGELOG
---------
+.. _420:
+
+4.2.0
+~~~~~
+
+- Implement new option ``--with-maintainers``
+- Implement new option ``--python``
+- Allow version spec in ``--ignore-packages`` parameters
+- When the ``Author`` field is ``UNKNOWN``, the output is automatically
+ completed from ``Author-email``
+- When the ``home-page`` field is ``UNKNOWN``, the output is
+ automatically completed from ``Project-URL``
+
+.. _410:
+
+4.1.0
+~~~~~
+
+- Support case-insensitive license name matching around ``--fail-on``
+ and ``--allow-only`` parameters
+
.. _403:
4.0.3
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pip-licenses-4.0.3/piplicenses.py
new/pip-licenses-4.2.0/piplicenses.py
--- old/pip-licenses-4.0.3/piplicenses.py 2022-12-08 12:57:32.000000000
+0100
+++ new/pip-licenses-4.2.0/piplicenses.py 2023-04-12 13:23:06.000000000
+0200
@@ -30,7 +30,9 @@
import argparse
import codecs
+import os
import re
+import subprocess
import sys
from collections import Counter
from enum import Enum, auto
@@ -47,13 +49,14 @@
from prettytable import PrettyTable
if TYPE_CHECKING:
- from typing import Iterator, Optional, Sequence
+ from email.message import Message
+ from typing import Callable, Dict, Iterator, Optional, Sequence
open = open # allow monkey patching
__pkgname__ = "pip-licenses"
-__version__ = "4.0.3"
+__version__ = "4.2.0"
__author__ = "raimon"
__license__ = "MIT"
__summary__ = (
@@ -71,6 +74,7 @@
"NoticeFile",
"NoticeText",
"Author",
+ "Maintainer",
"Description",
"URL",
)
@@ -94,12 +98,50 @@
)
-METADATA_KEYS = (
- "home-page",
- "author",
- "license",
- "summary",
-)
+def extract_homepage(metadata: Message) -> Optional[str]:
+ """Extracts the homepage attribute from the package metadata.
+
+ Not all python packages have defined a home-page attribute.
+ As a fallback, the `Project-URL` metadata can be used.
+ The python core metadata supports multiple (free text) values for
+ the `Project-URL` field that are comma separated.
+
+ Args:
+ metadata: The package metadata to extract the homepage from.
+
+ Returns:
+ The home page if applicable, None otherwise.
+ """
+ homepage = metadata.get("home-page", None)
+ if homepage is not None:
+ return homepage
+
+ candidates: Dict[str, str] = {}
+
+ for entry in metadata.get_all("Project-URL", []):
+ key, value = entry.split(",", 1)
+ candidates[key.strip()] = value.strip()
+
+ for priority_key in ["Homepage", "Source", "Changelog", "Bug Tracker"]:
+ if priority_key in candidates:
+ return candidates[priority_key]
+
+ return None
+
+
+METADATA_KEYS: Dict[str, List[Callable[[Message], Optional[str]]]] = {
+ "home-page": [extract_homepage],
+ "author": [
+ lambda metadata: metadata.get("author"),
+ lambda metadata: metadata.get("author-email"),
+ ],
+ "maintainer": [
+ lambda metadata: metadata.get("maintainer"),
+ lambda metadata: metadata.get("maintainer-email"),
+ ],
+ "license": [lambda metadata: metadata.get("license")],
+ "summary": [lambda metadata: metadata.get("summary")],
+}
# Mapping of FIELD_NAMES to METADATA_KEYS where they differ by more than case
FIELDS_TO_METADATA_KEYS = {
@@ -167,8 +209,15 @@
"noticetext": notice_text,
}
metadata = pkg.metadata
- for key in METADATA_KEYS:
- pkg_info[key] = metadata.get(key, LICENSE_UNKNOWN) # type:
ignore[attr-defined] # noqa: E501
+ for field_name, field_selector_fns in METADATA_KEYS.items():
+ value = None
+ for field_selector_fn in field_selector_fns:
+ # Type hint of `Distribution.metadata` states `PackageMetadata`
+ # but it's actually of type `email.Message`
+ value = field_selector_fn(metadata) # type: ignore
+ if value:
+ break
+ pkg_info[field_name] = value or LICENSE_UNKNOWN
classifiers: list[str] = metadata.get_all("classifier", [])
pkg_info["license_classifier"] = find_license_from_classifier(
@@ -190,7 +239,21 @@
return pkg_info
- pkgs = importlib_metadata.distributions()
+ def get_python_sys_path(executable: str) -> list[str]:
+ script = "import sys; print(' '.join(filter(bool, sys.path)))"
+ output = subprocess.run(
+ [executable, "-c", script],
+ capture_output=True,
+ env={**os.environ, "PYTHONPATH": "", "VIRTUAL_ENV": ""},
+ )
+ return output.stdout.decode().strip().split()
+
+ if args.python == sys.executable:
+ search_paths = sys.path
+ else:
+ search_paths = get_python_sys_path(args.python)
+
+ pkgs = importlib_metadata.distributions(path=search_paths)
ignore_pkgs_as_lower = [pkg.lower() for pkg in args.ignore_packages]
pkgs_as_lower = [pkg.lower() for pkg in args.packages]
@@ -204,8 +267,12 @@
for pkg in pkgs:
pkg_name = pkg.metadata["name"]
+ pkg_name_and_version = pkg_name + ":" + pkg.metadata["version"]
- if pkg_name.lower() in ignore_pkgs_as_lower:
+ if (
+ pkg_name.lower() in ignore_pkgs_as_lower
+ or pkg_name_and_version.lower() in ignore_pkgs_as_lower
+ ):
continue
if pkgs_as_lower and pkg_name.lower() not in pkgs_as_lower:
@@ -223,7 +290,9 @@
)
if fail_on_licenses:
- failed_licenses = license_names.intersection(fail_on_licenses)
+ failed_licenses = case_insensitive_set_intersect(
+ license_names, fail_on_licenses
+ )
if failed_licenses:
sys.stderr.write(
"fail-on license {} was found for package "
@@ -236,7 +305,9 @@
sys.exit(1)
if allow_only_licenses:
- uncommon_licenses = license_names.difference(allow_only_licenses)
+ uncommon_licenses = case_insensitive_set_diff(
+ license_names, allow_only_licenses
+ )
if len(uncommon_licenses) == len(license_names):
sys.stderr.write(
"license {} not in allow-only licenses was found"
@@ -302,12 +373,32 @@
return table
+def case_insensitive_set_intersect(set_a, set_b):
+ """Same as set.intersection() but case-insensitive"""
+ common_items = set()
+ set_b_lower = {item.lower() for item in set_b}
+ for elem in set_a:
+ if elem.lower() in set_b_lower:
+ common_items.add(elem)
+ return common_items
+
+
+def case_insensitive_set_diff(set_a, set_b):
+ """Same as set.difference() but case-insensitive"""
+ uncommon_items = set()
+ set_b_lower = {item.lower() for item in set_b}
+ for elem in set_a:
+ if not elem.lower() in set_b_lower:
+ uncommon_items.add(elem)
+ return uncommon_items
+
+
class JsonPrettyTable(PrettyTable):
"""PrettyTable-like class exporting to JSON"""
def _format_row(self, row: Iterable[str]) -> dict[str, str | list[str]]:
resrow: dict[str, str | List[str]] = {}
- for (field, value) in zip(self._field_names, row):
+ for field, value in zip(self._field_names, row):
resrow[field] = value
return resrow
@@ -332,7 +423,7 @@
class JsonLicenseFinderTable(JsonPrettyTable):
def _format_row(self, row: Iterable[str]) -> dict[str, str | list[str]]:
resrow: dict[str, str | List[str]] = {}
- for (field, value) in zip(self._field_names, row):
+ for field, value in zip(self._field_names, row):
if field == "Name":
resrow["name"] = value
@@ -494,6 +585,9 @@
if args.with_authors:
output_fields.append("Author")
+ if args.with_maintainers:
+ output_fields.append("Maintainer")
+
if args.with_urls:
output_fields.append("URL")
@@ -523,6 +617,8 @@
return "Name"
elif args.order == OrderArg.AUTHOR and args.with_authors:
return "Author"
+ elif args.order == OrderArg.MAINTAINER and args.with_maintainers:
+ return "Maintainer"
elif args.order == OrderArg.URL and args.with_urls:
return "URL"
@@ -691,6 +787,7 @@
LICENSE = L = auto()
NAME = N = auto()
AUTHOR = A = auto()
+ MAINTAINER = M = auto()
URL = U = auto()
@@ -754,6 +851,17 @@
)
common_options.add_argument(
+ "--python",
+ type=str,
+ default=sys.executable,
+ metavar="PYTHON_EXEC",
+ help="R| path to python executable to search distributions from\n"
+ "Package will be searched in the selected python's sys.path\n"
+ "By default, will search packages for current env executable\n"
+ "(default: sys.executable)",
+ )
+
+ common_options.add_argument(
"--from",
dest="from_",
action=SelectAction,
@@ -839,6 +947,12 @@
help="dump with package authors",
)
format_options.add_argument(
+ "--with-maintainers",
+ action="store_true",
+ default=False,
+ help="dump with package maintainers",
+ )
+ format_options.add_argument(
"-u",
"--with-urls",
action="store_true",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pip-licenses-4.0.3/test_piplicenses.py
new/pip-licenses-4.2.0/test_piplicenses.py
--- old/pip-licenses-4.0.3/test_piplicenses.py 2022-12-08 12:56:51.000000000
+0100
+++ new/pip-licenses-4.2.0/test_piplicenses.py 2023-04-12 11:58:29.000000000
+0200
@@ -4,12 +4,16 @@
import copy
import email
+import json
import re
import sys
import unittest
+import venv
from enum import Enum, auto
from importlib.metadata import Distribution
+from types import SimpleNamespace
from typing import TYPE_CHECKING, Any, List
+from unittest.mock import MagicMock
import docutils.frontend
import docutils.parsers.rst
@@ -29,14 +33,18 @@
CompatibleArgumentParser,
FromArg,
__pkgname__,
+ case_insensitive_set_diff,
+ case_insensitive_set_intersect,
create_licenses_table,
create_output_string,
create_parser,
create_warn_string,
enum_key_to_value,
+ extract_homepage,
factory_styled_table_with_args,
find_license_from_classifier,
get_output_fields,
+ get_packages,
get_sortby,
output_colored,
save_if_needs,
@@ -218,14 +226,14 @@
for row in table.rows:
license_classifier.append(row[index_license_classifier])
- for license in ("BSD", "MIT", "Apache 2.0"):
- self.assertIn(license, license_meta)
- for license in (
+ for license_name in ("BSD", "MIT", "Apache 2.0"):
+ self.assertIn(license_name, license_meta)
+ for license_name in (
"BSD License",
"MIT License",
"Apache Software License",
):
- self.assertIn(license, license_classifier)
+ self.assertIn(license_name, license_classifier)
def test_find_license_from_classifier(self) -> None:
classifiers = ["License :: OSI Approved :: MIT License"]
@@ -303,6 +311,17 @@
output_string = create_output_string(args)
self.assertIn("Author", output_string)
+ def test_with_maintainers(self) -> None:
+ with_maintainers_args = ["--with-maintainers"]
+ args = self.parser.parse_args(with_maintainers_args)
+
+ output_fields = get_output_fields(args)
+ self.assertNotEqual(output_fields, list(DEFAULT_OUTPUT_FIELDS))
+ self.assertIn("Maintainer", output_fields)
+
+ output_string = create_output_string(args)
+ self.assertIn("Maintainer", output_string)
+
def test_with_urls(self) -> None:
with_urls_args = ["--with-urls"]
args = self.parser.parse_args(with_urls_args)
@@ -388,17 +407,32 @@
self.assertIn("best paired with --format=json", warn_string)
def test_ignore_packages(self) -> None:
- if "PTable" in SYSTEM_PACKAGES:
- ignore_pkg_name = "PTable"
- else:
- ignore_pkg_name = "prettytable"
- ignore_packages_args = ["--ignore-package=" + ignore_pkg_name]
+ ignore_pkg_name = "prettytable"
+ ignore_packages_args = [
+ "--ignore-package=" + ignore_pkg_name,
+ "--with-system",
+ ]
args = self.parser.parse_args(ignore_packages_args)
table = create_licenses_table(args)
pkg_name_columns = self._create_pkg_name_columns(table)
self.assertNotIn(ignore_pkg_name, pkg_name_columns)
+ def test_ignore_packages_and_version(self) -> None:
+ # Fictitious version that does not exist
+ ignore_pkg_name = "prettytable"
+ ignore_pkg_spec = ignore_pkg_name + ":1.99.99"
+ ignore_packages_args = [
+ "--ignore-package=" + ignore_pkg_spec,
+ "--with-system",
+ ]
+ args = self.parser.parse_args(ignore_packages_args)
+ table = create_licenses_table(args)
+
+ pkg_name_columns = self._create_pkg_name_columns(table)
+ # It is expected that prettytable will include
+ self.assertIn(ignore_pkg_name, pkg_name_columns)
+
def test_with_packages(self) -> None:
pkg_name = "py"
only_packages_args = ["--packages=" + pkg_name]
@@ -409,10 +443,7 @@
self.assertListEqual([pkg_name], pkg_name_columns)
def test_with_packages_with_system(self) -> None:
- if "PTable" in SYSTEM_PACKAGES:
- pkg_name = "PTable"
- else:
- pkg_name = "prettytable"
+ pkg_name = "prettytable"
only_packages_args = ["--packages=" + pkg_name, "--with-system"]
args = self.parser.parse_args(only_packages_args)
table = create_licenses_table(args)
@@ -441,6 +472,13 @@
sortby = get_sortby(args)
self.assertEqual("Author", sortby)
+ def test_order_maintainer(self) -> None:
+ order_maintainer_args = ["--order=maintainer", "--with-maintainers"]
+ args = self.parser.parse_args(order_maintainer_args)
+
+ sortby = get_sortby(args)
+ self.assertEqual("Maintainer", sortby)
+
def test_order_url(self) -> None:
order_url_args = ["--order=url", "--with-urls"]
args = self.parser.parse_args(order_url_args)
@@ -669,6 +707,34 @@
importlib_metadata_distributions_orig
)
+ def test_case_insensitive_set_diff(self) -> None:
+ set_a = {"MIT License"}
+ set_b = {"Mit License", "BSD License"}
+ set_c = {"mit license"}
+ a_diff_b = case_insensitive_set_diff(set_a, set_b)
+ a_diff_c = case_insensitive_set_diff(set_a, set_c)
+ b_diff_c = case_insensitive_set_diff(set_b, set_c)
+ a_diff_empty = case_insensitive_set_diff(set_a, set())
+
+ self.assertTrue(len(a_diff_b) == 0)
+ self.assertTrue(len(a_diff_c) == 0)
+ self.assertIn("BSD License", b_diff_c)
+ self.assertIn("MIT License", a_diff_empty)
+
+ def test_case_insensitive_set_intersect(self) -> None:
+ set_a = {"Revised BSD"}
+ set_b = {"Apache License", "revised BSD"}
+ set_c = {"revised bsd"}
+ a_intersect_b = case_insensitive_set_intersect(set_a, set_b)
+ a_intersect_c = case_insensitive_set_intersect(set_a, set_c)
+ b_intersect_c = case_insensitive_set_intersect(set_b, set_c)
+ a_intersect_empty = case_insensitive_set_intersect(set_a, set())
+
+ self.assertTrue(set_a == a_intersect_b)
+ self.assertTrue(set_a == a_intersect_c)
+ self.assertTrue({"revised BSD"} == b_intersect_c)
+ self.assertTrue(len(a_intersect_empty) == 0)
+
class MockStdStream(object):
def __init__(self) -> None:
@@ -726,7 +792,7 @@
def test_allow_only(monkeypatch) -> None:
licenses = (
- "BSD License",
+ "Bsd License",
"Apache Software License",
"Mozilla Public License 2.0 (MPL 2.0)",
"Python Software Foundation License",
@@ -750,8 +816,28 @@
)
+def test_different_python() -> None:
+ import tempfile
+
+ class TempEnvBuild(venv.EnvBuilder):
+ def post_setup(self, context: SimpleNamespace) -> None:
+ self.context = context
+
+ with tempfile.TemporaryDirectory() as target_dir_path:
+ venv_builder = TempEnvBuild(with_pip=True)
+ venv_builder.create(str(target_dir_path))
+ python_exec = venv_builder.context.env_exe
+ python_arg = f"--python={python_exec}"
+ args = create_parser().parse_args([python_arg, "-s", "-f=json"])
+ pkgs = get_packages(args)
+ package_names = sorted(p["name"] for p in pkgs)
+ print(package_names)
+
+ assert package_names == ["pip", "setuptools"]
+
+
def test_fail_on(monkeypatch) -> None:
- licenses = ("MIT License",)
+ licenses = ("MIT license",)
allow_only_args = ["--fail-on={}".format(";".join(licenses))]
mocked_stdout = MockStdStream()
mocked_stderr = MockStdStream()
@@ -821,3 +907,56 @@
capture = capsys.readouterr().err
for arg in ("invalid code", "--filter-code-page"):
assert arg in capture
+
+
+def test_extract_homepage_home_page_set() -> None:
+ metadata = MagicMock()
+ metadata.get.return_value = "Foobar"
+
+ assert "Foobar" == extract_homepage(metadata=metadata) # type: ignore
+
+ metadata.get.assert_called_once_with("home-page", None)
+
+
+def test_extract_homepage_project_url_fallback() -> None:
+ metadata = MagicMock()
+ metadata.get.return_value = None
+
+ # `Homepage` is prioritized higher than `Source`
+ metadata.get_all.return_value = [
+ "Source, source",
+ "Homepage, homepage",
+ ]
+
+ assert "homepage" == extract_homepage(metadata=metadata) # type: ignore
+
+ metadata.get_all.assert_called_once_with("Project-URL", [])
+
+
+def test_extract_homepage_project_url_fallback_multiple_parts() -> None:
+ metadata = MagicMock()
+ metadata.get.return_value = None
+
+ # `Homepage` is prioritized higher than `Source`
+ metadata.get_all.return_value = [
+ "Source, source",
+ "Homepage, homepage, foo, bar",
+ ]
+
+ assert "homepage, foo, bar" == extract_homepage(
+ metadata=metadata # type: ignore
+ )
+
+ metadata.get_all.assert_called_once_with("Project-URL", [])
+
+
+def test_extract_homepage_empty() -> None:
+ metadata = MagicMock()
+
+ metadata.get.return_value = None
+ metadata.get_all.return_value = []
+
+ assert None is extract_homepage(metadata=metadata) # type: ignore
+
+ metadata.get.assert_called_once_with("home-page", None)
+ metadata.get_all.assert_called_once_with("Project-URL", [])