A library for filesystem path discovery and pattern matching.
Project description
searchpath
searchpath finds files across prioritized directories and tracks where each match comes from. Use it for config cascades (project overrides user overrides system), plugin discovery, or any scenario where files might exist in more than one location and you need to know which one you found.
[!NOTE] This package is under active development. The API may change before the 1.0 release.
Features
- Search across directories with priority: Find files in config cascades, plugin directories, or any ordered set of paths
- Track where matches come from: Every match includes provenance, telling you exactly which directory contains the file
- Filter with patterns: Use glob patterns (default), full regex, or gitignore-style rules with negation (using pathspec)
- Load patterns from files: Support hierarchical pattern files that cascade like
.gitignore - Simple one-liners for common cases:
searchpath.first("config.toml", project_dir, user_dir) - Minimal footprint: Only requires
typing-extensionson Python < 3.12;pathspecoptional for gitignore support - Safe for concurrent use: Immutable objects with no global state
Installation
pip:
pip install searchpath
# With gitignore-style pattern support via pathspec
pip install searchpath[gitignore]
poetry:
poetry add searchpath
# With gitignore-style pattern support via pathspec
poetry add searchpath[gitignore]
uv:
uv add searchpath
# With gitignore-style pattern support via pathspec
uv add searchpath[gitignore]
Quick start
import searchpath
# Find the first config.toml in project or user directories
config = searchpath.first("config.toml", "/project", "~/.config")
# Find all Python files
py_files = searchpath.all("**/*.py", "/src")
# Get match with provenance information
match = searchpath.match("settings.json", ("project", "/project"), ("user", "~/.config"))
if match:
print(f"Found in {match.scope}: {match.path}")
Usage
The SearchPath class
from searchpath import SearchPath
# Create with named scopes
sp = SearchPath(
("project", "/project/.config"),
("user", "~/.config/myapp"),
("system", "/etc/myapp"),
)
# Find first matching file
config = sp.first("config.toml")
# Find all matching files with provenance
matches = sp.matches("**/*.toml")
for m in matches:
print(f"{m.scope}: {m.relative}")
Pattern filtering
# Include/exclude patterns
sp.all("**/*.py", exclude=["test_*", "**/tests/**"])
# Load patterns from files
sp.all(exclude_from="exclude_patterns.txt")
# Ancestor pattern files (like gitignore cascading)
sp.all(exclude_from_ancestors=".searchignore")
Manipulating search paths
# Append path components
config_sp = sp.with_suffix(".config", "myapp")
# Concatenate search paths
combined = project_sp + user_sp
# Filter to existing directories
sp.existing()
Custom matchers
from searchpath import RegexMatcher, GitignoreMatcher
# Regex patterns
sp.all(r".*\.py$", matcher=RegexMatcher())
# Gitignore-style patterns (requires pathspec)
sp.all(exclude=["*.pyc", "__pycache__/"], matcher=GitignoreMatcher())
API
Module-level functions
def first(
pattern: str = "**",
*entries: Entry,
kind: Literal["files", "dirs", "both"] = "files",
include: str | Sequence[str] | None = None,
include_from: Path | str | Sequence[Path | str] | None = None,
include_from_ancestors: str | None = None,
exclude: str | Sequence[str] | None = None,
exclude_from: Path | str | Sequence[Path | str] | None = None,
exclude_from_ancestors: str | None = None,
matcher: PathMatcher | None = None,
follow_symlinks: bool = True,
) -> Path | None
Find the first matching path across directories. Returns Path or None.
def match(...) -> Match | None # Same parameters as first()
Find the first matching path with provenance information.
def all(
pattern: str = "**",
*entries: Entry,
kind: Literal["files", "dirs", "both"] = "files",
dedupe: bool = True, # Additional parameter
include: ..., # Same as first()
...
) -> list[Path]
Find all matching paths across directories.
def matches(...) -> list[Match] # Same parameters as all()
Find all matching paths with provenance information.
The SearchPath class
class SearchPath:
def __init__(self, *entries: Entry) -> None: ...
@property
def dirs(self) -> list[Path]: ...
@property
def scopes(self) -> list[str]: ...
def first(self, pattern: str = "**", ...) -> Path | None: ...
def match(self, pattern: str = "**", ...) -> Match | None: ...
def all(self, pattern: str = "**", ...) -> list[Path]: ...
def matches(self, pattern: str = "**", ...) -> list[Match]: ...
def with_suffix(self, *parts: str) -> SearchPath: ...
def filter(self, predicate: Callable[[Path], bool]) -> SearchPath: ...
def existing(self) -> SearchPath: ...
def items(self) -> Iterator[tuple[str, Path]]: ...
Match dataclass
@dataclass(frozen=True, slots=True)
class Match:
path: Path # Absolute path to the matched file
scope: str # Scope name (e.g., "user", "project")
source: Path # The search path directory
@property
def relative(self) -> Path: ... # Path relative to source
Entry type
Entry = tuple[str, Path | str | None] | Path | str | None
Pattern matchers
GlobMatcher- Default glob-style patterns (*,**,?,[abc])RegexMatcher- Full Python regex syntaxGitignoreMatcher- Full gitignore compatibility (requirespathspec)
Exceptions
SearchPathError # Base exception
├── PatternError # Pattern-related errors
│ ├── PatternSyntaxError(pattern, message, position)
│ └── PatternFileError(path, message, line_number)
└── ConfigurationError # Invalid configuration
Development
# Clone and install
git clone https://github.com/tbhb/searchpath
cd searchpath
just install
# Common commands
just test # Run tests
just lint # Run linters
just format # Format code
See CONTRIBUTING.md for more details.
AI disclosure
The development of this library involved AI language models, specifically Claude. AI tools contributed to drafting code, tests, and documentation. Human authors made all design decisions and final implementations, and they reviewed, edited, and validated AI-generated content. The authors take full responsibility for the correctness of this software.
Acknowledgments
This library optionally uses the pathspec library by Caleb P. Burns for gitignore-compatible pattern matching.
License
MIT License. See LICENSE for details.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file searchpath-0.1.0.tar.gz.
File metadata
- Download URL: searchpath-0.1.0.tar.gz
- Upload date:
- Size: 19.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.9.24 {"installer":{"name":"uv","version":"0.9.24","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0589c994447d214f5e4518d2247bc7dba95e6ba851b5a087ab69bd88f613ea89
|
|
| MD5 |
0275513420bcddfea5d3320b014bca27
|
|
| BLAKE2b-256 |
d0638cb1e05f615b57f5f3ad72c20beda9a73e4044dcc95e91f991296407c671
|
File details
Details for the file searchpath-0.1.0-py3-none-any.whl.
File metadata
- Download URL: searchpath-0.1.0-py3-none-any.whl
- Upload date:
- Size: 21.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.9.24 {"installer":{"name":"uv","version":"0.9.24","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5898f2b93a606a93d29e2844b42e58b1cbef1ce996b11ab884e5cbdf4dbf246e
|
|
| MD5 |
a66ff71633c7b93efbd5795760e7b87e
|
|
| BLAKE2b-256 |
8573950454231105cc387ac857dfa8c779c07adc88be72a66eb12b355b7ce4c0
|