mrkeyoor.com_
Wed 05 Aug 19:52 UTC
PyPIUtilsupdated 05 Aug 2026

pathspec

pathspec answers one question well: does this file path match this set of gitignore-style patterns? You compile pattern lines (usually straight out of a .gitignore) into a PathSpec object, then test single paths, filter iterables of paths, or walk a directory tree. It ships two implementations: PathSpec with the 'gitignore' pattern type follows the gitignore documentation literally, while GitIgnoreSpec replicates what Git actually does, including the undocumented edge cases like negations re-including files from excluded directories. It is pure Python with zero required dependencies, and its enormous download count comes from being the ignore engine inside tools like Black and MkDocs.

Verdict

The standard way to do gitignore matching in Python, proven inside Black and MkDocs; use GitIgnoreSpec when fidelity to Git matters. Skip it when stdlib fnmatch would do, and read the 1.0 upgrade notes before bumping from 0.x.

API stability3/5The core from_lines/match_file surface has been steady for years, but the 1.0 release in January 2026 renamed the main pattern type, deprecated GitWildMatchPattern, and changed matching edge cases, so the 0.x to 1.x jump needs care.
Docs3/5The README tutorial and Read the Docs reference cover the API, and the upgrade guide is honest about behavior changes, but explanations of the PathSpec vs GitIgnoreSpec split stay terse and examples are thin.
Maintenance4/5Steady releases through 2026 (1.0 in January, 1.1.1 in April), pushed June 2026, and only 8 open issues and PRs; the limiting factor is that it is a single-maintainer project.
Ecosystem4/5Around 173M weekly downloads because major tools like Black and MkDocs depend on it, so it is battle-tested everywhere, though there is little plugin ecosystem around such a focused library.

Use it if

  • Your tool needs to respect .gitignore files the way Git does: GitIgnoreSpec exists precisely for that and matches Git's edge cases, not just the docs
  • You let users configure include/exclude rules and want them to write familiar gitignore syntax instead of raw regex or fnmatch globs
  • You need to filter thousands of paths and can install an optional speedup: the backend parameter switches matching to google-re2 or hyperscan for large pattern sets
  • You want zero required dependencies in a library of your own, since pathspec adds nothing else to your dependency tree
Skip it if

Setup reality

pip install pathspec is as easy as installs get: pure Python, no compiled parts, Python 3.9+. The gotchas are semantic. You must pick between PathSpec.from_lines('gitignore', ...) and GitIgnoreSpec.from_lines(...), and the two disagree on real-world patterns; if the goal is 'behave like Git', use GitIgnoreSpec, full stop. Paths are matched as relative strings, so passing absolute paths against patterns written for a repo root quietly matches nothing. Directory patterns need the trailing slash exactly as in gitignore. And if you came from 0.x, 'gitwildmatch' still works but is deprecated with different edge-case behavior than the new 'gitignore' type, which is an easy source of confusing diffs.

Patterns

Compile gitignore-style patternscompile-gitignore-lines

from pathspec import PathSpec

spec = PathSpec.from_lines('gitignore', [
    '*.pyc',
    'build/',
    '!build/keep.txt',
])
print(spec.match_file('module.pyc'))  # True

Since 1.0 the pattern type is named 'gitignore'; the old 'gitwildmatch' name still works but is deprecated and keeps slightly different edge-case behavior.

Load patterns from a .gitignore fileload-gitignore-file

from pathspec import GitIgnoreSpec

with open('.gitignore') as fh:
    spec = GitIgnoreSpec.from_lines(fh)

print(spec.match_file('dist/app.tar.gz'))

from_lines accepts any iterable of lines, so the open file object can be passed directly; comments and blank lines are handled like Git does.

Match exactly like Git, edge cases includedexact-git-behavior

from pathspec import GitIgnoreSpec

spec = GitIgnoreSpec.from_lines([
    'logs/',
    '!logs/important.log',
])
print(spec.match_file('logs/debug.log'))      # True
print(spec.match_file('logs/important.log'))  # False (re-included)

GitIgnoreSpec replicates Git's real behavior, including negations inside excluded directories, where plain PathSpec follows the documentation instead.

Check one pathmatch-single-file

from pathlib import PurePath
from pathspec import PathSpec

spec = PathSpec.from_lines('gitignore', ['docs/**/*.md'])
spec.match_file('docs/guide/intro.md')      # True
spec.match_file(PurePath('docs/README.md')) # pathlib works too

Paths are matched as given, relative to the implied root of the patterns; absolute paths against repo-relative patterns will not match.

Filter an existing list of pathsfilter-file-list

from pathspec import PathSpec

spec = PathSpec.from_lines('gitignore', ['*.tmp', 'cache/'])
files = ['a.txt', 'b.tmp', 'cache/x.json']
ignored = set(spec.match_files(files))
kept = [f for f in files if f not in ignored]

match_files returns a generator of the matching (ignored) entries; wrap it in set or list before reusing it.

Walk a directory tree for matcheswalk-directory-tree

from pathspec import PathSpec

spec = PathSpec.from_lines('gitignore', ['**/*.log'])
for rel_path in spec.match_tree_files('/var/app'):
    print(rel_path)

Yields paths relative to the root you pass in, walking the filesystem for you; use match_files instead if you already have the path list.

Invert matching to select files to keepkeep-instead-of-ignore

from pathspec import GitIgnoreSpec

spec = GitIgnoreSpec.from_lines(open('.gitignore'))
keep = set(spec.match_tree_files('project/', negate=True))

Specs positively match the ignored files by default; negate=True flips the result, which is what backup and packaging tools usually want.

Combine multiple pattern sourcescombine-specs

from pathspec import PathSpec

base = PathSpec.from_lines('gitignore', open('.gitignore'))
local = PathSpec.from_lines('gitignore', open('.gitignore.local'))
spec = base + local  # later patterns win, like nested .gitignore files

The + operator concatenates pattern lists, preserving order, so later negations can re-include files excluded by earlier specs.

Speed up matching with an alternate regex backendfaster-backend

# pip install 'pathspec[re2]'
from pathspec import GitIgnoreSpec

spec = GitIgnoreSpec.from_lines(lines, backend='re2')
# or backend='hyperscan', or the default backend='best'

New in 1.0. The default 'best' picks the fastest installed backend; re2 tends to win at high pattern counts per the project's own benchmarks.

Find out which pattern matchedcheck-which-pattern-matched

from pathspec import PathSpec

spec = PathSpec.from_lines('gitignore', ['*.log', '!keep.log'])
result = spec.check_file('debug.log')
print(result.include)  # True if ignored
print(result.index)    # index of the deciding pattern

check_file returns a result object instead of a bare bool, which is what you need for 'why is this file ignored' debugging output.

Alternatives

PackageRegistryPick it when
gitignore-parserPyPIYou want a tiny single-purpose parser for one .gitignore file and do not need tree walking or combined specs.
wcmatchPyPIYou need advanced general globbing (extglob, brace expansion, globstar) beyond gitignore semantics.