Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Filters

Filters can be used to restrict Ganzua to operate only on certain dependencies. Which filters are supported depends on the exact subcommand.

  • ganzua inspect (--filter)
    Select which locked packages are shown in the output.
    Added as --name in Ganzua 0.4.0, renamed to --filter in Ganzua NEXT.

  • ganzua diff (--filter)
    Select which locked packages are included in the diff.
    Added as --name in Ganzua 0.4.0, renamed to --filter in Ganzua NEXT.

  • ganzua constraints inspect (--filter)
    Select which constraints are shown in the output.
    Added as --name in Ganzua 0.4.0, renamed to --filter in Ganzua NEXT.

  • ganzua constraints bump (--filter)
    Select which constraints are edited.
    Added as --name in Ganzua 0.4.0, renamed to --filter in Ganzua NEXT.

  • ganzua constraints reset (--filter)
    Select which constraints are edited.
    Added as --name in Ganzua 0.4.0, renamed to --filter in Ganzua NEXT.

The following sections describe the syntax of filters, how filters are evaluated, and provide examples.

Syntax

A PEG-style grammar for the filter syntax would look as follows:

filter → ws? rule ws? ( , ws? rule ws? )*

ruleLockfile!? pattern

ruleConstraint!? ( pattern ( ws qualifier )? | qualifier )

qualifier → ( extra: pattern | group: pattern )

pattern → ( literal | * | ? )+

literal → /[a-zA-Z0-9._-]+/

ws → /\s+/

A filter consists of one or more rules, separated by comma. Trailing commas are not allowed. Each rule may be surrounded by whitespace.

Each rule consists of a name pattern and/or further qualifiers. A negated rule has a leading ! exclamation mark. There may not be any space between the exclamation mark and the rest of the rule.

Rules come in two flavors: A lockfile rule always consists of just a name pattern. A constraint rule may consist of just a name pattern, a name pattern followed by a qualifier, or just a qualifier (which implies * as the name pattern).

A qualifier is a labeled pattern that either matches on an extra or group rather than on a constraint name.

Each pattern is a Unix glob expression, similar to what is supported in .gitignore files. A pattern can contain literal content, * asterisk metacharacters, and ? question mark metacharacters. The pattern must not be empty.

The * asterisk metacharacter matches zero or more arbitrary characters.

The ? question mark metacharacter matches a single arbitrary character.

Literal content can only consist of ASCII letters, ASCII digits, and PyPI name separators (._-). Filters are only used to match names (e.g. PyPI package names), so literal content is matched in a normalized manner. Literal content is not case sensitive, so the patterns A and a are equivalent. Separators all match each other as per the Python packaging name normalization rules, so the patterns a_b, a.b, and a--b are all equivalent.

Differences to .gitignore:

  • filter rules are separated by commas, not by newlines
  • comments are not supported
  • escapes are not needed – metacharacters can never occur in a name
  • no special rules for directory separators – slashes can never occur in a name
  • other fnmatch syntax is not supported (see below)
  • no ** double asterisk metacharacter

Differences to fnmatch(3), glob(3), or glob(7):

  • no character classes [abc], ranges [a-z], or complementation [!a-z] (also called bracket expressions)
  • no brace expansion {a,b,c}-{1,2,3}
  • no special handling for leading . periods – names can never start with a separator
  • matching is always case-insensitive

References:

Evaluation

When matching an item against a filter, all rules are evaluated in order. When a normal rule matches, the item is explicitly included. When a negated rule matches, the item is explicitly excluded. An item can flip between included and excluded state arbitrarily often, and only the final state matters. Filter matching does not short-circuit.

If a filter contains at least one normal (non-negated) rule, each item is excluded by default, and at least one of the normal rules must match. This is as-if such a filter started with a !* pattern. If a filter only contains negated rales, names are included by default, and the negated patterns can exclude names. This is as-if the filter started with a * rule.

These semantics are exactly how glob patterns work in ripgrep.

A constraint rule can consist of a plain pattern which matches the dependency name, and/or qualifiers which are patterns for matching group or extra names. If a qualifier is not specified, it will match everything – qualifiers only narrow a rule, never broaden it. For example, the rule * is a name-pattern which matches all constraints. The rules * group:* or just group:* match any constraints within any dependency group, but not constraints outside of a dependency group. If the same constraint is part of multiple dependency groups, a group:… qualifier will match if any group name matches. This matters especially for negated rules in the presence of the include-group operator.

Examples

Let's consider a project with the following lockfile:

