Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
14 changes: 12 additions & 2 deletions Doc/library/os.path.rst
Original file line number Diff line number Diff line change
Expand Up @@ -239,14 +239,24 @@ the :mod:`glob` module.)
Accepts a :term:`path-like object`.


.. function:: getsize(path, /)
.. function:: getsize(path, /, apparent=True)

Return the size, in bytes, of *path*. Raise :exc:`OSError` if the file does
not exist or is inaccessible.
not exist or is inaccessible. If *apparent* is ``True``, the apparent size
(number of bytes) of the file is returned. If ``False``, the actual size
(disk space occupied) is returned. The actual size reflects the block size,
meaning it will typically be larger than the apparent size. However, the
inverse may also be true due to holes in ("sparse") files, internal
fragmentation, indirect blocks, etc. Passing ``apparent=False`` is only
supported on platforms where :data:`os.DEV_BSIZE` is available; a
:exc:`NotImplementedError` is raised otherwise.

.. versionchanged:: 3.6
Accepts a :term:`path-like object`.

.. versionchanged:: 3.15
Add the optional *apparent* parameter.


.. function:: isabs(path, /)

Expand Down
10 changes: 10 additions & 0 deletions Doc/library/os.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2663,6 +2663,16 @@ features:
.. versionadded:: 3.15


.. data:: DEV_BSIZE

The size, in bytes, of a block as reported by the :attr:`~os.stat_result.st_blocks`
field of :class:`~os.stat_result`. This is typically 512 bytes.

:ref:`Availability <availability>`: Unix.

.. versionadded:: 3.15


.. function:: pathconf(path, name)

Return system configuration information relevant to a named file. *name*
Expand Down
24 changes: 21 additions & 3 deletions Lib/genericpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,27 @@ def isdevdrive(path):
return False


def getsize(filename, /):
"""Return the size of a file, reported by os.stat()."""
return os.stat(filename).st_size
def getsize(filename, /, apparent=True):
"""Return the size of a file, reported by os.stat().

If 'apparent' is True (default), the apparent size (number of bytes) of the
file is returned. If False, the actual size (disk space occupied) is
returned. The actual size reflects the block size, meaning it will
typically be larger than the apparent size. However, the inverse may also
be true due to holes in ("sparse") files, internal fragmentation, indirect
blocks, etc.

Not all platforms support apparent=False; a NotImplementedError is raised
on platforms where os.DEV_BSIZE is not available.
"""
if apparent:
return os.stat(filename).st_size
_dev_bsize = getattr(os, 'DEV_BSIZE', None)
if _dev_bsize is None:
raise NotImplementedError(
"os.path.getsize() with apparent=False is not supported on this platform"
)
return os.stat(filename).st_blocks * _dev_bsize


def getmtime(filename, /):
Expand Down
47 changes: 47 additions & 0 deletions Lib/test/test_genericpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,53 @@ def test_getsize(self):

create_file(filename, b'Hello World!')
self.assertEqual(self.pathmodule.getsize(filename), 12)
os.remove(filename)

open(filename, 'xb', 0).close()
os.truncate(filename, 512)
self.assertEqual(self.pathmodule.getsize(filename), 512)

@unittest.skipUnless(hasattr(os, 'DEV_BSIZE'),
"os.DEV_BSIZE not available on this platform")
def test_getsize_actual(self):
filename = os_helper.TESTFN
self.addCleanup(os_helper.unlink, filename)

# DEV_BSIZE varies across platforms
if support.is_android:
expected = 8192
elif support.is_wasi:
expected = 0
else:
expected = 4096

create_file(filename, b'Hello')
self.assertEqual(self.pathmodule.getsize(filename, apparent=False), expected)
os.remove(filename)

create_file(filename, b'Hello World!')
self.assertEqual(self.pathmodule.getsize(filename, apparent=False), expected)
os.remove(filename)

# DEV_BSIZE varies across platforms
if support.is_android:
expected = 4096
else:
expected = 0

open(filename, 'xb', 0).close()
os.truncate(filename, 512)
self.assertEqual(self.pathmodule.getsize(filename, apparent=False), expected)

@unittest.skipIf(hasattr(os, 'DEV_BSIZE'),
"os.DEV_BSIZE is available on this platform")
def test_getsize_actual_not_supported(self):
filename = os_helper.TESTFN
self.addCleanup(os_helper.unlink, filename)

create_file(filename, b'Hello')
with self.assertRaises(NotImplementedError):
self.pathmodule.getsize(filename, apparent=False)

def test_filetime(self):
filename = os_helper.TESTFN
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add support for an ``apparent`` option for ``os.get_size`` to allow
retrieving the actual on-disk size of sparse files.
11 changes: 11 additions & 0 deletions Modules/posixmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -18393,6 +18393,17 @@ all_ins(PyObject *m)
/* STATX_ATTR_* constants are in the stat module */
#endif /* HAVE_STATX */

/* Block size for the st_blocks field of stat(2).
* st_blocks is in 512-byte units on most platforms. DEV_BSIZE (or BSIZE
* as a fallback) from sys/param.h gives the actual platform value. */
#if defined(HAVE_STRUCT_STAT_ST_BLOCKS)
# if defined(DEV_BSIZE)
if (PyModule_AddIntConstant(m, "DEV_BSIZE", DEV_BSIZE)) return -1;
# elif defined(BSIZE)
if (PyModule_AddIntConstant(m, "DEV_BSIZE", BSIZE)) return -1;
# endif
#endif

#if defined(__APPLE__)
if (PyModule_AddIntConstant(m, "_COPYFILE_DATA", COPYFILE_DATA)) return -1;
if (PyModule_AddIntConstant(m, "_COPYFILE_STAT", COPYFILE_STAT)) return -1;
Expand Down
Loading