diff -r ff5abf93db80 Doc/library/shutil.rst --- a/Doc/library/shutil.rst Wed Jun 29 18:12:33 2011 +0200 +++ b/Doc/library/shutil.rst Wed Jun 29 18:33:35 2011 +0200 @@ -164,6 +164,14 @@ 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 plus the percentage usage. + + .. versionadded:: 3.3 + + Availability: Unix, Windows. .. exception:: Error diff -r ff5abf93db80 Lib/shutil.py --- a/Lib/shutil.py Wed Jun 29 18:12:33 2011 +0200 +++ b/Lib/shutil.py Wed Jun 29 18:33:35 2011 +0200 @@ -12,6 +12,7 @@ import collections import errno import tarfile +from collections import namedtuple try: import bz2 @@ -754,3 +755,29 @@ 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 percent') + + def disk_usage(path): + """Return disk usage statistics about the given path as a namedtuple + including total, used and free space expressed in bytes plus the + percentage usage. + """ + 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 + # NB: on UNIX the percentage is -5% than what shown by df + # due to reserved blocks that we are currently not + # considering: http://goo.gl/sWGbH + try: + percent = (float(used) / total) * 100 + except ZeroDivisionError: + percent = 0 + return _ntuple_diskusage(total, used, free, round(percent, 1)) diff -r ff5abf93db80 Lib/test/test_shutil.py --- a/Lib/test/test_shutil.py Wed Jun 29 18:12:33 2011 +0200 +++ b/Lib/test/test_shutil.py Wed Jun 29 18:33:35 2011 +0200 @@ -729,6 +729,16 @@ unregister_unpack_format('Boo2') self.assertEqual(get_unpack_formats(), formats) + @unittest.skipUnless(os.name in ('nt', 'posix'), "disk_usage not available") + 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) + self.assertTrue(0 <= usage.percent <= 100) + class TestMove(unittest.TestCase): diff -r ff5abf93db80 Modules/posixmodule.c --- a/Modules/posixmodule.c Wed Jun 29 18:12:33 2011 +0200 +++ b/Modules/posixmodule.c Wed Jun 29 18:33:35 2011 +0200 @@ -7451,6 +7451,30 @@ } #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; + + retval = GetDiskFreeSpaceEx(path, &_, &total, &free); + 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 +9740,7 @@ {"_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__},