Skip to content

Core Path API

pathapi.path

Object-oriented filesystem paths.

This module provides classes to represent abstract paths and concrete paths with operations that have semantics appropriate for different operating systems.

PathLike = _ty.Union[str, Path] module-attribute

PurePathLike = _ty.Union[str, Pathname] module-attribute

__all__ = ['FsPathLike', 'Path', 'PathLike', 'Pathname', 'PurePathLike'] module-attribute

FsPathLike

Bases: Protocol

Anything implementing __fspath__, for annotations. A class qualifies by defining __fspath__ itself, and os.PathLike recognises it the same way; this protocol is not registered with os.PathLike, so a class that does not define one is rejected by os.fspath() with TypeError.

__slots__ = () class-attribute instance-attribute

__fspath__()

Source code in src/pathapi/path/_pure.py
85
86
def __fspath__(self) -> str:
    return _stubs._unimplemented("__fspath__")

Path

Bases: Pathname, Move, Copy, Identity, Create, Symlink, Remove, Listing, Resolve, Readlink, Hardlink, Owned, Stat, Chmod, Chown, Utime, BinaryOpen

Base class for manipulating paths with I/O.

__slots__ = () class-attribute instance-attribute

__init_subclass__(**kwargs)

Guarantee pathapi operation precedence in every subclass.

Concrete path classes are routinely built by mixing a pathlib class with this one -- our own LocalPath does it, and the documented downstream recipe (class X(PosixPathname, Path)) does it transitively. Python's MRO then resolves a name to whichever base declares it first, which for those classes can be pathlib rather than pathapi -- and which one wins changes with the interpreter version, because stdlib pathlib keeps gaining and changing methods (see _OPERATION_NAMES).

Rather than making every downstream implementer rediscover this and hand-write forwarding methods, re-assert our implementations here for any subclass that would otherwise inherit a non-pathapi one. A subclass (or an intermediate mixin) that defines the method itself is always left alone -- this only displaces implementations coming from outside this library.

_OPERATION_NAMES lists what is re-asserted, for two opposite reasons. Newer stdlib overrides ours: 3.14's copy and move expect the private _copy_from protocol, so such a class either raises AttributeError on non-local backends or silently succeeds on local ones with stdlib's metadata semantics (timestamps preserved, where ours preserves st_mode), so mtime-based syncs converge on 3.14 and not before; 3.14's copy_into and move_into call copy() and move() with stdlib's keywords and defaults. Older stdlib lacks our keywords: exists(follow_symlinks=) (3.12+), read_text and write_text(newline=) (3.13+), rglob's extensions, and symlink_to(force=), which no stdlib has. replace is not listed: LocalPath keeps stdlib's os.replace, which move() uses to swap a local file atomically.

A class that mixes a concrete stdlib path with Path but not LocalPath is a local class too, and gets what LocalPath has for each name it defines (_local_primitives) wherever stdlib or a generic default would otherwise answer: the generic is_junction() and is_mount() say False, so rm(recursive=True) would walk into a junction, and stdlib's stat(), chmod() and _scandir() do not take pathapi's arguments.

