Guide¶
Installation¶
Install globlin from PyPI:
$ python3 -m venv .venv
$ source .venv/bin/activate
$ pip install globlin
$ uv init
$ uv add globlin
Usage¶
Case sensitivity
Unlike Python's fnmatch.fnmatch, which is case-insensitive
on case-insensitive filesystems (macOS, Windows), globlin always performs
case-sensitive matching - equivalent to
fnmatch.fnmatchcase. For case-insensitive matching,
normalize both the pattern and value with os.path.normcase()
before matching.
Builder API¶
The primary API is the Glob class. Create one with Glob.default() for
POSIX fnmatch-like behavior (bracket expansion and escape sequences enabled):
from globlin import Glob
glob = Glob.default()
Use .match(pattern, value) to test whether a value matches a glob pattern:
glob.match("*.py", "foo.py") # ✅
glob.match("*.py", "foo.txt") # ❌
glob.match("test_?", "test_a") # ✅
glob.match("[abc]", "b") # ✅
Enable additional features by chaining builder methods:
# Enable globstar (implies path separator handling)
glob = Glob.default().globstar()
glob.match("src/**/*.py", "src/a/b/foo.py") # ✅
# Enable brace expansion
glob = Glob.default().brace_expansion()
glob.match("{src,lib}/*.py", "src/foo.py") # ✅
# Combine features
glob = Glob.default().globstar().brace_expansion().negate()
Every feature has a no_ counterpart to disable it:
# Disable escape sequences
glob = Glob.default().no_escape()
glob.match(r"\*", r"\*") # ✅ (backslash is literal)
Use Glob.empty() to start with all features off - only * and ?
wildcards work:
glob = Glob.empty()
glob.match("*.py", "foo.py") # ✅
glob.match("[abc]", "b") # ❌ (brackets are literal)
Flags based API¶
Deprecated
The fnmatch function and Flag enum will be
removed in 1.0. See Migrating from v0.2 for how to update
your code.
from globlin import fnmatch
fnmatch("*.py", "foo.py") # ✅
Pass Flag values to fnmatch() to enable features:
from globlin import fnmatch, Flag
# Enable globstar
fnmatch("src/**/*.py", "src/a/b/foo.py", Flag.GLOB_STAR) # ✅
# Enable brace expansion
fnmatch("{src,lib}/*.py", "src/foo.py", Flag.BRACE_EXPANSION) # ✅
# Combine features
fnmatch("!{a,b}", "c", Flag.NEGATE, Flag.BRACE_EXPANSION) # ✅
Pass Flags to Glob.from_flags(): unreleased
from globlin import Glob, Flags
glob = Glob.from_flags(Flags.GLOB_STAR | Flags.PATH_SEPARATOR | Flags.ESCAPE)
See the API reference for all available flags.
Pattern features¶
Path separator handling¶
With path separator handling, * and ? stop at /.
The pattern *.py matches foo.py but not dir/foo.py.
glob = Glob.default().path_separator()
Note: .globstar() automatically enables path separator handling.
# Without path separator (default)
glob = Glob.default()
glob.match("*.py", "dir/foo.py") # ✅ (* matches anything)
# With path separator
glob = Glob.default().path_separator()
glob.match("*.py", "foo.py") # ✅
glob.match("*.py", "dir/foo.py") # ❌ (* stops at /)
glob.match("*/*.py", "dir/foo.py") # ✅
Globstar (**)¶
The ** pattern matches zero or more path segments, including nested
directories.
glob = Glob.default().globstar()
Path Separator handling
Calling .globstar() automatically enables .path_separator(), since
globstar is only meaningful when / is treated as a path separator.
glob = Glob.default().globstar()
# Match files at any depth
glob.match("src/**/*.py", "src/foo.py") # ✅
glob.match("src/**/*.py", "src/a/b/c/foo.py") # ✅
# ** matches zero segments too
glob.match("**/*.py", "foo.py") # ✅
# Single * does not cross /
glob.match("src/*/*.py", "src/a/b/foo.py") # ❌
** matches any number of path components (sequences of characters
separated by /). A single * matches any character except /.
Bracket expansion ([...])¶
Bracket expressions match a single character from a set or range.
Glob.default() enables bracket expansion.
glob = Glob.default()
# Character set
glob.match("[abc]", "a") # ✅
glob.match("[abc]", "d") # ❌
# Character range
glob.match("[a-z]", "m") # ✅
glob.match("[a-z]", "M") # ❌
# Negated set
glob.match("[!abc]", "d") # ✅
glob.match("[!abc]", "a") # ❌
# Combined with wildcards
glob.match("file[0-9].txt", "file3.txt") # ✅
glob.match("file[0-9].txt", "file10.txt") # ❌
Without bracket expansion, brackets match literally:
glob = Glob.default().no_bracket_expansion()
glob.match("[abc]", "a") # ❌
glob.match("[abc]", "[abc]") # ✅
Brace expansion ({...})¶
Brace expressions match any of several comma-separated alternatives.
glob = Glob.default().brace_expansion()
# Simple alternatives
glob.match("{foo,bar}", "foo") # ✅
glob.match("{foo,bar}", "baz") # ❌
# In paths
glob.match("{src,lib}/*.py", "src/foo.py") # ✅
glob.match("{src,lib}/*.py", "lib/bar.py") # ✅
glob.match("{src,lib}/*.py", "test/baz.py") # ❌
# Nested braces (up to 10 levels)
glob.match("{a,{b,c}}", "c") # ✅
Braces nest up to 10 levels deep. The matcher tries each alternative in order and picks the first match.
Negation (!)¶
A leading ! inverts the match: the pattern matches when the value does
not match the rest of the pattern.
glob = Glob.default().negate()
glob.match("!*.py", "foo.txt") # ✅ (does not match *.py)
glob.match("!*.py", "foo.py") # ❌ (matches *.py, so negated)
Escape sequences (\)¶
A backslash \ before a special character removes its special meaning,
letting you match literal *, ?, [, etc.
Glob.default() enables escape sequences.
glob = Glob.default()
# Match literal *
glob.match(r"\*", "*") # ✅
glob.match(r"\*", "foo") # ❌
# Match literal ?
glob.match(r"\?", "?") # ✅
glob.match(r"\?", "a") # ❌
# Match literal brackets
glob.match(r"\[abc\]", "[abc]") # ✅
Without escape handling, backslash matches literally:
glob = Glob.default().no_escape()
glob.match(r"\*", r"\foo") # ✅ (\ is literal, * is wildcard)
glob.match(r"\*", "foo") # ❌ (no leading \)