Skip to content

Commit 53b28f4

Browse files
committed
GH-139174: Add pathlib.Path.info.stat()
Add `stat()` method to `pathlib.Path.info` that returns a (possibly cached) `os.stat_result` object. We don't add it to `pathlib.types.PathInfo` because it's too specific to local filesystem paths. This requires a bit of reworking of the docs to explain! Rename `pathlib._Info` to `pathlib.Info` and document it, including the new `stat()` method. Ensure it can't be instantiated by users. Move the existing docs for `pathlib.types` to a new page.
1 parent 9e64938 commit 53b28f4

File tree

7 files changed

+186
-74
lines changed

7 files changed

+186
-74
lines changed

Doc/library/filesys.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ in this chapter is:
1313
.. toctree::
1414

1515
pathlib.rst
16+
pathlib.types.rst
1617
os.path.rst
1718
stat.rst
1819
filecmp.rst

Doc/library/pathlib.rst

Lines changed: 63 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1173,7 +1173,7 @@ Querying file type and status
11731173

11741174
.. attribute:: Path.info
11751175

1176-
A :class:`~pathlib.types.PathInfo` object that supports querying file type
1176+
An :class:`Info` object that supports querying file type
11771177
information. The object exposes methods that cache their results, which can
11781178
help reduce the number of system calls needed when switching on file type.
11791179
For example::
@@ -1202,6 +1202,10 @@ Querying file type and status
12021202

12031203
.. versionadded:: 3.14
12041204

1205+
.. versionchanged:: next
1206+
Value is specifically a :class:`Info` object, rather than an unnamed
1207+
implementation of the :class:`pathlib.types.PathInfo` protocol.
1208+
12051209

12061210
Reading and writing files
12071211
^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -1750,6 +1754,64 @@ Permissions and ownership
17501754
symbolic link's mode is changed rather than its target's.
17511755

17521756

1757+
Path information
1758+
----------------
1759+
1760+
.. class:: Info
1761+
1762+
Class that supports fetching and caching information about a local
1763+
filesystem path. An instance of this class is available from
1764+
:attr:`Path.info`; do not instantiate it directly.
1765+
1766+
This is an implementation of the :class:`pathlib.types.PathInfo` protocol
1767+
with an additional method specific to local paths (:meth:`~Info.stat`.)
1768+
1769+
.. versionadded:: next
1770+
1771+
.. method:: exists(*, follow_symlinks=True)
1772+
1773+
Return ``True`` if the path is an existing file or directory, or any
1774+
other kind of file; return ``False`` if the path doesn't exist.
1775+
1776+
If *follow_symlinks* is ``False``, return ``True`` for symlinks without
1777+
checking if their targets exist.
1778+
1779+
.. method:: is_dir(*, follow_symlinks=True)
1780+
1781+
Return ``True`` if the path is a directory, or a symbolic link pointing
1782+
to a directory; return ``False`` if the path is (or points to) any other
1783+
kind of file, or if it doesn't exist.
1784+
1785+
If *follow_symlinks* is ``False``, return ``True`` only if the path
1786+
is a directory (without following symlinks); return ``False`` if the
1787+
path is any other kind of file, or if it doesn't exist.
1788+
1789+
.. method:: is_file(*, follow_symlinks=True)
1790+
1791+
Return ``True`` if the path is a file, or a symbolic link pointing to
1792+
a file; return ``False`` if the path is (or points to) a directory or
1793+
other non-file, or if it doesn't exist.
1794+
1795+
If *follow_symlinks* is ``False``, return ``True`` only if the path
1796+
is a file (without following symlinks); return ``False`` if the path
1797+
is a directory or other non-file, or if it doesn't exist.
1798+
1799+
.. method:: is_symlink()
1800+
1801+
Return ``True`` if the path is a symbolic link (even if broken); return
1802+
``False`` if the path is a directory or any kind of file, or if it
1803+
doesn't exist.
1804+
1805+
.. method:: stat(*, follow_symlinks=True)
1806+
1807+
Return an :class:`os.stat_result` object containing low-level
1808+
information about this path, like :func:`os.stat`. The result is cached
1809+
on the :class:`Info` object.
1810+
1811+
If *follow_symlinks* is ``False`` and this path is a symlink, return
1812+
information about the symlink rather than its target.
1813+
1814+
17531815
.. _pathlib-pattern-language:
17541816