Source code in src/pathapi/path/_path.py
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
def __init_subclass__(cls, **kwargs):
    """Guarantee pathapi operation precedence in every subclass.

    Concrete path classes are routinely built by mixing a `pathlib`
    class with this one -- our own `LocalPath` does it, and the
    documented downstream recipe (`class X(PosixPathname, Path)`) does
    it transitively. Python's MRO then resolves a name to whichever
    base declares it first, which for those classes can be `pathlib`
    rather than `pathapi` -- and which one wins changes with the
    interpreter version, because stdlib `pathlib` keeps gaining and
    changing methods (see `_OPERATION_NAMES`).

    Rather than making every downstream implementer rediscover this and
    hand-write forwarding methods, re-assert our implementations here
    for any subclass that would otherwise inherit a non-pathapi
    one. A subclass (or an intermediate mixin) that defines the method
    *itself* is always left alone -- this only displaces implementations
    coming from outside this library.

    `_OPERATION_NAMES` lists what is re-asserted, for two opposite
    reasons. Newer stdlib overrides ours: 3.14's `copy` and `move` expect
    the private `_copy_from` protocol, so such a class either raises
    `AttributeError` on non-local backends or silently succeeds on local
    ones with stdlib's metadata semantics (timestamps preserved, where
    ours preserves `st_mode`), so mtime-based syncs converge on 3.14 and
    not before; 3.14's `copy_into` and `move_into` call `copy()` and
    `move()` with stdlib's keywords and defaults. Older stdlib lacks our
    keywords: `exists(follow_symlinks=)` (3.12+), `read_text` and
    `write_text(newline=)` (3.13+), `rglob`'s extensions, and
    `symlink_to(force=)`, which no stdlib has. `replace` is not listed:
    `LocalPath` keeps stdlib's `os.replace`, which `move()` uses to swap a
    local file atomically.

    A class that mixes a concrete stdlib path with `Path` but not
    `LocalPath` is a local class too, and gets what `LocalPath` has for
    each name it defines (`_local_primitives`) wherever stdlib or a
    generic default would otherwise answer: the generic `is_junction()`
    and `is_mount()` say False, so `rm(recursive=True)` would walk into a
    junction, and stdlib's `stat()`, `chmod()` and `_scandir()` do not
    take pathapi's arguments.
    """
    super().__init_subclass__(**kwargs)
    primitives = _local_primitives() if issubclass(cls, _pathlib.Path) else {}
    if primitives:
        from ..fspath import _BaseFSPathname

        for name, ours in primitives.items():
            if name == "glob" and not issubclass(cls, _BaseFSPathname):
                # LocalPath.glob needs the flavour helpers of
                # _BaseFSPathname.
                ours = Path.glob
            if _inherits_foreign_default(cls, name):
                setattr(cls, name, ours)
    for name in _OPERATION_NAMES:
        # A class that defines the operation in its OWN body is always
        # authoritative -- never displace a deliberate override (this
        # also covers `LocalPath.copy`/`move`'s explicit routing).
        if name in vars(cls):
            continue
        # The owner is the base that supplies the implementation, not the module of
        # the resolved function: a downstream mixin may define it in its own module.
        owner = next((base for base in cls.__mro__[1:] if name in vars(base)), None)
        if owner is None:
            continue
        # Only stdlib `pathlib` is displaced; a mixin, a user base or pathapi
        # itself is deliberate and stays, which keeps this off third-party code.
        owner_module = getattr(owner, "__module__", "") or ""
        if owner_module != "pathlib" and not owner_module.startswith("pathlib."):
            continue
        ours = getattr(Path, name, None)
        if ours is None:
            continue
        setattr(cls, name, ours)

__new__(*args, **kwargs)

Source code in src/pathapi/path/_path.py
183
184
185
186
187
188
189
190
191
def __new__(cls, *args: _ty.Any, **kwargs: _ty.Any) -> _ty.Self:
    if cls is Path:
        from ..fspath import LocalPath

        # `LocalPath.__new__` is `pathlib.Path.__new__`, which parses `args` into
        # `_drv/_root/_parts` before 3.12 (3.12+ does it in `PurePath.__init__`);
        # `Pathname.__new__(cls)` would skip that and drop `args`.
        return _ty.cast("_ty.Self", LocalPath.__new__(LocalPath, *args, **kwargs))
    return Pathname.__new__(cls)

is_hidden()

Return True if the final path component is a hidden file/directory.

Source code in src/pathapi/path/_path.py
193
194
195
def is_hidden(self):
    """Return True if the final path component is a hidden file/directory."""
    return self.name.startswith(".")

Pathname

Bases: Generic[_P]

Base class for manipulating paths without I/O.

__slots__ = () class-attribute instance-attribute

anchor property

drive + root.

drive property

No generic concept of a drive; always "". LocalPath gets a real one from pathlib on Windows.

name property

The final path component, if any.

parent property

The logical parent of the path.

parents property

An immutable sequence providing access to the logical ancestors of the path.

parts property

The individual components/parts of the path.

root property

The root of the path, if any (e.g. "/"). See docs/divergences.md for how this is derived generically vs. LocalPath's real one.

segments property

The sequence of path component strings.

stem property

The final path component, minus its last suffix.

suffix property

The final component's last suffix, if any.

This includes the leading period. For example: '.txt'

suffixes property

A list of the final component's suffixes, if any.

These include the leading periods. For example: ['.tar', '.gz']

__eq__(other)

Compare by (exact type, segments).

Identity equality would make is_relative_to() (which decides via ==) return False for every subclass and break paths as dict keys or set members, so a pure subclass that defines none gets this one. _BaseFSPathname/LocalPath are unaffected: pathlib.PurePath precedes Pathname in their MRO and keeps its own __eq__, as does any class that defines one.

Source code in src/pathapi/path/_pure.py
253
254
255
256
257
258
259
260
261
262
263
264
265
def __eq__(self, other: object) -> bool:
    """Compare by (exact type, segments).

    Identity equality would make `is_relative_to()` (which decides via
    `==`) return False for every subclass and break paths as dict keys
    or set members, so a pure subclass that defines none gets this one.
    `_BaseFSPathname`/`LocalPath` are unaffected: `pathlib.PurePath`
    precedes `Pathname` in their MRO and keeps its own `__eq__`, as
    does any class that defines one.
    """
    if type(self) is not type(other):
        return NotImplemented
    return tuple(self.segments) == tuple(other.segments)

