Skip to content

Commit 63f277b

Browse files
committed
Issue python#21741: Add st_file_attributes to os.stat_result on Windows.
Patch by Ben Hoyt.
1 parent 6ef1202 commit 63f277b

File tree

10 files changed

+174
-1
lines changed

10 files changed

+174
-1
lines changed

Doc/library/os.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1905,6 +1905,11 @@ features:
19051905
* :attr:`st_creator`
19061906
* :attr:`st_type`
19071907

1908+
On Windows systems, the following attribute is also available:
1909+
1910+
* :attr:`st_file_attributes` - Windows file attribute bits (see the
1911+
``FILE_ATTRIBUTE_*`` constants in the :mod:`stat` module)
1912+
19081913
.. note::
19091914

19101915
The exact meaning and resolution of the :attr:`st_atime`,
@@ -1958,6 +1963,9 @@ features:
19581963
and the :attr:`st_atime_ns`, :attr:`st_mtime_ns`,
19591964
and :attr:`st_ctime_ns` members.
19601965

1966+
.. versionadded:: 3.5
1967+
Added the :attr:`st_file_attributes` member on Windows.
1968+
19611969

19621970
.. function:: stat_float_times([newvalue])
19631971

Doc/library/stat.rst

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ Example::
126126
if __name__ == '__main__':
127127
walktree(sys.argv[1], visitfile)
128128

129-
An additional utility function is provided to covert a file's mode in a human
129+
An additional utility function is provided to convert a file's mode in a human
130130
readable string:
131131

132132
.. function:: filemode(mode)
@@ -399,3 +399,29 @@ The following flags can be used in the *flags* argument of :func:`os.chflags`:
399399
The file is a snapshot file.
400400

401401
See the \*BSD or Mac OS systems man page :manpage:`chflags(2)` for more information.
402+
403+
On Windows, the following file attribute constants are available for use when
404+
testing bits in the ``st_file_attributes`` member returned by :func:`os.stat`.
405+
See the `Windows API documentation
406+
<http://msdn.microsoft.com/en-us/library/windows/desktop/gg258117.aspx>`_
407+
for more detail on the meaning of these constants.
408+
409+
.. data:: FILE_ATTRIBUTE_ARCHIVE
410+
FILE_ATTRIBUTE_COMPRESSED
411+
FILE_ATTRIBUTE_DEVICE
412+
FILE_ATTRIBUTE_DIRECTORY
413+
FILE_ATTRIBUTE_ENCRYPTED
414+
FILE_ATTRIBUTE_HIDDEN
415+
FILE_ATTRIBUTE_INTEGRITY_STREAM
416+
FILE_ATTRIBUTE_NORMAL
417+
FILE_ATTRIBUTE_NOT_CONTENT_INDEXED
418+
FILE_ATTRIBUTE_NO_SCRUB_DATA
419+
FILE_ATTRIBUTE_OFFLINE
420+
FILE_ATTRIBUTE_READONLY
421+
FILE_ATTRIBUTE_REPARSE_POINT
422+
FILE_ATTRIBUTE_SPARSE_FILE
423+
FILE_ATTRIBUTE_SYSTEM
424+
FILE_ATTRIBUTE_TEMPORARY
425+
FILE_ATTRIBUTE_VIRTUAL
426+
427+
.. versionadded:: 3.5

Doc/whatsnew/3.5.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,15 @@ ipaddress
176176
network objects from existing addresses (contributed by Peter Moody
177177
and Antoine Pitrou in :issue:`16531`).
178178

179+
os
180+
--
181+
182+
* :class:`os.stat_result` now has a ``st_file_attributes`` field on Windows,
183+
containing the ``dwFileAttributes`` member of the
184+
``BY_HANDLE_FILE_INFORMATION`` structure returned by
185+
``GetFileInformationByHandle()`` (contributed by Ben Hoyt in
186+
:issue:`21719`).
187+
179188
shutil
180189
------
181190

Lib/stat.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,29 @@ def filemode(mode):
148148
perm.append("-")
149149
return "".join(perm)
150150

