Skip to content

Commit 6c12100

Browse files
committed
gh-85264: Request actual filesize from 'os.path.getsize'
Provide the ability to get the actual, on-disk size of a file using 'os.path.getsize'. This is helpful when dealing with things like sparse files. Signed-off-by: Stephen Finucane <stephen@that.guru>
1 parent 15468a7 commit 6c12100

File tree

4 files changed

+82
-5
lines changed

4 files changed

+82
-5
lines changed

Doc/library/os.path.rst

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -239,14 +239,24 @@ the :mod:`glob` module.)
239239
Accepts a :term:`path-like object`.
240240

241241

242-
.. function:: getsize(path, /)
242+
.. function:: getsize(path, /, apparent=True)
243243

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

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

257+
.. versionchanged:: 3.15
258+
Add the optional *apparent* parameter.
259+
250260

251261
.. function:: isabs(path, /)
252262

Lib/genericpath.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,27 @@ def isdevdrive(path):
8282
return False
8383

8484

85-
def getsize(filename, /):
86-
"""Return the size of a file, reported by os.stat()."""
87-
return os.stat(filename).st_size
85+
def getsize(filename, /, apparent=True):
86+
"""Return the size of a file, reported by os.stat().
87+
88+
If 'apparent' is True (default), the apparent size (number of bytes) of the
89+
file is returned. If False, the actual size (disk space occupied) is
90+
returned. The actual size reflects the block size, meaning it will
91+
typically be larger than the apparent size. However, the inverse may also
92+
be true due to holes in ("sparse") files, internal fragmentation, indirect
93+
blocks, etc.
94+
95+
Not all platforms support apparent=False; a NotImplementedError is raised
96+
on platforms where os.DEV_BSIZE is not available.
97+
"""
98+
if apparent:
99+
return os.stat(filename).st_size
100+
_dev_bsize = getattr(os, 'DEV_BSIZE', None)
101+
if _dev_bsize is None:
102+
raise NotImplementedError(
103+
"os.path.getsize() with apparent=False is not supported on this platform"
104+
)
105+
return os.stat(filename).st_blocks * _dev_bsize
88106

89107

90108
def getmtime(filename, /):

Lib/test/test_genericpath.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,53 @@ def test_getsize(self):
114114

115115
create_file(filename, b'Hello World!')
116116
self.assertEqual(self.pathmodule.getsize(filename), 12)
117+
os.remove(filename)
118+
119+
open(filename, 'xb', 0).close()
120+
os.truncate(filename, 512)
121+
self.assertEqual(self.pathmodule.getsize(filename), 512)
122+
123+
@unittest.skipUnless(hasattr(os, 'DEV_BSIZE'),
124+
"os.DEV_BSIZE not available on this platform")
125+
def test_getsize_actual(self):
126+
filename = os_helper.TESTFN
127+
self.addCleanup(os_helper.unlink, filename)
128+
129+
# DEV_BSIZE varies across platforms
130+
if support.is_android:
131+
expected = 8192
132+
elif support.is_wasi:
133+
expected = 0
134+
else:
135+
expected = 4096
136+
137+
create_file(filename, b'Hello')
138+
self.assertEqual(self.pathmodule.getsize(filename, apparent=False), expected)
139+
os.remove(filename)
140+
141+
create_file(filename, b'Hello World!')
142+
self.assertEqual(self.pathmodule.getsize(filename, apparent=False), expected)
143+
os.remove(filename)
144+
145+
# DEV_BSIZE varies across platforms
146+
if support.is_android:
147+
expected = 4096
148+
else:
149+
expected = 0
150+
151+
open(filename, 'xb', 0).close()
152+
os.truncate(filename, 512)
153+
self.assertEqual(self.pathmodule.getsize(filename, apparent=False), expected)
154+
155+
@unittest.skipIf(hasattr(os, 'DEV_BSIZE'),
156+
"os.DEV_BSIZE is available on this platform")
157+
def test_getsize_actual_not_supported(self):
158+
filename = os_helper.TESTFN
159+
self.addCleanup(os_helper.unlink, filename)
160+
161+
create_file(filename, b'Hello')
162+
with self.assertRaises(NotImplementedError):
163+
self.pathmodule.getsize(filename, apparent=False)
117164

118165
def test_filetime(self):
119166
filename = os_helper.TESTFN
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add support for an ``apparent`` option for ``os.get_size`` to allow
2+
retrieving the actual on-disk size of sparse files.

0 commit comments

Comments
 (0)