__ge__(other)

Source code in src/pathapi/path/_pure.py
292
293
def __ge__(self, other: Pathname) -> bool:
    return self._compare(other, _operator.ge)

__gt__(other)

Source code in src/pathapi/path/_pure.py
289
290
def __gt__(self, other: Pathname) -> bool:
    return self._compare(other, _operator.gt)

__hash__()

Source code in src/pathapi/path/_pure.py
267
268
def __hash__(self) -> int:
    return hash((type(self), tuple(self.segments)))

__le__(other)

Source code in src/pathapi/path/_pure.py
286
287
def __le__(self, other: Pathname) -> bool:
    return self._compare(other, _operator.le)

__lt__(other)

Source code in src/pathapi/path/_pure.py
283
284
def __lt__(self, other: Pathname) -> bool:
    return self._compare(other, _operator.lt)

__rtruediv__(key)

"prefix" / path, as pathlib supports. self is passed as an object, so per-instance state it carries (a MemPath backend) survives, and an absolute self restarts the join.

Source code in src/pathapi/path/_pure.py
301
302
303
304
305
306
307
308
309
310
def __rtruediv__(self, key: str) -> _ty.Self:
    """`"prefix" / path`, as pathlib supports. `self` is passed as an
    object, so per-instance state it carries (a `MemPath` backend)
    survives, and an absolute `self` restarts the join."""
    if not isinstance(key, str):
        return NotImplemented
    try:
        return type(self)(key, self)
    except (TypeError, NotImplementedError):
        return NotImplemented

__truediv__(key)

Source code in src/pathapi/path/_pure.py
295
296
297
298
299
def __truediv__(self, key: _ty.Self | str) -> _ty.Self:
    try:
        return type(self)(self, key)
    except (TypeError, NotImplementedError):
        return NotImplemented

as_posix()

Return the string representation of the path with forward slashes.

Source code in src/pathapi/path/_pure.py
442
443
444
def as_posix(self) -> str:
    """Return the string representation of the path with forward slashes."""
    return "/".join(self.segments)

as_uri()

Return the path as a URI string.

Source code in src/pathapi/path/_pure.py
140
141
142
143
@_abstract
def as_uri(self) -> str:
    """Return the path as a URI string."""
    ...

full_match(pattern, *, case_sensitive=None)

Return True if this path matches the glob-style pattern against the whole path (3.13 parity). Unlike match(), this isn't a right-anchored partial match, and "" matches any number of path segments (including zero). The root is a component of its own: a rooted path needs a rooted pattern, or a leading "" that reaches past it (see glob.full_match). Empty and "." components of the pattern are ignored, as pathlib parses its pattern as a path.

Source code in src/pathapi/path/_pure.py
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
def full_match(
    self, pattern: str | _os.PathLike, *, case_sensitive: bool | None = None
) -> bool:
    """Return True if this path matches the glob-style `pattern`
    against the whole path (3.13 parity). Unlike match(), this isn't a
    right-anchored partial match, and "**" matches any number of path
    segments (including zero). The root is a component of its own: a
    rooted path needs a rooted pattern, or a leading "**" that reaches
    past it (see `glob.full_match`). Empty and "." components of
    the pattern are ignored, as `pathlib` parses its pattern as a path.
    """
    if case_sensitive is None:
        case_sensitive = self._is_case_sensitive
    if isinstance(pattern, Pathname):
        pattern = "/".join(pattern.segments)
    elif not isinstance(pattern, str):
        pattern = _os.fspath(pattern)
    return _glob.full_match(self.segments, pattern, case_sensitive)

has_glob_pattern()

Return True if any of the path segments contain glob wildcards.

The anchor is not one of them: a Windows extended-length path (\\?\C:\data, and the \\?\UNC\server\share form) carries a literal ? in its drive, which is a prefix, not a wildcard -- so a caller asking "is this a pattern or a plain path?" got True for every such path.

Source code in src/pathapi/path/_pure.py
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
def has_glob_pattern(self):
    """Return True if any of the path segments contain glob wildcards.

    The anchor is not one of them: a Windows extended-length path
    (`\\\\?\\C:\\data`, and the `\\\\?\\UNC\\server\\share` form) carries a
    literal `?` in its drive, which is a prefix, not a wildcard -- so a
    caller asking "is this a pattern or a plain path?" got `True` for
    every such path.
    """
    segments = list(self.segments)
    anchor = getattr(self, "anchor", "")
    if anchor and segments and segments[0] == anchor:
        segments = segments[1:]
    for segment in segments:
        if _glob.WILDCARD_PATTERN.search(segment) is not None:
            return True
    return False