151+
152+
# Windows FILE_ATTRIBUTE constants for interpreting os.stat()'s
153+
# "st_file_attributes" member
154+
155+
FILE_ATTRIBUTE_ARCHIVE = 32
156+
FILE_ATTRIBUTE_COMPRESSED = 2048
157+
FILE_ATTRIBUTE_DEVICE = 64
158+
FILE_ATTRIBUTE_DIRECTORY = 16
159+
FILE_ATTRIBUTE_ENCRYPTED = 16384
160+
FILE_ATTRIBUTE_HIDDEN = 2
161+
FILE_ATTRIBUTE_INTEGRITY_STREAM = 32768
162+
FILE_ATTRIBUTE_NORMAL = 128
163+
FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 8192
164+
FILE_ATTRIBUTE_NO_SCRUB_DATA = 131072
165+
FILE_ATTRIBUTE_OFFLINE = 4096
166+
FILE_ATTRIBUTE_READONLY = 1
167+
FILE_ATTRIBUTE_REPARSE_POINT = 1024
168+
FILE_ATTRIBUTE_SPARSE_FILE = 512
169+
FILE_ATTRIBUTE_SYSTEM = 4
170+
FILE_ATTRIBUTE_TEMPORARY = 256
171+
FILE_ATTRIBUTE_VIRTUAL = 65536
172+
173+
151174
# If available, use C implementation
152175
try:
153176
from _stat import *

Lib/test/test_os.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,28 @@ def test_15261(self):
530530
os.stat(r)
531531
self.assertEqual(ctx.exception.errno, errno.EBADF)
532532

533+
def check_file_attributes(self, result):
534+
self.assertTrue(hasattr(result, 'st_file_attributes'))
535+
self.assertTrue(isinstance(result.st_file_attributes, int))
536+
self.assertTrue(0 <= result.st_file_attributes <= 0xFFFFFFFF)
537+
538+
@unittest.skipUnless(sys.platform == "win32",
539+
"st_file_attributes is Win32 specific")
540+
def test_file_attributes(self):
541+
# test file st_file_attributes (FILE_ATTRIBUTE_DIRECTORY not set)
542+
result = os.stat(self.fname)
543+
self.check_file_attributes(result)
544+
self.assertEqual(
545+
result.st_file_attributes & stat.FILE_ATTRIBUTE_DIRECTORY,
546+
0)
547+
548+
# test directory st_file_attributes (FILE_ATTRIBUTE_DIRECTORY set)
549+
result = os.stat(support.TESTFN)
550+
self.check_file_attributes(result)
551+
self.assertEqual(
552+
result.st_file_attributes & stat.FILE_ATTRIBUTE_DIRECTORY,
553+
stat.FILE_ATTRIBUTE_DIRECTORY)
554+
533555
from test import mapping_tests
534556

535557
class EnvironTests(mapping_tests.BasicTestMappingProtocol):

Lib/test/test_stat.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import unittest
22
import os
3+
import sys
34
from test.support import TESTFN, import_fresh_module
45

56
c_stat = import_fresh_module('stat', fresh=['_stat'])
@@ -52,6 +53,26 @@ class TestFilemode:
5253
'S_IWOTH': 0o002,
5354
'S_IXOTH': 0o001}
5455

56+
# defined by the Windows API documentation
57+
file_attributes = {
58+
'FILE_ATTRIBUTE_ARCHIVE': 32,
59+
'FILE_ATTRIBUTE_COMPRESSED': 2048,
60+
'FILE_ATTRIBUTE_DEVICE': 64,
61+
'FILE_ATTRIBUTE_DIRECTORY': 16,
62+
'FILE_ATTRIBUTE_ENCRYPTED': 16384,
63+
'FILE_ATTRIBUTE_HIDDEN': 2,
64+
'FILE_ATTRIBUTE_INTEGRITY_STREAM': 32768,
65+
'FILE_ATTRIBUTE_NORMAL': 128,
66+
'FILE_ATTRIBUTE_NOT_CONTENT_INDEXED': 8192,
67+
'FILE_ATTRIBUTE_NO_SCRUB_DATA': 131072,
68+
'FILE_ATTRIBUTE_OFFLINE': 4096,
69+
'FILE_ATTRIBUTE_READONLY': 1,
70+
'FILE_ATTRIBUTE_REPARSE_POINT': 1024,
71+
'FILE_ATTRIBUTE_SPARSE_FILE': 512,
72+
'FILE_ATTRIBUTE_SYSTEM': 4,
73+
'FILE_ATTRIBUTE_TEMPORARY': 256,
74+
'FILE_ATTRIBUTE_VIRTUAL': 65536}
75+
5576
def setUp(self):
5677
try:
5778
os.remove(TESTFN)
@@ -185,6 +206,14 @@ def test_module_attributes(self):
185206
self.assertTrue(callable(func))
186207
self.assertEqual(func(0), 0)
187208

