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 <mc...@suse.com>
+
+- 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. `&#123;`) 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", [])

Reply via email to