is_absolute()

True if the path is absolute: it has a root. A class whose paths can be rooted without being absolute overrides it.

Source code in src/pathapi/path/_pure.py
351
352
353
354
def is_absolute(self) -> bool:
    """True if the path is absolute: it has a root. A class whose paths
    can be rooted without being absolute overrides it."""
    return bool(self.root)

is_relative_to(other)

Return True if the path is relative to another path or False.

Source code in src/pathapi/path/_pure.py
244
245
246
247
248
249
250
251
def is_relative_to(self, other: _ty.Self | str):
    """Return True if the path is relative to another path or False."""
    cls = type(self)
    # `other` is parsed standalone, as CPython does, not joined under `self`;
    # `with_segments()` rather than the constructor keeps per-instance state
    # such as MemPath's backend.
    other = other if isinstance(other, cls) else self.with_segments(other)
    return other == self or other in self.parents

joinpath(*args)

Combine this path with one or more paths/segments.

Source code in src/pathapi/path/_pure.py
312
313
314
def joinpath(self, *args: str | _ty.Self) -> _ty.Self:
    """Combine this path with one or more paths/segments."""
    return type(self)(self, *args)

match(path_pattern, *, case_sensitive=None)

Return True if this path matches the given glob-style pattern.

pathlib's semantics on the running interpreter: a relative pattern matches the last N segments (from the right), an absolute pattern must match the whole path, and each segment is matched on its own, so * never crosses a "/". "**" acts like "*" (use full_match() for recursive matching). An empty pattern raises ValueError.

