Skip to content

Utilities API

pathlib_next.utils.glob

full_match(segments, pattern, case_sensitive)

Match segments against a glob pattern that may contain "**" components matching zero or more segments (pathlib 3.13's PurePath.full_match semantics).

Source code in src/pathlib_next/utils/glob.py
31
32
33
34
35
36
37
def full_match(
    segments: _ty.Sequence[str], pattern: str, case_sensitive: bool
) -> bool:
    """Match `segments` against a glob pattern that may contain "**"
    components matching zero or more segments (pathlib 3.13's
    PurePath.full_match semantics)."""
    return _full_match(tuple(segments), tuple(pattern.split("/")), case_sensitive)

glob(path, *, dironly=False, root_dir=None, recursive=False, include_hidden=False, case_sensitive=None)

Return an iterator which yields the paths matching a pathname pattern.

The pattern may contain simple shell-style wildcards a la fnmatch. However, unlike fnmatch, filenames starting with a dot are special cases that are not matched by '*' and '?' patterns.

If recursive is true, the pattern '**' will match any files and zero or more directories and subdirectories.

Source code in src/pathlib_next/utils/glob.py
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
def glob(
    path: _Globable,
    *,
    dironly: bool = False,
    root_dir: _Globable | None = None,
    recursive: bool = False,
    include_hidden: bool = False,
    case_sensitive: bool | None = None,
) -> _ty.Iterable[_Globable]:
    """Return an iterator which yields the paths matching a pathname pattern.

    The pattern may contain simple shell-style wildcards a la
    fnmatch. However, unlike fnmatch, filenames starting with a
    dot are special cases that are not matched by '*' and '?'
    patterns.

    If recursive is true, the pattern '**' will match any files and
    zero or more directories and subdirectories.
    """
    if case_sensitive is None:
        case_sensitive = path._is_case_sensitive

    include_hidden = include_hidden or path.is_hidden()
    pattern = compile_pattern(path.name, case_sensitive) if path.name else ANY_PATTERN

    name_is_pattern = WILDCARD_PATTERN.search(path.name) != None
    wildcard_in_path = name_is_pattern or path.has_glob_pattern()
    parent = next(iter(path.parents), None)

    root: _Globable = (
        (root_dir or parent) if not root_dir or not parent else (root_dir / parent)
    )

    if recursive and path.name == RECURSIVE:
        globber = _glob_recursive
    else:
        globber = _glob_with_pattern

    if not parent or not wildcard_in_path:
        yield from globber(
            root or path,
            pattern,
            dironly,
            include_hidden=include_hidden,
        )
        return

    # Recurse to enumerate matching directories only when the *parent*
    # portion itself contains a wildcard (e.g. "sub*/*.py" needs every
    # "sub*"-matching dir found first). Using `name_is_pattern` (whether the
    # *leaf* is a pattern) here instead -- which is almost always true, since
    # that's the common case of a literal directory + wildcarded filename --
    # was wrong: it re-globbed `parent`'s own parent to "rediscover" parent
    # by name, which only degenerates back to plain `[parent]` when `parent`
    # has a non-empty literal name to match against. It silently returned
    # the wrong directory set when `parent.name` is "" (MemPath's virtual
    # root, and -- untested here, LocalPath at an OS filesystem root).
    if parent and parent.has_glob_pattern():
        dirs = glob(
            parent,
            root_dir=root_dir,
            recursive=recursive,
            dironly=True,
            include_hidden=include_hidden,
            case_sensitive=case_sensitive,
        )
    else:
        dirs = [parent]

    for parent in dirs:
        yield from globber(parent, pattern, dironly, include_hidden)

pathlib_next.utils.stat

FileStat(st_mode=None, st_size=0, st_mtime=0, is_dir=False)

Bases: FileStatLike

Concrete, slotted FileStatLike for backends without a real os.stat_result (e.g. MemPath, HttpPath). from_path() builds one from any object with a stat() method, or passes a FileStat through unchanged.

Source code in src/pathlib_next/utils/stat.py
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
def __init__(
    self,
    st_mode: int = None,
    st_size: int = 0,
    st_mtime: int = 0,
    is_dir: bool = False,
):
    self.st_mode = st_mode or (
        _stat.S_IFDIR | 0o555 if is_dir else _stat.S_IFREG | 0o444
    )
    self.st_nlink = 1
    self.st_uid = 0
    self.st_gid = 0
    self.st_size = st_size
    self.st_atime = 0
    self.st_mtime = st_mtime
    self.st_ctime = 0

from_stat(stat) classmethod

Copy any stat-like object's (os.stat_result, paramiko's SFTPAttributes, ...) recognized fields into a fresh FileStat, so downstream code (e.g. .is_dir()) can rely on a uniform type. Passes an already-FileStat through unchanged.