209+
@unittest.skipUnless(sys.platform == "win32",
210+
"FILE_ATTRIBUTE_* constants are Win32 specific")
211+
def test_file_attribute_constants(self):
212+
for key, value in sorted(self.file_attributes.items()):
213+
self.assertTrue(hasattr(self.statmod, key), key)
214+
modvalue = getattr(self.statmod, key)
215+
self.assertEqual(value, modvalue, key)
216+
188217

189218
class TestFilemodeCStat(TestFilemode, unittest.TestCase):
190219
statmod = c_stat

Misc/ACKS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -580,6 +580,7 @@ Alan Hourihane
580580
Ken Howard
581581
Brad Howes
582582
Mike Hoy
583+
Ben Hoyt
583584
Chih-Hao Huang
584585
Christian Hudon
585586
Lawrence Hudson

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,9 @@ Core and Builtins
103103
Library
104104
-------
105105

106+
- Issue #21719: Added the ``st_file_attributes`` field to os.stat_result on
107+
Windows.
108+
106109
- Issue #21722: The distutils "upload" command now exits with a non-zero
107110
return code when uploading fails. Patch by Martin Dengler.
108111

Modules/_stat.c

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,21 @@ extern "C" {
2727
#endif /* HAVE_SYS_STAT_H */
2828

2929
#ifdef MS_WINDOWS
30+
#include <windows.h>
3031
typedef unsigned short mode_t;
32+
33+
/* FILE_ATTRIBUTE_INTEGRITY_STREAM and FILE_ATTRIBUTE_NO_SCRUB_DATA
34+
are not present in VC2010, so define them manually */
35+
#ifndef FILE_ATTRIBUTE_INTEGRITY_STREAM
36+
# define FILE_ATTRIBUTE_INTEGRITY_STREAM 0x8000
37+
#endif
38+
39+
#ifndef FILE_ATTRIBUTE_NO_SCRUB_DATA
40+
# define FILE_ATTRIBUTE_NO_SCRUB_DATA 0x20000
3141
#endif
3242

43+
#endif /* MS_WINDOWS */
44+
3345
/* From Python's stat.py */
3446
#ifndef S_IMODE
3547
# define S_IMODE 07777
@@ -473,6 +485,10 @@ ST_SIZE\n\
473485
ST_ATIME\n\
474486
ST_MTIME\n\
475487
ST_CTIME\n\
488+
\n"
489+
490+
"FILE_ATTRIBUTE_*: Windows file attribute constants\n\
491+
(only present on Windows)\n\
476492
");
477493

478494

@@ -555,6 +571,26 @@ PyInit__stat(void)
555571
if (PyModule_AddIntConstant(m, "ST_MTIME", 8)) return NULL;
556572
if (PyModule_AddIntConstant(m, "ST_CTIME", 9)) return NULL;
557573

574+
#ifdef MS_WINDOWS
575+
if (PyModule_AddIntMacro(m, FILE_ATTRIBUTE_ARCHIVE)) return NULL;
576+
if (PyModule_AddIntMacro(m, FILE_ATTRIBUTE_COMPRESSED)) return NULL;
577+
if (PyModule_AddIntMacro(m, FILE_ATTRIBUTE_DEVICE)) return NULL;
578+
if (PyModule_AddIntMacro(m, FILE_ATTRIBUTE_DIRECTORY)) return NULL;
579+
if (PyModule_AddIntMacro(m, FILE_ATTRIBUTE_ENCRYPTED)) return NULL;
580+
if (PyModule_AddIntMacro(m, FILE_ATTRIBUTE_HIDDEN)) return NULL;
581+
if (PyModule_AddIntMacro(m, FILE_ATTRIBUTE_INTEGRITY_STREAM)) return NULL;
582+
if (PyModule_AddIntMacro(m, FILE_ATTRIBUTE_NORMAL)) return NULL;
583+
if (PyModule_AddIntMacro(m, FILE_ATTRIBUTE_NOT_CONTENT_INDEXED)) return NULL;
584+
if (PyModule_AddIntMacro(m, FILE_ATTRIBUTE_NO_SCRUB_DATA)) return NULL;
585+
if (PyModule_AddIntMacro(m, FILE_ATTRIBUTE_OFFLINE)) return NULL;
586+
if (PyModule_AddIntMacro(m, FILE_ATTRIBUTE_READONLY)) return NULL;
587+
if (PyModule_AddIntMacro(m, FILE_ATTRIBUTE_REPARSE_POINT)) return NULL;
588+
if (PyModule_AddIntMacro(m, FILE_ATTRIBUTE_SPARSE_FILE)) return NULL;
589+
if (PyModule_AddIntMacro(m, FILE_ATTRIBUTE_SYSTEM)) return NULL;
590+
if (PyModule_AddIntMacro(m, FILE_ATTRIBUTE_TEMPORARY)) return NULL;
591+
if (PyModule_AddIntMacro(m, FILE_ATTRIBUTE_VIRTUAL)) return NULL;
592+
#endif
593+
558594
return m;
559595
}
560596

