Skip to content

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 \)