17551817
Pattern language
@@ -1925,56 +1987,3 @@ Below is a table mapping various :mod:`os` functions to their corresponding
19251987
.. [4] :func:`os.walk` always follows symlinks when categorizing paths into
19261988
*dirnames* and *filenames*, whereas :meth:`Path.walk` categorizes all
19271989
symlinks into *filenames* when *follow_symlinks* is false (the default.)
1928-
1929-
1930-
Protocols
1931-
---------
1932-
1933-
.. module:: pathlib.types
1934-
:synopsis: pathlib types for static type checking
1935-
1936-
1937-
The :mod:`pathlib.types` module provides types for static type checking.
1938-
1939-
.. versionadded:: 3.14
1940-
1941-
1942-
.. class:: PathInfo()
1943-
1944-
A :class:`typing.Protocol` describing the
1945-
:attr:`Path.info <pathlib.Path.info>` attribute. Implementations may
1946-
return cached results from their methods.
1947-
1948-
.. method:: exists(*, follow_symlinks=True)
1949-
1950-
Return ``True`` if the path is an existing file or directory, or any
1951-
other kind of file; return ``False`` if the path doesn't exist.
1952-
1953-
If *follow_symlinks* is ``False``, return ``True`` for symlinks without
1954-
checking if their targets exist.
1955-
1956-
.. method:: is_dir(*, follow_symlinks=True)
1957-
1958-
Return ``True`` if the path is a directory, or a symbolic link pointing
1959-
to a directory; return ``False`` if the path is (or points to) any other
1960-
kind of file, or if it doesn't exist.
1961-
1962-
If *follow_symlinks* is ``False``, return ``True`` only if the path
1963-
is a directory (without following symlinks); return ``False`` if the
1964-
path is any other kind of file, or if it doesn't exist.
1965-
1966-
.. method:: is_file(*, follow_symlinks=True)
1967-
1968-
Return ``True`` if the path is a file, or a symbolic link pointing to
1969-
a file; return ``False`` if the path is (or points to) a directory or
1970-
other non-file, or if it doesn't exist.
1971-
1972-
If *follow_symlinks* is ``False``, return ``True`` only if the path
1973-
is a file (without following symlinks); return ``False`` if the path
1974-
is a directory or other non-file, or if it doesn't exist.
1975-
1976-
.. method:: is_symlink()
1977-
1978-
Return ``True`` if the path is a symbolic link (even if broken); return
1979-
``False`` if the path is a directory or any kind of file, or if it
1980-
doesn't exist.

