Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
gh-83714: Use "stx_" prefix for all os.statx_result members
  • Loading branch information
vstinner committed Oct 21, 2025
commit 55db458aa73e9f9b54a021202e829d4a17aebfad
2 changes: 1 addition & 1 deletion Doc/library/os.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3412,7 +3412,7 @@ features:
Information about a file returned by :func:`os.statx`.

:class:`!statx_result` has all the attributes that :class:`~stat_result` has
on Linux, making it :term:`duck-typing` compatible, but
on Linux, but using ``stx_`` prefix instead of ``st_``.
:class:`!statx_result` is not a subclass of :class:`~stat_result` and cannot
Comment thread
cmaloney marked this conversation as resolved.
Outdated
be used as a tuple.

Expand Down
54 changes: 28 additions & 26 deletions Lib/test/test_os/test_os.py
Original file line number Diff line number Diff line change
Expand Up @@ -750,7 +750,8 @@ def check_statx_attributes(self, filename):
result = os.statx(filename, maximal_mask)
basic_result = os.stat(filename)

time_attributes = ('st_atime', 'st_mtime', 'st_ctime', 'st_birthtime')
time_attributes = ('stx_atime', 'stx_mtime', 'stx_ctime',
'stx_birthtime')
# gh-83714: st_birthtime can be None on tmpfs even if STATX_BTIME mask
# is used
time_attributes = [name for name in time_attributes
Expand All @@ -759,43 +760,44 @@ def check_statx_attributes(self, filename):

# Check that valid attributes match os.stat.
requirements = (
('st_mode', os.STATX_TYPE | os.STATX_MODE),
('st_nlink', os.STATX_NLINK),
('st_uid', os.STATX_UID),
('st_gid', os.STATX_GID),
('st_atime', os.STATX_ATIME),
('st_atime_ns', os.STATX_ATIME),
('st_mtime', os.STATX_MTIME),
('st_mtime_ns', os.STATX_MTIME),
('st_ctime', os.STATX_CTIME),
('st_ctime_ns', os.STATX_CTIME),
('st_ino', os.STATX_INO),
('st_size', os.STATX_SIZE),
('st_blocks', os.STATX_BLOCKS),
('st_birthtime', os.STATX_BTIME),
('st_birthtime_ns', os.STATX_BTIME),
('stx_mode', os.STATX_TYPE | os.STATX_MODE),
('stx_nlink', os.STATX_NLINK),
('stx_uid', os.STATX_UID),
('stx_gid', os.STATX_GID),
('stx_atime', os.STATX_ATIME),
('stx_atime_ns', os.STATX_ATIME),
('stx_mtime', os.STATX_MTIME),
('stx_mtime_ns', os.STATX_MTIME),
('stx_ctime', os.STATX_CTIME),
('stx_ctime_ns', os.STATX_CTIME),
('stx_ino', os.STATX_INO),
('stx_size', os.STATX_SIZE),
('stx_blocks', os.STATX_BLOCKS),
('stx_birthtime', os.STATX_BTIME),
('stx_birthtime_ns', os.STATX_BTIME),
# unconditionally valid members
('st_blksize', 0),
('st_rdev', 0),
('st_dev', 0),
('stx_blksize', 0),
('stx_rdev', 0),
('stx_dev', 0),
)
for name, bits in requirements:
if result.stx_mask & bits == bits and hasattr(basic_result, name):
st_name = "st_" + name[4:]
if result.stx_mask & bits == bits and hasattr(basic_result, st_name):
x = getattr(result, name)
b = getattr(basic_result, name)
b = getattr(basic_result, st_name)
self.assertEqual(type(x), type(b))
if isinstance(x, float):
self.assertAlmostEqual(x, b, msg=name)
else:
self.assertEqual(x, b, msg=name)

self.assertEqual(result.stx_rdev_major, os.major(result.st_rdev))
self.assertEqual(result.stx_rdev_minor, os.minor(result.st_rdev))
self.assertEqual(result.stx_dev_major, os.major(result.st_dev))
self.assertEqual(result.stx_dev_minor, os.minor(result.st_dev))
self.assertEqual(result.stx_rdev_major, os.major(result.stx_rdev))
self.assertEqual(result.stx_rdev_minor, os.minor(result.stx_rdev))
self.assertEqual(result.stx_dev_major, os.major(result.stx_dev))
self.assertEqual(result.stx_dev_minor, os.minor(result.stx_dev))

members = [name for name in dir(result)
if name.startswith('st_') or name.startswith('stx_')]
if name.startswith('stx_')]
for name in members:
try:
setattr(result, name, 1)
Expand Down
15 changes: 10 additions & 5 deletions Lib/test/test_os/test_posix.py
Original file line number Diff line number Diff line change
Expand Up @@ -1672,17 +1672,22 @@ def test_chown_dir_fd(self):
with self.prepare_file() as (dir_fd, name, fullname):
posix.chown(name, os.getuid(), os.getgid(), dir_fd=dir_fd)

