Implement: Use fnmatch for include and exclude #213 (#215)

* feature: fnmatch based includes and excludes

This is to support wildcards.

* PR Feedback

Signed-off-by: Bernát Gábor <bgabor8@bloomberg.net>

---------

Signed-off-by: Bernát Gábor <bgabor8@bloomberg.net>
Co-authored-by: Bernát Gábor <bgabor8@bloomberg.net>
This commit is contained in:
Jens W. Klein 2023-03-25 21:24:21 +01:00 committed by GitHub
parent c6b7837a7f
commit 3d3c805777
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 59 additions and 9 deletions

View File

@ -8,7 +8,7 @@ works for packages installed globally on a machine as well as in a virtualenv. S
as a flat list, finding out which are the top level packages and which packages do they depend on requires some effort.
It\'s also tedious to resolve conflicting dependencies that could have been installed because older version of `pip`
didn\'t have true dependency resolution[^1]. `pipdeptree` can help here by identifying conflicting dependencies
installed in the environment.R
installed in the environment.
To some extent, `pipdeptree` is inspired by the `lein deps :tree` command of [Leiningen](http://leiningen.org/).
@ -27,7 +27,7 @@ pipdeptree has been tested with Python versions `3.7`, `3.8`, `3.9` and `3.10`.
If you want to run pipdeptree in the context of a particular virtualenv, you can specify the `--python` option. Note
that this capability has been recently added in version `2.0.0`.
Alternately, you may also install pipdeptree inside the virtualenv and then run it from there.
Alternatively, you may also install pipdeptree inside the virtualenv and then run it from there.
## Usage and examples
@ -250,10 +250,12 @@ optional arguments:
packages that need them under them.
-p PACKAGES, --packages PACKAGES
Comma separated list of select packages to show in the
output. If set, --all will be ignored.
output. Wildcards are supported, like 'somepackage.*'.
If set, --all will be ignored.
-e PACKAGES, --exclude PACKAGES
Comma separated list of select packages to exclude
from the output. If set, --all will be ignored.
from the output. Wildcards are supported, like
'somepackage.*'. If set, --all will be ignored.
-j, --json Display dependency tree as json. This will yield "raw"
output that may be used by external tools. This option
overrides all other options.

View File

@ -1,4 +1,5 @@
import argparse
import fnmatch
import inspect
import json
import os
@ -350,14 +351,14 @@ class PackageDAG(Mapping):
m = {}
seen = set()
for node in self._obj.keys():
if node.key in exclude:
if any(fnmatch.fnmatch(node.key, e) for e in exclude):
continue
if include is None or node.key in include:
if include is None or any(fnmatch.fnmatch(node.key, i) for i in include):
stack.append(node)
while True:
if len(stack) > 0:
n = stack.pop()
cldn = [c for c in self._obj[n] if c.key not in exclude]
cldn = [c for c in self._obj[n] if not any(fnmatch.fnmatch(c.key, e) for e in exclude)]
m[n] = cldn
seen.add(n.key)
for c in cldn:
@ -844,12 +845,20 @@ def get_parser():
parser.add_argument(
"-p",
"--packages",
help="Comma separated list of select packages to show " "in the output. If set, --all will be ignored.",
help=(
"Comma separated list of select packages to show in the output. "
"Wildcards are supported, like 'somepackage.*'. "
"If set, --all will be ignored."
),
)
parser.add_argument(
"-e",
"--exclude",
help="Comma separated list of select packages to exclude " "from the output. If set, --all will be ignored.",
help=(
"Comma separated list of select packages to exclude from the output. "
"Wildcards are supported, like 'somepackage.*'. "
"If set, --all will be ignored."
),
metavar="PACKAGES",
)
parser.add_argument(

View File

@ -7,6 +7,7 @@ from itertools import chain
from pathlib import Path
from tempfile import NamedTemporaryFile
from textwrap import dedent, indent
from typing import Any
try:
from unittest import mock
@ -94,6 +95,43 @@ def test_package_dag_filter():
dag_to_dict(t.filter({"d"}, {"D", "e"}))
@pytest.fixture(scope="session")
def t_fnmatch() -> Any:
return mock_package_dag(
{
("a.a", "1"): [("a.b", []), ("a.c", [])],
("a.b", "1"): [("a.c", [])],
("b.a", "1"): [("b.b", [])],
("b.b", "1"): [("a.b", [])],
}
)
def test_package_dag_filter_fnmatch_include_a(t_fnmatch: Any) -> None:
# test include for a.*in the result we got only a.* nodes
graph = dag_to_dict(t_fnmatch.filter({"a.*"}, None))
assert graph == {"a.a": ["a.b", "a.c"], "a.b": ["a.c"]}
def test_package_dag_filter_fnmatch_include_b(t_fnmatch: Any) -> None:
# test include for b.*, which has a.b and a.c in tree, but not a.a
# in the result we got the b.* nodes plus the a.b node as child in the tree
graph = dag_to_dict(t_fnmatch.filter({"b.*"}, None))
assert graph == {"b.a": ["b.b"], "b.b": ["a.b"], "a.b": ["a.c"]}
def test_package_dag_filter_fnmatch_exclude_c(t_fnmatch: Any) -> None:
# test exclude for b.* in the result we got only a.* nodes
graph = dag_to_dict(t_fnmatch.filter(None, {"b.*"}))
assert graph == {"a.a": ["a.b", "a.c"], "a.b": ["a.c"]}
def test_package_dag_filter_fnmatch_exclude_a(t_fnmatch: Any) -> None:
# test exclude for a.* in the result we got only b.* nodes
graph = dag_to_dict(t_fnmatch.filter(None, {"a.*"}))
assert graph == {"b.a": ["b.b"], "b.b": []}
def test_package_dag_reverse():
t1 = t.reverse()
expected = {"a": [], "b": ["a", "f"], "c": ["a"], "d": ["b", "c"], "e": ["c", "d", "g"], "f": ["g"], "g": []}

View File

@ -1,6 +1,7 @@
[tox]
envlist =
fix
py311
py310
py39
py38