Doc/library/pathlib.types.rst

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
:mod:`!pathlib.types` --- Types for virtual filesystem paths
2+
============================================================
3+
4+
.. module:: pathlib
5+
:synopsis: pathlib types for virtual filesystem paths
6+
7+
.. versionadded:: 3.14
8+
9+
**Source code:** :source:`Lib/pathlib/types.py`
10+
11+
--------------
12+
13+
The :mod:`pathlib.types` module provides protocols for user-defined types that
14+
resemble :class:`pathlib.Path` and its associated classes. This module
15+
includes type annotations, so it's also useful for static type checking.
16+
17+
.. versionadded:: 3.14
18+
19+
20+
.. class:: PathInfo()
21+
22+
A :class:`typing.Protocol` that supports fetching and caching information
23+
about a path. :class:`pathlib.Info` is an implementation of this protocol
24+
with additional methods specific to local filesystems.
25+
26+
.. method:: exists(*, follow_symlinks=True)
27+
28+
Return ``True`` if the path is an existing file or directory, or any
29+
other kind of file; return ``False`` if the path doesn't exist.
30+
31+
If *follow_symlinks* is ``False``, return ``True`` for symlinks without
32+
checking if their targets exist.
33+
34+
.. method:: is_dir(*, follow_symlinks=True)
35+
36+
Return ``True`` if the path is a directory, or a symbolic link pointing
37+
to a directory; return ``False`` if the path is (or points to) any other
38+
kind of file, or if it doesn't exist.
39+
40+
If *follow_symlinks* is ``False``, return ``True`` only if the path
41+
is a directory (without following symlinks); return ``False`` if the
42+
path is any other kind of file, or if it doesn't exist.
43+
44+
.. method:: is_file(*, follow_symlinks=True)
45+
46+
Return ``True`` if the path is a file, or a symbolic link pointing to
47+
a file; return ``False`` if the path is (or points to) a directory or
48+
other non-file, or if it doesn't exist.
49+
50+
If *follow_symlinks* is ``False``, return ``True`` only if the path
51+
is a file (without following symlinks); return ``False`` if the path
52+
is a directory or other non-file, or if it doesn't exist.
53+
54+
.. method:: is_symlink()
55+
56+
Return ``True`` if the path is a symbolic link (even if broken); return
57+
``False`` if the path is a directory or any kind of file, or if it
58+
doesn't exist.

Doc/whatsnew/3.15.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,17 @@ os.path
407407
(Contributed by Petr Viktorin for :cve:`2025-4517`.)
408408

409409

410+
pathlib
411+
-------
412+
413+
* Add :class:`pathlib.Info` type, which queries and caches path metadata for
414+
local filesystem paths. This type is used by the :attr:`pathlib.Path.info`
415+
attribute, which consequently gains a :meth:`~pathlib.Info.stat` method;
416+
this method returns an :class:`os.stat_result` object with low-level details
417+
about the path.
418+
(Contributed by Barney Gale in :gh:`139174`.)
419+
420+
410421
resource
411422
--------
412423

Lib/pathlib/__init__.py

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -613,41 +613,44 @@ class PureWindowsPath(PurePath):
613613
__slots__ = ()
614614

615615

616-
class _Info:
616+
class Info:
617+
"""Implementation of pathlib.types.PathInfo that provides cached
618+
information about a local filesystem path. Don't try to construct it
619+
yourself.
620+
"""
617621
__slots__ = ('_path',)
618622

619623
def __init__(self, path):
624+
if type(self) is Info:
625+
raise TypeError('Info cannot be directly instantiated; '
626+
'use Path.info instead.')
620627
self._path = path
621628

622-
def __repr__(self):
623-
path_type = "WindowsPath" if os.name == "nt" else "PosixPath"
624-
return f"<{path_type}.info>"
625-
626-
def _stat(self, *, follow_symlinks=True):
629+
def stat(self, *, follow_symlinks=True):
627630
"""Return the status as an os.stat_result."""
628631
raise NotImplementedError
629632

630633
def _posix_permissions(self, *, follow_symlinks=True):
631634
"""Return the POSIX file permissions."""
632-
return S_IMODE(self._stat(follow_symlinks=follow_symlinks).st_mode)
635+
return S_IMODE(self.stat(follow_symlinks=follow_symlinks).st_mode)
633636

634637
def _file_id(self, *, follow_symlinks=True):
635638
"""Returns the identifier of the file."""
636-
st = self._stat(follow_symlinks=follow_symlinks)
639+
st = self.stat(follow_symlinks=follow_symlinks)
637640
return st.st_dev, st.st_ino
638641

639642
def _access_time_ns(self, *, follow_symlinks=True):
640643
"""Return the access time in nanoseconds."""
641-
return self._stat(follow_symlinks=follow_symlinks).st_atime_ns
644+
return self.stat(follow_symlinks=follow_symlinks).st_atime_ns
642645

643646
def _mod_time_ns(self, *, follow_symlinks=True):
644647
"""Return the modify time in nanoseconds."""
645-
return self._stat(follow_symlinks=follow_symlinks).st_mtime_ns
648+
return self.stat(follow_symlinks=follow_symlinks).st_mtime_ns
646649