def check_statlike_dir_fd(self, func):
def check_statlike_dir_fd(self, func, prefix):
with self.prepare() as (dir_fd, name, fullname):
with open(fullname, 'w') as outfile:
outfile.write("testline\n")
self.addCleanup(posix.unlink, fullname)

def get(result, attr):
return getattr(result, prefix + attr)

s1 = func(fullname)
s2 = func(name, dir_fd=dir_fd)
self.assertEqual((s1.st_dev, s1.st_ino), (s2.st_dev, s2.st_ino))
self.assertEqual((get(s1, "dev"), get(s1, "ino")),
(get(s2, "dev"), get(s2, "ino")))
s2 = func(fullname, dir_fd=None)
self.assertEqual((s1.st_dev, s1.st_ino), (s2.st_dev, s2.st_ino))
self.assertEqual((get(s1, "dev"), get(s1, "ino")),
(get(s2, "dev"), get(s2, "ino")))

self.assertRaisesRegex(TypeError, 'should be integer or None, not',
func, name, dir_fd=posix.getcwd())
Expand All @@ -1700,13 +1705,13 @@ def check_statlike_dir_fd(self, func):

@unittest.skipUnless(os.stat in os.supports_dir_fd, "test needs dir_fd support in os.stat()")
def test_stat_dir_fd(self):
self.check_statlike_dir_fd(posix.stat)
self.check_statlike_dir_fd(posix.stat, prefix="st_")

@unittest.skipUnless(hasattr(posix, 'statx'), "test needs os.statx()")
def test_statx_dir_fd(self):
def func(path, **kwargs):
return posix.statx(path, os.STATX_INO, **kwargs)
self.check_statlike_dir_fd(func)
self.check_statlike_dir_fd(func, prefix="stx_")

@unittest.skipUnless(os.utime in os.supports_dir_fd, "test needs dir_fd support in os.utime()")
def test_utime_dir_fd(self):
Expand Down
64 changes: 32 additions & 32 deletions Modules/posixmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -3330,17 +3330,17 @@ typedef struct {

static PyMemberDef pystatx_result_members[] = {
MM(stx_mask, Py_T_UINT, mask, "member validity mask"),
MM(st_blksize, Py_T_UINT, blksize, "blocksize for filesystem I/O"),
MM(stx_blksize, Py_T_UINT, blksize, "blocksize for filesystem I/O"),
MM(stx_attributes, Py_T_ULONGLONG, attributes, "Linux inode attribute bits"),
MM(st_mode, Py_T_USHORT, mode, "protection bits"),
MM(stx_mode, Py_T_USHORT, mode, "protection bits"),
MM(stx_attributes_mask, Py_T_ULONGLONG, attributes_mask,
"Mask of supported bits in stx_attributes"),
MM(stx_rdev_major, Py_T_UINT, rdev_major, "represented device major number"),
MM(stx_rdev_minor, Py_T_UINT, rdev_minor, "represented device minor number"),
MX(st_rdev, Py_T_ULONGLONG, rdev, "device type (if inode device)"),
MX(stx_rdev, Py_T_ULONGLONG, rdev, "device type (if inode device)"),
MM(stx_dev_major, Py_T_UINT, dev_major, "containing device major number"),
MM(stx_dev_minor, Py_T_UINT, dev_minor, "containing device minor number"),
MX(st_dev, Py_T_ULONGLONG, dev, "device"),
MX(stx_dev, Py_T_ULONGLONG, dev, "device"),
{NULL},
};

Expand All @@ -3361,9 +3361,9 @@ static PyMemberDef pystatx_result_members[] = {
return PyLong_FromUnsignedLong(value); \
}

STATX_GET_UINT(st_uid, stx_uid, STATX_UID)
STATX_GET_UINT(st_gid, stx_gid, STATX_GID)
STATX_GET_UINT(st_nlink, stx_nlink, STATX_NLINK)
STATX_GET_UINT(stx_uid, stx_uid, STATX_UID)
STATX_GET_UINT(stx_gid, stx_gid, STATX_GID)
STATX_GET_UINT(stx_nlink, stx_nlink, STATX_NLINK)
#ifdef HAVE_STRUCT_STATX_STX_DIO_MEM_ALIGN
STATX_GET_UINT(stx_dio_mem_align, stx_dio_mem_align, STATX_DIOALIGN)
STATX_GET_UINT(stx_dio_offset_align, stx_dio_offset_align, STATX_DIOALIGN)
Expand Down Expand Up @@ -3398,9 +3398,9 @@ STATX_GET_UINT(stx_atomic_write_unit_max_opt, stx_atomic_write_unit_max_opt,
return PyLong_FromUnsignedLongLong(value); \
}

STATX_GET_ULONGLONG(st_blocks, stx_blocks, STATX_BLOCKS)
STATX_GET_ULONGLONG(st_ino, stx_ino, STATX_INO)
STATX_GET_ULONGLONG(st_size, stx_size, STATX_SIZE)
STATX_GET_ULONGLONG(stx_blocks, stx_blocks, STATX_BLOCKS)
STATX_GET_ULONGLONG(stx_ino, stx_ino, STATX_INO)
STATX_GET_ULONGLONG(stx_size, stx_size, STATX_SIZE)
#ifdef HAVE_STRUCT_STATX_STX_MNT_ID
STATX_GET_ULONGLONG(stx_mnt_id, stx_mnt_id, STATX_MNT_ID)
#endif
Expand All @@ -3421,10 +3421,10 @@ STATX_GET_ULONGLONG(stx_subvol, stx_subvol, STATX_SUBVOL)
return PyFloat_FromDouble(sec); \
}