Modules/posixmodule.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1417,6 +1417,7 @@ win32_wchdir(LPCWSTR path)
14171417
Therefore, we implement our own stat, based on the Win32 API directly.
14181418
*/
14191419
#define HAVE_STAT_NSEC 1
1420+
#define HAVE_STRUCT_STAT_ST_FILE_ATTRIBUTES 1
14201421

14211422
struct win32_stat{
14221423
unsigned long st_dev;
@@ -1433,6 +1434,7 @@ struct win32_stat{
14331434
int st_mtime_nsec;
14341435
time_t st_ctime;
14351436
int st_ctime_nsec;
1437+
unsigned long st_file_attributes;
14361438
};
14371439

14381440
static __int64 secs_between_epochs = 11644473600; /* Seconds between 1.1.1601 and 1.1.1970 */
@@ -1497,6 +1499,7 @@ attribute_data_to_stat(BY_HANDLE_FILE_INFORMATION *info, ULONG reparse_tag, stru
14971499
/* now set the bits that make this a symlink */
14981500
result->st_mode |= S_IFLNK;
14991501
}
1502+
result->st_file_attributes = info->dwFileAttributes;
15001503

15011504
return 0;
15021505
}
@@ -1960,6 +1963,9 @@ static PyStructSequence_Field stat_result_fields[] = {
19601963
#endif
19611964
#ifdef HAVE_STRUCT_STAT_ST_BIRTHTIME
19621965
{"st_birthtime", "time of creation"},
1966+
#endif
1967+
#ifdef HAVE_STRUCT_STAT_ST_FILE_ATTRIBUTES
1968+
{"st_file_attributes", "Windows file attribute bits"},
19631969
#endif
19641970
{0}
19651971
};
@@ -2000,6 +2006,12 @@ static PyStructSequence_Field stat_result_fields[] = {
20002006
#define ST_BIRTHTIME_IDX ST_GEN_IDX
20012007
#endif
20022008

2009+
#ifdef HAVE_STRUCT_STAT_ST_FILE_ATTRIBUTES
2010+
#define ST_FILE_ATTRIBUTES_IDX (ST_BIRTHTIME_IDX+1)
2011+
#else
2012+
#define ST_FILE_ATTRIBUTES_IDX ST_BIRTHTIME_IDX
2013+
#endif
2014+
20032015
static PyStructSequence_Desc stat_result_desc = {
20042016
"stat_result", /* name */
20052017
stat_result__doc__, /* doc */
@@ -2267,6 +2279,10 @@ _pystat_fromstructstat(STRUCT_STAT *st)
22672279
PyStructSequence_SET_ITEM(v, ST_FLAGS_IDX,
22682280
PyLong_FromLong((long)st->st_flags));
22692281
#endif
2282+
#ifdef HAVE_STRUCT_STAT_ST_FILE_ATTRIBUTES
2283+
PyStructSequence_SET_ITEM(v, ST_FILE_ATTRIBUTES_IDX,
2284+
PyLong_FromUnsignedLong(st->st_file_attributes));
2285+
#endif
22702286

22712287
if (PyErr_Occurred()) {
22722288
Py_DECREF(v);

0 commit comments

Comments
 (0)