647650
if hasattr(os.stat_result, 'st_flags'):
648651
def _bsd_flags(self, *, follow_symlinks=True):
649652
"""Return the flags."""
650-
return self._stat(follow_symlinks=follow_symlinks).st_flags
653+
return self.stat(follow_symlinks=follow_symlinks).st_flags
651654

652655
if hasattr(os, 'listxattr'):
653656
def _xattrs(self, *, follow_symlinks=True):
@@ -666,7 +669,7 @@ def _xattrs(self, *, follow_symlinks=True):
666669
_STAT_RESULT_ERROR = [] # falsy sentinel indicating stat() failed.
667670

668671

669-
class _StatResultInfo(_Info):
672+
class _StatResultInfo(Info):
670673
"""Implementation of pathlib.types.PathInfo that provides status
671674
information by querying a wrapped os.stat_result object. Don't try to
672675
construct it yourself."""
@@ -677,7 +680,7 @@ def __init__(self, path):
677680
self._stat_result = None
678681
self._lstat_result = None
679682

680-
def _stat(self, *, follow_symlinks=True):
683+
def stat(self, *, follow_symlinks=True):
681684
"""Return the status as an os.stat_result."""
682685
if follow_symlinks:
683686
if not self._stat_result:
@@ -705,7 +708,7 @@ def exists(self, *, follow_symlinks=True):
705708
if self._lstat_result is _STAT_RESULT_ERROR:
706709
return False
707710
try:
708-
self._stat(follow_symlinks=follow_symlinks)
711+
self.stat(follow_symlinks=follow_symlinks)
709712
except (OSError, ValueError):
710713
return False
711714
return True
@@ -719,7 +722,7 @@ def is_dir(self, *, follow_symlinks=True):
719722
if self._lstat_result is _STAT_RESULT_ERROR:
720723
return False
721724
try:
722-
st = self._stat(follow_symlinks=follow_symlinks)
725+
st = self.stat(follow_symlinks=follow_symlinks)
723726
except (OSError, ValueError):
724727
return False
725728
return S_ISDIR(st.st_mode)
@@ -733,7 +736,7 @@ def is_file(self, *, follow_symlinks=True):
733736
if self._lstat_result is _STAT_RESULT_ERROR:
734737
return False
735738
try:
736-
st = self._stat(follow_symlinks=follow_symlinks)
739+
st = self.stat(follow_symlinks=follow_symlinks)
737740
except (OSError, ValueError):
738741
return False
739742
return S_ISREG(st.st_mode)
@@ -743,13 +746,13 @@ def is_symlink(self):
743746
if self._lstat_result is _STAT_RESULT_ERROR:
744747
return False
745748
try:
746-
st = self._stat(follow_symlinks=False)
749+
st = self.stat(follow_symlinks=False)
747750
except (OSError, ValueError):
748751
return False
749752
return S_ISLNK(st.st_mode)
750753

751754

752-
class _DirEntryInfo(_Info):
755+
class _DirEntryInfo(Info):
753756
"""Implementation of pathlib.types.PathInfo that provides status
754757
information by querying a wrapped os.DirEntry object. Don't try to
755758
construct it yourself."""
@@ -759,7 +762,7 @@ def __init__(self, entry):
759762
super().__init__(entry.path)
760763
self._entry = entry
761764

762-
def _stat(self, *, follow_symlinks=True):
765+
def stat(self, *, follow_symlinks=True):
763766
"""Return the status as an os.stat_result."""
764767
return self._entry.stat(follow_symlinks=follow_symlinks)
765768

@@ -768,7 +771,7 @@ def exists(self, *, follow_symlinks=True):
768771
if not follow_symlinks:
769772
return True
770773
try:
771-
self._stat(follow_symlinks=follow_symlinks)
774+
self.stat(follow_symlinks=follow_symlinks)
772775
except OSError:
773776
return False
774777
return True

0 commit comments

Comments
 (0)