STATX_GET_DOUBLE(st_atime, atime_sec, STATX_ATIME)
STATX_GET_DOUBLE(st_birthtime, btime_sec, STATX_BTIME)
STATX_GET_DOUBLE(st_ctime, ctime_sec, STATX_CTIME)
STATX_GET_DOUBLE(st_mtime, mtime_sec, STATX_MTIME)
STATX_GET_DOUBLE(stx_atime, atime_sec, STATX_ATIME)
STATX_GET_DOUBLE(stx_birthtime, btime_sec, STATX_BTIME)
STATX_GET_DOUBLE(stx_ctime, ctime_sec, STATX_CTIME)
STATX_GET_DOUBLE(stx_mtime, mtime_sec, STATX_MTIME)

#define STATX_GET_NSEC(ATTR, MEMBER, MASK) \
static PyObject* \
Expand All @@ -3440,29 +3440,29 @@ STATX_GET_DOUBLE(st_mtime, mtime_sec, STATX_MTIME)
return stat_nanosecond_timestamp(state, ts->tv_sec, ts->tv_nsec); \
}

STATX_GET_NSEC(st_atime_ns, stx_atime, STATX_ATIME)
STATX_GET_NSEC(st_birthtime_ns, stx_btime, STATX_BTIME)
STATX_GET_NSEC(st_ctime_ns, stx_ctime, STATX_CTIME)
STATX_GET_NSEC(st_mtime_ns, stx_mtime, STATX_MTIME)
STATX_GET_NSEC(stx_atime_ns, stx_atime, STATX_ATIME)
STATX_GET_NSEC(stx_birthtime_ns, stx_btime, STATX_BTIME)
STATX_GET_NSEC(stx_ctime_ns, stx_ctime, STATX_CTIME)
STATX_GET_NSEC(stx_mtime_ns, stx_mtime, STATX_MTIME)

#define G(attr, doc) \
{#attr, pystatx_result_get_##attr, NULL, PyDoc_STR(doc), NULL}

static PyGetSetDef pystatx_result_getset[] = {
G(st_nlink, "number of hard links"),
G(st_uid, "user ID of owner"),
G(st_gid, "group ID of owner"),
G(st_ino, "inode"),
G(st_size, "total size, in bytes"),
G(st_blocks, "number of blocks allocated"),
G(st_atime, "time of last access"),
G(st_atime_ns, "time of last access in nanoseconds"),
G(st_birthtime, "time of creation"),
G(st_birthtime_ns, "time of creation in nanoseconds"),
G(st_ctime, "time of last change"),
G(st_ctime_ns, "time of last change in nanoseconds"),
G(st_mtime, "time of last modification"),
G(st_mtime_ns, "time of last modification in nanoseconds"),
G(stx_nlink, "number of hard links"),
G(stx_uid, "user ID of owner"),
G(stx_gid, "group ID of owner"),
G(stx_ino, "inode"),
G(stx_size, "total size, in bytes"),
G(stx_blocks, "number of blocks allocated"),
G(stx_atime, "time of last access"),
G(stx_atime_ns, "time of last access in nanoseconds"),
G(stx_birthtime, "time of creation"),
G(stx_birthtime_ns, "time of creation in nanoseconds"),
G(stx_ctime, "time of last change"),
G(stx_ctime_ns, "time of last change in nanoseconds"),
G(stx_mtime, "time of last modification"),
G(stx_mtime_ns, "time of last modification in nanoseconds"),
#ifdef HAVE_STRUCT_STATX_STX_MNT_ID
G(stx_mnt_id, "mount ID"),
#endif
Expand Down
Loading