A compiled re.Pattern cannot be split into segments; it is matched against the whole path string instead ("/"-joined segments, so a URI's scheme and host are never part of it).

Source code in src/pathapi/path/_pure.py
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
def match(self, path_pattern: str | _re.Pattern, *, case_sensitive=None):
    """
    Return True if this path matches the given glob-style pattern.

    pathlib's semantics on the running interpreter: a relative pattern
    matches the last N segments (from the right), an absolute pattern
    must match the whole path, and each segment is matched on its own,
    so `*` never crosses a "/". "**" acts like "*" (use `full_match()`
    for recursive matching). An empty pattern raises ValueError.

    A compiled `re.Pattern` cannot be split into segments; it is matched
    against the whole path string instead ("/"-joined segments, so a
    URI's scheme and host are never part of it).
    """
    import fnmatch as _fnmatch

    if case_sensitive is None:
        case_sensitive = self._is_case_sensitive
    anchored, names = self._match_parts()
    if isinstance(path_pattern, _re.Pattern):
        path = ("/" if anchored else "") + "/".join(names)
        return path_pattern.match(path) is not None
    if isinstance(path_pattern, Pathname):
        path_pattern = "/".join(path_pattern.segments)
    elif not isinstance(path_pattern, str):
        path_pattern = _os.fspath(path_pattern)
    pattern_anchored = path_pattern.startswith("/")
    pattern_names = [s for s in path_pattern.split("/") if s and s != "."]
    if not pattern_names and not pattern_anchored:
        raise ValueError("empty pattern")
    if _sys.version_info[:2] == (3, 12):
        # 3.12 matches the whole path as one string with its separators swapped for
        # newlines, which differs at the edges: see `_match_lines_312`.
        return _match_lines_312(
            anchored, names, pattern_anchored, pattern_names, case_sensitive
        )
    flags = 0 if case_sensitive else _re.IGNORECASE
    if pattern_anchored and not (anchored and len(names) == len(pattern_names)):
        return False
    # The root counts as one more part a relative pattern can reach.
    path_parts = len(names) + anchored
    if len(pattern_names) > path_parts:
        return False
    for index, pattern in enumerate(reversed(pattern_names)):
        if index == len(names):
            # A relative pattern as long as the path reaches its root. 3.13+ compiles the
            # root like any part (a bracket expression matches "/", "*" and "?" do not);
            # 3.9-3.11 fnmatch the root string, so "*" and "?" match it too.
            if _sys.version_info >= (3, 13):
                from ..fspath import _translate_segment

                regex = f"(?s:{_translate_segment(pattern, '[^/]')})\\Z"
                return _re.match(regex, "/", flags) is not None
            return _re.match(_fnmatch.translate(pattern), "/", flags) is not None
        name = names[len(names) - 1 - index]
        if _re.match(_fnmatch.translate(pattern), name, flags) is None:
            return False
    return True

relative_to(other)

Return the relative path to another path identified by the passed arguments. If the operation is not possible (because this is not related to the other path), raise ValueError.

Source code in src/pathapi/path/_pure.py
236
237
238
239
240
241
242
@_abstract
def relative_to(self, other: _ty.Self | str) -> _ty.Self:
    """Return the relative path to another path identified by the passed
    arguments.  If the operation is not possible (because this is not
    related to the other path), raise ValueError.
    """
    ...

with_name(name)

Return a new path with the name changed.

Raises ValueError for an empty name, ".", or a name containing "/", as pathlib does. Without the check a name such as "../../etc/passwd" or "x/y" was spliced in verbatim, so a caller relying on pathlib's validation of an untrusted file name got a path outside the directory. A bare ".." is accepted, also as pathlib does; use providers.is_safe_child_name() to reject it. with_stem()/ with_suffix() validate their result through here.

Source code in src/pathapi/path/_pure.py
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
def with_name(self, name: str) -> _ty.Self:
    """Return a new path with the name changed.

    Raises ValueError for an empty name, ".", or a name containing "/",
    as pathlib does. Without the check a name such as "../../etc/passwd"
    or "x/y" was spliced in verbatim, so a caller relying on pathlib's
    validation of an untrusted file name got a path outside the
    directory. A bare ".." is accepted, also as pathlib does; use
    `providers.is_safe_child_name()` to reject it. `with_stem()`/
    `with_suffix()` validate their result through here.
    """
    if not name or "/" in name or name == ".":
        raise ValueError("Invalid name %r" % (name,))
    if not self.name:
        raise ValueError("%r has an empty name" % (self,))
    return self.with_segments(*self.segments[:-1], name)

with_segments(*segments)

Construct a same-type path instance from new segments.

Source code in src/pathapi/path/_pure.py
186
187
188
189
@_abstract
def with_segments(self, *segments: str) -> _ty.Self:
    """Construct a same-type path instance from new segments."""
    ...

with_stem(stem)

Return a new path with the stem changed (validated like with_name()). From 3.13 on an empty stem is refused when the name has a suffix, as pathlib does: it would leave a name made of the suffix alone.

Source code in src/pathapi/path/_pure.py
208
209
210
211
212
213
214
215
216
def with_stem(self, stem: str) -> _ty.Self:
    """Return a new path with the stem changed (validated like
    `with_name()`). From 3.13 on an empty stem is refused when the name
    has a suffix, as `pathlib` does: it would leave a name made of the
    suffix alone."""
    suffix = self.suffix
    if _STEM_KEEPS_SUFFIX and suffix and not stem:
        raise ValueError("%r has a non-empty suffix" % (self,))
    return self.with_name(stem + suffix)

with_suffix(suffix)

Return a new path with the suffix changed or added.

Source code in src/pathapi/path/_pure.py
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
def with_suffix(self, suffix: str) -> _ty.Self:
    """Return a new path with the suffix changed or added."""
    name = self.name
    if (
        suffix
        and not suffix.startswith(".")
        or (suffix == "." and _sys.version_info < (3, 14))
    ):
        raise ValueError("Invalid suffix %r" % (suffix))
    if not name:
        raise ValueError("%r has an empty name" % (self,))
    old_suffix = self.suffix
    if not old_suffix:
        name = name + suffix
    else:
        name = name[: -len(old_suffix)] + suffix
    return self.with_name(name)

pathapi.fspath

__all__ = ['LocalPath', 'PosixPathname', 'WindowsPathname'] module-attribute

LocalPath

Bases: WindowsPath if name == 'nt' else PosixPath, Path, _BaseFSPathname

The real local filesystem path: pathlib.WindowsPath/PosixPath with this library's Path mixed in via MRO. Behaves exactly like pathlib.Path for anything not explicitly overridden here (see docs/divergences.md).

A class mixing a concrete stdlib path with Path but not this class is given every function defined here (Path.__init_subclass__), so each one names stdlib's implementation explicitly instead of calling super().

__slots__ = () class-attribute instance-attribute

chmod(mode, *, follow_symlinks=True)

Source code in src/pathapi/fspath.py
326
327
328
329
330
331
332
333
334
335
336
def chmod(self, mode: int | str, *, follow_symlinks: bool = True):
    # Same follow_symlinks= 3.10+ gap as stat() above; lchmod() is the
    # equivalent (NotImplementedError itself where os.lchmod is missing).
    mode = _permissions.parse_mode(mode)
    if _HAS_FOLLOW_SYMLINKS:
        return _path.Path.chmod(self, mode, follow_symlinks=follow_symlinks)
    return (
        _path.Path.chmod(self, mode)
        if follow_symlinks
        else _path.Path.lchmod(self, mode)
    )

glob(pattern, *, case_sensitive=None, include_hidden=True, recursive=None, dironly=None, recurse_symlinks=False, native=True, on_error=None, bound_loops=False)

Iterate over this subtree and yield all existing files (of any kind, including directories) matching the given relative pattern.

Same semantics as Path.glob(), including pattern=None (expand the pattern this path carries) and native= (follow the running interpreter, or one rule on every version); every separator of this flavour splits the pattern, and a pattern with a drive or root raises NonRelativePatternError like pathlib.

Raises the audit event pathlib.Path.glob with (self, pattern), as pathlib does. pattern=None has no pathlib counterpart and raises none.

Source code in src/pathapi/fspath.py
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
def glob(
    self,
    pattern: "str | _proto.FsPathLike | None",
    *,
    case_sensitive: bool = None,
    include_hidden: bool = True,
    recursive: bool = None,
    dironly: bool = None,
    recurse_symlinks: bool = False,
    native: bool = True,
    on_error: "_ty.Callable[[OSError], None]" = None,
    bound_loops: bool = False,
):
    """Iterate over this subtree and yield all existing files (of any
    kind, including directories) matching the given relative pattern.

    Same semantics as Path.glob(), including `pattern=None` (expand the
    pattern this path carries) and `native=` (follow the running
    interpreter, or one rule on every version); every separator of this
    flavour splits the pattern, and a pattern with a drive or root
    raises `NonRelativePatternError` like pathlib.

    Raises the audit event `pathlib.Path.glob` with `(self, pattern)`, as
    pathlib does. `pattern=None` has no pathlib counterpart and raises none.
    """
    if pattern is not None and not _IN_RGLOB.get():
        _sys.audit("pathlib.Path.glob", self, pattern)
    if pattern is None:
        return _proto.Path.glob(
            self,
            None,
            case_sensitive=case_sensitive,
            include_hidden=include_hidden,
            recursive=recursive,
            dironly=dironly,
            recurse_symlinks=recurse_symlinks,
            native=native,
            on_error=on_error,
            bound_loops=bound_loops,
        )
    pattern = _os.fspath(pattern)
    if pattern:
        anchored = self.with_segments(pattern)
        if anchored.drive or anchored.root:
            raise NonRelativePatternError("Non-relative patterns are unsupported")
        for sep in self._path_separators:
            pattern = pattern.replace(sep, "/")
        if any(self._parser.splitdrive(part)[0] for part in pattern.split("/")):
            # "sub/C:/x": joining "C:" would re-anchor outside self, and
            # no child can be named that, so nothing matches (pathlib).
            return iter(())
    return _proto.Path.glob(
        self,
        pattern,
        case_sensitive=case_sensitive,
        include_hidden=include_hidden,
        recursive=recursive,
        dironly=dironly,
        recurse_symlinks=recurse_symlinks,
        native=native,
        on_error=on_error,
        bound_loops=bound_loops,
    )

is_dir(*, follow_symlinks=True)

Source code in src/pathapi/fspath.py
343
344
345
346
347
348
349
def is_dir(self, *, follow_symlinks=True):
    if follow_symlinks:
        return _path.Path.is_dir(self)
    try:
        return _stat.S_ISDIR(self.stat(follow_symlinks=False).st_mode)
    except (OSError, ValueError):
        return False

is_file(*, follow_symlinks=True)

Source code in src/pathapi/fspath.py
351
352
353
354
355
356
357
def is_file(self, *, follow_symlinks=True):
    if follow_symlinks:
        return _path.Path.is_file(self)
    try:
        return _stat.S_ISREG(self.stat(follow_symlinks=False).st_mode)
    except (OSError, ValueError):
        return False

is_junction()

Whether this is a Windows junction. os.path.isjunction() on 3.12+, the reparse tag directly before that.

lstat() reports a junction (IO_REPARSE_TAG_MOUNT_POINT) as a plain directory -- CPython only rewrites the mode to S_IFLNK for real symlinks -- so a recursive removal must test for it or it would walk into one and delete the target's files, as shutil.rmtree guards.

Source code in src/pathapi/fspath.py
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
def is_junction(self) -> bool:
    """Whether this is a Windows junction. `os.path.isjunction()` on
    3.12+, the reparse tag directly before that.

    lstat() reports a junction (IO_REPARSE_TAG_MOUNT_POINT) as a plain
    directory -- CPython only rewrites the mode to S_IFLNK for real
    symlinks -- so a recursive removal must test for it or it would walk
    into one and delete the target's files, as `shutil.rmtree` guards.
    """
    if _os.name != "nt":
        return False
    isjunction = getattr(_os.path, "isjunction", None)
    if isjunction is not None:
        try:
            return bool(isjunction(self))
        except (OSError, ValueError):
            return False
    try:
        st = _os.lstat(self)
    except OSError:
        return False
    return getattr(st, "st_reparse_tag", 0) == getattr(
        _stat, "IO_REPARSE_TAG_MOUNT_POINT", 0xA0000003
    )

is_mount()

Whether this is a mount point -- a bind mount included, which is how POSIX spells what a junction does on Windows.

Source code in src/pathapi/fspath.py
281
282
283
284
285
286
287
def is_mount(self) -> bool:
    """Whether this is a mount point -- a bind mount included, which is
    how POSIX spells what a junction does on Windows."""
    try:
        return bool(_os.path.ismount(self))
    except (OSError, ValueError):
        return False

rglob(pattern, *, case_sensitive=None, include_hidden=True, recursive=True, dironly=None, recurse_symlinks=False, native=True, on_error=None, bound_loops=False)

Equivalent to glob(f"**/{pattern}", recursive=True), as Path.rglob(); raises the audit event pathlib.Path.rglob with (self, pattern), and from 3.13 the pathlib.Path.glob event of the glob() it stands for, as pathlib does. pattern=None raises none.

Source code in src/pathapi/fspath.py
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
def rglob(
    self,
    pattern: "str | _proto.FsPathLike | None",
    *,
    case_sensitive: bool = None,
    include_hidden: bool = True,
    recursive: bool = True,
    dironly: bool = None,
    recurse_symlinks: bool = False,
    native: bool = True,
    on_error: "_ty.Callable[[OSError], None]" = None,
    bound_loops: bool = False,
):
    """Equivalent to `glob(f"**/{pattern}", recursive=True)`, as
    `Path.rglob()`; raises the audit event `pathlib.Path.rglob` with
    `(self, pattern)`, and from 3.13 the `pathlib.Path.glob` event of the
    `glob()` it stands for, as pathlib does. `pattern=None` raises none."""
    if pattern is None:
        return _proto.Path.rglob(
            self,
            None,
            case_sensitive=case_sensitive,
            include_hidden=include_hidden,
            recursive=recursive,
            dironly=dironly,
            recurse_symlinks=recurse_symlinks,
            native=native,
            on_error=on_error,
            bound_loops=bound_loops,
        )
    _sys.audit("pathlib.Path.rglob", self, pattern)
    if _sys.version_info >= (3, 13):
        _sys.audit(
            "pathlib.Path.glob", self, self._parser.join("**", _os.fspath(pattern))
        )
    token = _IN_RGLOB.set(True)
    try:
        return _proto.Path.rglob(
            self,
            pattern,
            case_sensitive=case_sensitive,
            include_hidden=include_hidden,
            recursive=recursive,
            dironly=dironly,
            recurse_symlinks=recurse_symlinks,
            native=native,
            on_error=on_error,
            bound_loops=bound_loops,
        )
    finally:
        _IN_RGLOB.reset(token)

stat(*, follow_symlinks=True)

Source code in src/pathapi/fspath.py
318
319
320
321
322
323
324
def stat(self, *, follow_symlinks=True):
    # pathlib.Path.stat() (next in MRO via WindowsPath/PosixPath) only
    # accepts follow_symlinks= on 3.10+; below that, lstat() is the
    # equivalent for follow_symlinks=False.
    if _HAS_FOLLOW_SYMLINKS:
        return _path.Path.stat(self, follow_symlinks=follow_symlinks)
    return _path.Path.stat(self) if follow_symlinks else _path.Path.lstat(self)

walk(top_down=True, on_error=None, follow_symlinks=False)

Source code in src/pathapi/fspath.py
289
290
291
292
293
294
295
def walk(self, top_down=True, on_error=None, follow_symlinks=False):
    # 3.12+ stdlib `Path.walk()` would win the MRO, but it uses `self._scandir()` as a
    # context manager (`with scandir_it:`) and ours is a plain generator: route
    # explicitly to our own `walk()`.
    return _proto.Path.walk(
        self, top_down=top_down, on_error=on_error, follow_symlinks=follow_symlinks
    )

PosixPathname

Bases: PurePosixPath, _BaseFSPathname

Pure (no I/O) POSIX-flavour path, implementing the Pathname protocol on top of pathlib.PurePosixPath.

__slots__ = () class-attribute instance-attribute

WindowsPathname

Bases: PureWindowsPath, _BaseFSPathname

Pure (no I/O) Windows-flavour path, implementing the Pathname protocol on top of pathlib.PureWindowsPath.

__slots__ = () class-attribute instance-attribute

pathapi.FileStat(st_mode=None, st_size=0, st_mtime=0, is_dir=False, size_known=True)

Bases: FileStatLike

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

Source code in src/pathapi/_filestat.py
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
def __init__(
    self,
    st_mode: int | None = None,
    st_size: int = 0,
    st_mtime: float = 0,
    is_dir: bool = False,
    size_known: bool = True,
):
    self.size_known = size_known
    self.mode_known = bool(st_mode)
    if st_mode and not _stat.S_IFMT(st_mode):
        # Permission bits alone: the type comes from `is_dir`.
        st_mode |= _stat.S_IFDIR if is_dir else _stat.S_IFREG
    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

__slots__ = _FIELDS + ('mode_known', 'size_known') class-attribute instance-attribute

mode_known = bool(st_mode) instance-attribute

size_known = size_known instance-attribute

st_atime = 0 instance-attribute

st_ctime = 0 instance-attribute

st_gid = 0 instance-attribute

st_mode = st_mode or (_stat.S_IFDIR | 365 if is_dir else _stat.S_IFREG | 292) instance-attribute

st_mtime = st_mtime instance-attribute

st_size = st_size instance-attribute

st_uid = 0 instance-attribute

__getitem__(key)

Source code in src/pathapi/_filestat.py
102
103
def __getitem__(self, key):
    return getattr(self, key)

__repr__()

Source code in src/pathapi/_filestat.py
109
110
111
112
113
114
115
def __repr__(self):
    return "<%s mode=%o, size=%s, mtime=%d>" % (
        type(self).__name__,
        self.st_mode,
        sizeof_fmt(self.st_size),
        self.st_mtime,
    )

__str__()

Source code in src/pathapi/_filestat.py
117
118
119
def __str__(self):
    props = [f"{k}={v}" for k, v in self.items()]
    return "<%s %s>" % (type(self).__name__, ", ".join(props))

from_path(path, *, follow_symlink=True) classmethod

Source code in src/pathapi/_filestat.py
145
146
147
148
149
150
151
@classmethod
def from_path(cls, path: "Path", *, follow_symlink=True):
    try:
        stat: _os_stat = path.stat(follow_symlinks=follow_symlink)
    except FileNotFoundError:
        return None
    return cls.from_stat(stat)

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. The copied mode counts as backend-reported (mode_known) unless the source says otherwise or carries no mode at all (missing, None or 0).

A missing or None field becomes 0: paramiko's SFTPAttributes leaves every field the server did not send as None, which made is_dir() and repr() raise TypeError.

Source code in src/pathapi/_filestat.py
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
@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. The copied mode
    counts as backend-reported (`mode_known`) unless the source says
    otherwise or carries no mode at all (missing, `None` or `0`).

    A missing or `None` field becomes `0`: paramiko's `SFTPAttributes`
    leaves every field the server did not send as `None`, which made
    `is_dir()` and `repr()` raise TypeError."""
    if isinstance(stat, FileStat):
        return stat
    result = FileStat.__new__(FileStat)
    for prop in FileStat._FIELDS:
        value = getattr(stat, prop, None)
        setattr(result, prop, 0 if value is None else value)
    result.mode_known = bool(getattr(stat, "mode_known", True)) and bool(
        result.st_mode
    )
    result.size_known = bool(getattr(stat, "size_known", True))
    return result

is_block_device()

Whether this path is a block device.

Source code in src/pathapi/_filestat.py
172
173
174
175
176
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/pathapi/_filestat.py
178
179
180
181
182
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/pathapi/_filestat.py
153
154
155
156
157
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/pathapi/_filestat.py
184
185
186
187
188
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/pathapi/_filestat.py
159
160
161
162
163
164
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/pathapi/_filestat.py
190
191
192
193
194
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/pathapi/_filestat.py
166
167
168
169
170
def is_symlink(self):
    """
    Whether this path is a symbolic link.
    """
    return _stat.S_ISLNK(self.st_mode)

items()

Source code in src/pathapi/_filestat.py
105
106
107
def items(self):
    for key in self._FIELDS:
        yield key, getattr(self, key)

setmode(value, isdir=None)

Source code in src/pathapi/_filestat.py
 93
 94
 95
 96
 97
 98
 99
100
def setmode(self, value, isdir=None):
    if isdir is None:
        isdir = _stat.S_ISDIR(self.st_mode)
    if isdir:
        self.st_mode = _stat.S_IFDIR | value
    else:
        self.st_mode = _stat.S_IFREG | value
    self.mode_known = True

settime(value)

Source code in src/pathapi/_filestat.py
90
91
def settime(self, value):
    self.st_atime = self.st_mtime = self.st_ctime = value