Source code in src/pathlib_next/utils/stat.py
81
82
83
84
85
86
87
88
89
90
91
92
@classmethod
def from_stat(cls, stat: _ty.Any) -> "FileStat":
    """Copy any stat-like object's (`os.stat_result`, paramiko's
    `SFTPAttributes`, ...) recognized fields into a fresh `FileStat`,
    so downstream code (e.g. `.is_dir()`) can rely on a uniform type.
    Passes an already-`FileStat` through unchanged."""
    if isinstance(stat, FileStat):
        return stat
    result = FileStat.__new__(FileStat)
    for prop in FileStat.__slots__:
        setattr(result, prop, getattr(stat, prop, 0))
    return result

is_block_device()

Whether this path is a block device.

Source code in src/pathlib_next/utils/stat.py
121
122
123
124
125
def is_block_device(self):
    """
    Whether this path is a block device.
    """
    return _stat.S_ISBLK(self.st_mode)

is_char_device()

Whether this path is a character device.

Source code in src/pathlib_next/utils/stat.py
127
128
129
130
131
def is_char_device(self):
    """
    Whether this path is a character device.
    """
    return _stat.S_ISCHR(self.st_mode)

is_dir()

Whether this path is a directory.

Source code in src/pathlib_next/utils/stat.py
102
103
104
105
106
def is_dir(self):
    """
    Whether this path is a directory.
    """
    return _stat.S_ISDIR(self.st_mode)

is_fifo()

Whether this path is a FIFO.

Source code in src/pathlib_next/utils/stat.py
133
134
135
136
137
def is_fifo(self):
    """
    Whether this path is a FIFO.
    """
    return _stat.S_ISFIFO(self.st_mode)

is_file()

Whether this path is a regular file (also True for symlinks pointing to regular files).

Source code in src/pathlib_next/utils/stat.py
108
109
110
111
112
113
def is_file(self):
    """
    Whether this path is a regular file (also True for symlinks pointing
    to regular files).
    """
    return _stat.S_ISREG(self.st_mode)

is_socket()

Whether this path is a socket.

Source code in src/pathlib_next/utils/stat.py
139
140
141
142
143
def is_socket(self):
    """
    Whether this path is a socket.
    """
    return _stat.S_ISSOCK(self.st_mode)

Whether this path is a symbolic link.

Source code in src/pathlib_next/utils/stat.py
115
116
117
118
119
def is_symlink(self):
    """
    Whether this path is a symbolic link.
    """
    return _stat.S_ISLNK(self.st_mode)

pathlib_next.utils.sync

PathSyncer(checksum=None, /, remove_missing=False, follow_symlinks=True, hook=None, ignore_error=False)

Bases: object

One-way checksum-driven tree sync: copies/creates in target whatever differs from source (by checksum), optionally removing files in target that are missing from source. Works across any two Path implementations (e.g. MemPath -> LocalPath, or between two UriPath schemes) -- see sync().

Source code in src/pathlib_next/utils/sync.py
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
def __init__(
    self,
    checksum: _ty.Callable[[PathAndStat], _ty.Any] | None = None,
    /,
    remove_missing: bool = False,
    follow_symlinks: bool = True,
    hook: _ty.Callable[[PathAndStat, PathAndStat, SyncEvent, bool], None] = None,
    ignore_error: _OnPathSyncerError | bool = False,
) -> None:
    if checksum is None:
        checksum = lambda entry: _md5(entry.path)
    self.checksum = checksum
    self.remove_missing = remove_missing
    self._hook = hook
    self.follow_symlinks = follow_symlinks
    if not callable(ignore_error):
        _ignore_error = lambda *args, **kwargs: bool(ignore_error)
    else:
        _ignore_error = ignore_error
    self.ignore_error = _ty.cast(_OnPathSyncerError, _ignore_error)

SyncEvent

Bases: Enum

Events PathSyncer.hook() fires during a sync, for progress/logging callbacks.

pathlib_next.utils

LRU(func, maxsize=128)

Bases: Generic[K, V]

Thread-safe memoizing LRU cache over a function, callable like the function itself; invalidate(*args) evicts and recomputes an entry.

Source code in src/pathlib_next/utils/__init__.py
41
42
43
44
45
def __init__(self, func: _ty.Callable[K, V], maxsize=128):
    self.cache = collections.OrderedDict()
    self.func = func
    self._maxsize = maxsize
    self.lock = RLock()