full lockfile contents
nameversion
annotated-types0.7.0
click8.3.1
colorama0.4.6
coverage7.12.0
dirty-equals0.11
executing2.2.1
idna3.11
iniconfig2.3.0
inline-snapshot0.31.1
multidict6.7.0
mypy1.18.2
mypy-extensions1.1.0
packaging25.0
pluggy1.6.0
propcache0.4.1
pydantic2.12.4
pydantic-core2.41.5
pygments2.19.2
pytest9.0.1
pytest-cov7.0.0
tomlkit0.13.3
typing-extensions4.15.0
typing-inspection0.4.2
yarl1.22.0

We can use a name filter to select one or more specific packages from the lockfile:

$ ganzua inspect $EXAMPLE --filter=pydantic --format=markdown
| package  | version |
|----------|---------|
| pydantic | 2.12.4  |
$ ganzua inspect $EXAMPLE --filter=pydantic,mypy,pytest-cov --format=markdown
| package    | version |
|------------|---------|
| mypy       | 1.18.2  |
| pydantic   | 2.12.4  |
| pytest-cov | 7.0.0   |

We can use glob patterns to select all packages that start with py:

$ ganzua inspect $EXAMPLE --filter='py*' --format=markdown
| package       | version |
|---------------|---------|
| pydantic      | 2.12.4  |
| pydantic-core | 2.41.5  |
| pygments      | 2.19.2  |
| pytest        | 9.0.1   |
| pytest-cov    | 7.0.0   |

Or all packages that contain py but do not start with py:

$ ganzua inspect $EXAMPLE --filter='*py*, !py*' --format=markdown
| package         | version |
|-----------------|---------|
| mypy            | 1.18.2  |
| mypy-extensions | 1.1.0   |

Using the question mark operator, we can select all packages that have a 4-letter name:

$ ganzua inspect $EXAMPLE --filter='????' --format=markdown
| package | version |
|---------|---------|
| idna    | 3.11    |
| mypy    | 1.18.2  |
| yarl    | 1.22.0  |

When only using negated patterns, results are included by default. Here, we exclude all patterns that contain a hyphen, or start with the letters p or c:

$ ganzua inspect $EXAMPLE --filter='!*-*, !p*, !c*' --format=markdown
| package   | version |
|-----------|---------|
| executing | 2.2.1   |
| idna      | 3.11    |
| iniconfig | 2.3.0   |
| multidict | 6.7.0   |
| mypy      | 1.18.2  |
| tomlkit   | 0.13.3  |
| yarl      | 1.22.0  |

Filters are case-insensitive, and hyphens/underscores/periods are all equivalent. Thus, all of these filters produce the same results:

  • $ ganzua inspect $EXAMPLE --filter='*ex*,!*-*' --format=markdown
  • $ ganzua inspect $EXAMPLE --filter='*EX*,!*...*' --format=markdown
  • $ ganzua inspect $EXAMPLE --filter='*eX*,!*_*' --format=markdown
output for the above commands
| package   | version |
|-----------|---------|
| executing | 2.2.1   |

Some further filter syntax details that describe edge cases:

rules may be surrounded by spaces
$ ganzua inspect $EXAMPLE --filter='  foo , bar, pytest  ' --format=markdown
| package | version |
|---------|---------|
| pytest  | 9.0.1   |
filters must not be empty
$ ganzua inspect $EXAMPLE --filter='' --format=markdown
Usage: ganzua inspect [OPTIONS] [LOCKFILE]
Try 'ganzua inspect --help' for help.

Error: Invalid value for '--filter': expected filter pattern
at offset 0 (EOF):
  |
  |^
[command exited with status 2]
$ ganzua inspect $EXAMPLE --filter='foo,' --format=markdown
Usage: ganzua inspect [OPTIONS] [LOCKFILE]
Try 'ganzua inspect --help' for help.

Error: Invalid value for '--filter': expected filter pattern
at offset 4 (EOF):
  |foo,
  |    ^
[command exited with status 2]
syntax errors
$ ganzua inspect $EXAMPLE --filter='must not contain spaces' --format=markdown
Usage: ganzua inspect [OPTIONS] [LOCKFILE]
Try 'ganzua inspect --help' for help.

Error: Invalid value for '--filter': unexpected content after filter rule
at offset 5 (char 'n' U+006E LATIN SMALL LETTER N):
  |must not contain spaces
  |     ^
[command exited with status 2]
$ ganzua inspect $EXAMPLE --filter='/slashes/not/supported/' --format=markdown
Usage: ganzua inspect [OPTIONS] [LOCKFILE]
Try 'ganzua inspect --help' for help.

Error: Invalid value for '--filter': expected filter pattern
at offset 0 (char '/' U+002F SOLIDUS):
  |/slashes/not/supported/
  |^
