diff --git a/Doc/library/shutil.rst b/Doc/library/shutil.rst index 1a878d5fd03..88c0eaa8db0 100644 --- a/Doc/library/shutil.rst +++ b/Doc/library/shutil.rst @@ -164,6 +164,14 @@ Directory and files operations If the destination is on the current filesystem, then simply use rename. Otherwise, copy src (with :func:`copy2`) to the dst and then remove src. +.. function:: disk_usage(path) + + Return disk usage statistics about the given path as a namedtuple including + total, used and free space expressed in bytes. + + .. versionadded:: 3.3 + + Availability: Unix, Windows. .. exception:: Error diff --git a/Doc/whatsnew/3.3.rst b/Doc/whatsnew/3.3.rst index 63f8b155c87..a2f512b8bc1 100644 --- a/Doc/whatsnew/3.3.rst +++ b/Doc/whatsnew/3.3.rst @@ -200,7 +200,16 @@ The :class:`~ftplib.FTP_TLS` class now provides a new plaintex. This can be useful to take advantage of firewalls that know how to handle NAT with non-secure FTP without opening fixed ports. -(Patch submitted by Giampaolo Rodolà in :issue:`12139`.) +(Contributed by Giampaolo Rodolà in :issue:`12139`) + + +shutil +------ + +The :mod:`shutil` module has a new :func:`~shutil.disk_usage` providing total, +used and free disk space statistics. + +(Contributed by Giampaolo Rodolà in :issue:`12442`) Optimizations diff --git a/Lib/shutil.py b/Lib/shutil.py index d2e2dc52fc9..0af9fa5607e 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -12,6 +12,7 @@ import fnmatch import collections import errno import tarfile +from collections import namedtuple try: import bz2 @@ -754,3 +755,21 @@ def unpack_archive(filename, extract_dir=None, format=None): func = _UNPACK_FORMATS[format][1] kwargs = dict(_UNPACK_FORMATS[format][2]) func(filename, extract_dir, **kwargs) + +if hasattr(os, "statvfs") or os.name == 'nt': + _ntuple_diskusage = namedtuple('usage', 'total used free') + + def disk_usage(path): + """Return disk usage statistics about the given path as a namedtuple + including total, used and free space expressed in bytes. + """ + if hasattr(os, "statvfs"): + st = os.statvfs(path) + free = (st.f_bavail * st.f_frsize) + total = (st.f_blocks * st.f_frsize) + used = (st.f_blocks - st.f_bfree) * st.f_frsize + else: + import nt + total, free = nt._getdiskusage(path) + used = total - free + return _ntuple_diskusage(total, used, free) diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index ad31f470a03..20e9412aec3 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -728,6 +728,16 @@ class TestShutil(unittest.TestCase): unregister_unpack_format('Boo2') self.assertEqual(get_unpack_formats(), formats) + @unittest.skipUnless(hasattr(shutil, 'disk_usage'), + "disk_usage not available on this platform") + def test_disk_usage(self): + usage = shutil.disk_usage(os.getcwd()) + self.assertTrue(usage.total > 0) + self.assertTrue(usage.used > 0) + self.assertTrue(usage.free >= 0) + self.assertTrue(usage.total >= usage.used) + self.assertTrue(usage.total > usage.free) + class TestMove(unittest.TestCase): diff --git a/Misc/NEWS b/Misc/NEWS index 28d574f2ef7..a0ab6a15f57 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -200,6 +200,9 @@ Core and Builtins Library ------- +- Issue #12442: new shutil.disk_usage function, providing total, used and free + disk space statistics. + - Issue #12451: The XInclude default loader of xml.etree now decodes files from UTF-8 instead of the locale encoding if the encoding is not specified. It now also opens XML files for the parser in binary mode instead of the text mode diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 60c374d0c0e..ba80f5742d9 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -7451,6 +7451,32 @@ posix_statvfs(PyObject *self, PyObject *args) } #endif /* HAVE_STATVFS */ +#ifdef MS_WINDOWS +PyDoc_STRVAR(win32__getdiskusage__doc__, +"_getdiskusage(path) -> (total, free)\n\n\ +Return disk usage statistics about the given path as (total, free) tuple."); + +static PyObject * +win32__getdiskusage(PyObject *self, PyObject *args) +{ + BOOL retval; + ULARGE_INTEGER _, total, free; + LPCTSTR path; + + if (! PyArg_ParseTuple(args, "s", &path)) + return NULL; + + Py_BEGIN_ALLOW_THREADS + retval = GetDiskFreeSpaceEx(path, &_, &total, &free); + Py_END_ALLOW_THREADS + if (retval == 0) + return PyErr_SetFromWindowsErr(0); + + return Py_BuildValue("(LL)", total.QuadPart, free.QuadPart); +} +#endif + + /* This is used for fpathconf(), pathconf(), confstr() and sysconf(). * It maps strings representing configuration variable names to * integer values, allowing those functions to be called with the @@ -9716,6 +9742,7 @@ static PyMethodDef posix_methods[] = { {"_getfinalpathname", posix__getfinalpathname, METH_VARARGS, NULL}, {"_getfileinformation", posix__getfileinformation, METH_VARARGS, NULL}, {"_isdir", posix__isdir, METH_VARARGS, posix__isdir__doc__}, + {"_getdiskusage", win32__getdiskusage, METH_VARARGS, win32__getdiskusage__doc__}, #endif #ifdef HAVE_GETLOADAVG {"getloadavg", posix_getloadavg, METH_NOARGS, posix_getloadavg__doc__},