Skip to main content

searchpath

PyPI CI Codecov CodSpeed Badge Python 3.10+ License: MIT

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-extensions on Python < 3.12; pathspec optional 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 syntax
  • GitignoreMatcher - Full gitignore compatibility (requires pathspec)

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.

Metadata

Release files for searchpath 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for searchpath 0.1.0
File Size Uploaded Provenance
searchpath-0.1.0.tar.gz 19.3 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for searchpath 0.1.0
File Interpreter ABI Platform
searchpath-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 40.6 kB

Release files / searchpath-0.1.0.tar.gz

Download URL searchpath-0.1.0.tar.gz
Size 19.3 kB
Tags Source
SHA-256 checksum
How to use checksums
0589c994447d214f5e4518d2247bc7dba95e6ba851b5a087ab69bd88f613ea89
BLAKE2b-256 checksum
How to use checksums
d0638cb1e05f615b57f5f3ad72c20beda9a73e4044dcc95e91f991296407c671
Upload date
Uploaded using Trusted Publishing?
What is 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}

Release files / searchpath-0.1.0-py3-none-any.whl

Download URL searchpath-0.1.0-py3-none-any.whl
Size 21.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
5898f2b93a606a93d29e2844b42e58b1cbef1ce996b11ab884e5cbdf4dbf246e
BLAKE2b-256 checksum
How to use checksums
8573950454231105cc387ac857dfa8c779c07adc88be72a66eb12b355b7ce4c0
Upload date
Uploaded using Trusted Publishing?
What is 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}

Security for searchpath 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page