[command exited with status 2]
$ ganzua inspect $EXAMPLE --filter='Ÿñiçøðœ' --format=markdown
Usage: ganzua inspect [OPTIONS] [LOCKFILE]
Try 'ganzua inspect --help' for help.

Error: Invalid value for '--filter': expected filter pattern
at offset 0 (char 'Ÿ' U+0178 LATIN CAPITAL LETTER Y WITH DIAERESIS):
  |Ÿñiçøðœ
  |^
[command exited with status 2]
Bracket expressions and brace expansion are not supported

Conventional fnmatch syntax allows character classes like [a-z] or [!a-z]. This doesn't seem overly helpful for matching package names, so hasn't been implemented yet.

$ ganzua inspect $EXAMPLE --filter='ex[a-z]mple' --format=markdown
Usage: ganzua inspect [OPTIONS] [LOCKFILE]
Try 'ganzua inspect --help' for help.

Error: Invalid value for '--filter': bracket expressions not supported
at offset 2 (char '[' U+005B LEFT SQUARE BRACKET):
  |ex[a-z]mple
  |  ^
[command exited with status 2]

Similarly, brace expansion is not supported. Instead, it's usually possible to write multiple separate filters.

$ ganzua inspect $EXAMPLE --filter='foo{,-bar}' --format=markdown
Usage: ganzua inspect [OPTIONS] [LOCKFILE]
Try 'ganzua inspect --help' for help.

Error: Invalid value for '--filter': brace expansion not supported
at offset 3 (char '{' U+007B LEFT CURLY BRACKET):
  |foo{,-bar}
  |   ^
[command exited with status 2]

The --name option is a deprecated alias for --filter:

$ ganzua inspect $EXAMPLE --name pydantic --format=markdown
DeprecationWarning: The option 'name' is deprecated. Use `--filter` instead of `--name`.
| package  | version |
|----------|---------|
| pydantic | 2.12.4  |

Constraint rule examples

Let's consider a project with the following pyproject.toml file:

$ cat $EXAMPLE/pyproject.toml
[project]
name = "example"
description = "demo for filters with constraint rules"
version = "0.0.0"
requires-python = ">=3.14"
dependencies = [
  "pydantic>=2.13.4",
]

[project.optional-dependencies]
db = ["sqlalchemy>=2.0.52"]
server = ["fastapi>=0.141.1"]

[dependency-groups]
dev = [{include-group = "test"}, {include-group = "types"}]
test = ["pytest>=9.1.1", "inline-snapshot>=0.35.4"]
types = ["mypy>=2.3.1"]
chores = ["towncrier>=25.8.0"]

By default, we'll see all dependencies:

$ ganzua constraints inspect $EXAMPLE --format=markdown
| package         | version   | group/extra                |
|-----------------|-----------|----------------------------|
| fastapi         | >=0.141.1 | extra `server`             |
| inline-snapshot | >=0.35.4  | group `dev`, group `test`  |
| mypy            | >=2.3.1   | group `dev`, group `types` |
| pydantic        | >=2.13.4  |                            |
| pytest          | >=9.1.1   | group `dev`, group `test`  |
| sqlalchemy      | >=2.0.52  | extra `db`                 |
| towncrier       | >=25.8.0  | group `chores`             |

Name filters work across extras and dependencies:

$ ganzua constraints inspect $EXAMPLE --filter='py*' --format=markdown
| package  | version  | group/extra               |
|----------|----------|---------------------------|
| pydantic | >=2.13.4 |                           |
| pytest   | >=9.1.1  | group `dev`, group `test` |

Can use qualifier rules to select just a certain extra or group:

$ ganzua constraints inspect $EXAMPLE --filter='extra:db' --format=markdown
| package    | version  | group/extra |
|------------|----------|-------------|
| sqlalchemy | >=2.0.52 | extra `db`  |
$ ganzua constraints inspect $EXAMPLE --filter='group:dev' --format=markdown
| package         | version  | group/extra                |
|-----------------|----------|----------------------------|
| inline-snapshot | >=0.35.4 | group `dev`, group `test`  |
| mypy            | >=2.3.1  | group `dev`, group `types` |
| pytest          | >=9.1.1  | group `dev`, group `test`  |
$ ganzua constraints inspect $EXAMPLE --filter='group:test' --format=markdown
| package         | version  | group/extra               |
|-----------------|----------|---------------------------|
| inline-snapshot | >=0.35.4 | group `dev`, group `test` |
| pytest          | >=9.1.1  | group `dev`, group `test` |

Qualifiers themselves can take patterns:

$ ganzua constraints inspect $EXAMPLE --filter='group:t*' --format=markdown
| package         | version  | group/extra                |
|-----------------|----------|----------------------------|
| inline-snapshot | >=0.35.4 | group `dev`, group `test`  |
| mypy            | >=2.3.1  | group `dev`, group `types` |
| pytest          | >=9.1.1  | group `dev`, group `test`  |

Wildcard qualifiers can be used to select "any extra" or "any group":

$ ganzua constraints inspect $EXAMPLE --filter='group:*' --format=markdown
| package         | version  | group/extra                |
|-----------------|----------|----------------------------|
| inline-snapshot | >=0.35.4 | group `dev`, group `test`  |
| mypy            | >=2.3.1  | group `dev`, group `types` |
| pytest          | >=9.1.1  | group `dev`, group `test`  |
| towncrier       | >=25.8.0 | group `chores`             |
$ ganzua constraints inspect $EXAMPLE --filter='extra:*' --format=markdown
| package    | version   | group/extra    |
|------------|-----------|----------------|
| fastapi    | >=0.141.1 | extra `server` |
| sqlalchemy | >=2.0.52  | extra `db`     |

Can select main dependencies by excluding any extras or groups:

$ ganzua constraints inspect $EXAMPLE --filter='!extra:*, !group:*' --format=markdown
| package  | version  |
|----------|----------|
| pydantic | >=2.13.4 |

Can combine name patterns with qualifiers. For example, can select all py* dependencies form the dev-group:

$ ganzua constraints inspect $EXAMPLE --filter='py* group:dev' --format=markdown
| package | version | group/extra               |
|---------|---------|---------------------------|
| pytest  | >=9.1.1 | group `dev`, group `test` |

Qualifier-related syntax errors:

Qualifier cannot come before name
$ ganzua constraints inspect $EXAMPLE --filter='group:dev py*'
Usage: ganzua constraints inspect [OPTIONS] [PYPROJECT]
Try 'ganzua constraints inspect --help' for help.

Error: Invalid value for '--filter': unexpected content after filter rule
at offset 10 (char 'p' U+0070 LATIN SMALL LETTER P):
  |group:dev py*
  |          ^
[command exited with status 2]
Unknown qualifiers are rejected
$ ganzua constraints inspect $EXAMPLE --filter='foo:bar'
Usage: ganzua constraints inspect [OPTIONS] [PYPROJECT]
Try 'ganzua constraints inspect --help' for help.

Error: Invalid value for '--filter': unexpected content after filter rule
at offset 3 (char ':' U+003A COLON):
  |foo:bar
  |   ^
[command exited with status 2]
$ ganzua constraints inspect $EXAMPLE --filter='* foo:bar'
Usage: ganzua constraints inspect [OPTIONS] [PYPROJECT]
Try 'ganzua constraints inspect --help' for help.

Error: Invalid value for '--filter': unexpected content after filter rule
at offset 2 (char 'f' U+0066 LATIN SMALL LETTER F):
  |* foo:bar
  |  ^
[command exited with status 2]
Qualifiers cannot be repeated
$ ganzua constraints inspect $EXAMPLE --filter='group:dev group:test'
Usage: ganzua constraints inspect [OPTIONS] [PYPROJECT]
Try 'ganzua constraints inspect --help' for help.

Error: Invalid value for '--filter': unexpected content after filter rule
at offset 10 (char 'g' U+0067 LATIN SMALL LETTER G):
  |group:dev group:test
  |          ^
[command exited with status 2]
Invalid characters
$ ganzua constraints inspect $EXAMPLE --filter='/foo/bar'
Usage: ganzua constraints inspect [OPTIONS] [PYPROJECT]
Try 'ganzua constraints inspect --help' for help.

Error: Invalid value for '--filter': expected filter pattern or qualifier
at offset 0 (char '/' U+002F SOLIDUS):
  |/foo/bar
  |^
[command exited with status 2]
Qualifier pattern must not be empty
$ ganzua constraints inspect $EXAMPLE --filter='group: ,*'
Usage: ganzua constraints inspect [OPTIONS] [PYPROJECT]
Try 'ganzua constraints inspect --help' for help.

Error: Invalid value for '--filter': expected pattern
at offset 6 (char ' ' U+0020 SPACE):
  |group: ,*
  |      ^
[command exited with status 2]
Lockfile filters cannot have qualifiers
$ ganzua inspect $EXAMPLE --filter='group:dev'
Usage: ganzua inspect [OPTIONS] [LOCKFILE]
Try 'ganzua inspect --help' for help.

Error: Invalid value for '--filter': unexpected content after filter rule
at offset 5 (char ':' U+003A COLON):
  |group:dev
  |     ^
[command exited with status 2]