Skip to content

Commit 98bf58f

Browse files
committed
SF patch #462296: Add attributes to os.stat results; by Nick Mathewson.
This is a big one, touching lots of files. Some of the platforms aren't tested yet. Briefly, this changes the return value of the os/posix functions stat(), fstat(), statvfs(), fstatvfs(), and the time functions localtime(), gmtime(), and strptime() from tuples into pseudo-sequences. When accessed as a sequence, they behave exactly as before. But they also have attributes like st_mtime or tm_year. The stat return value, moreover, has a few platform-specific attributes that are not available through the sequence interface (because everybody expects the sequence to have a fixed length, these couldn't be added there). If your platform's struct stat doesn't define st_blksize, st_blocks or st_rdev, they won't be accessible from Python either. (Still missing is a documentation update.)
1 parent 8dd7ade commit 98bf58f

File tree

11 files changed

+814
-240
lines changed

11 files changed

+814
-240
lines changed

Include/structseq.h

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
2+
/* Tuple object interface */
3+
4+
#ifndef Py_STRUCTSEQ_H
5+
#define Py_STRUCTSEQ_H
6+
#ifdef __cplusplus
7+
extern "C" {
8+
#endif
9+
10+
typedef struct PyStructSequence_Field {
11+
char *name;
12+
char *doc;
13+
} PyStructSequence_Field;
14+
15+
typedef struct PyStructSequence_Desc {
16+
char *name;
17+
char *doc;
18+
struct PyStructSequence_Field *fields;
19+
int n_in_sequence;
20+
} PyStructSequence_Desc;
21+
22+
extern DL_IMPORT(void) PyStructSequence_InitType(PyTypeObject *type,
23+
PyStructSequence_Desc *desc);
24+
25+
extern DL_IMPORT(PyObject *) PyStructSequence_New(PyTypeObject* type);
26+
27+
typedef struct {
28+
PyObject_VAR_HEAD
29+
PyObject *ob_item[1];
30+
} PyStructSequence;
31+
32+
/* Macro, *only* to be used to fill in brand new objects */
33+
#define PyStructSequence_SET_ITEM(op, i, v) \
34+
(((PyStructSequence *)(op))->ob_item[i] = v)
35+
36+
#ifdef __cplusplus
37+
}
38+
#endif
39+
#endif /* !Py_STRUCTSEQ_H */

Lib/test/test_os.py

Lines changed: 119 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111

1212
from test_support import TESTFN, run_unittest
1313

14-
1514
class TemporaryFileTests(unittest.TestCase):
1615
def setUp(self):
1716
self.files = []
@@ -61,10 +60,128 @@ def test_tmpnam(self):
6160
"test_os")
6261
self.check_tempfile(os.tmpnam())
6362

63+
# Test attributes on return values from os.*stat* family.
64+
class StatAttributeTests(unittest.TestCase):
65+
def setUp(self):
66+
os.mkdir(TESTFN)
67+
self.fname = os.path.join(TESTFN, "f1")
68+
f = open(self.fname, 'wb')
69+
f.write("ABC")
70+
f.close()
71+
72+
def tearDown(self):
73+
os.unlink(self.fname)
74+
os.rmdir(TESTFN)
75+
76+
def test_stat_attributes(self):
77+
if not hasattr(os, "stat"):
78+
return
79+
80+
import stat
81+
result = os.stat(self.fname)
82+
83+
# Make sure direct access works
84+
self.assertEquals(result[stat.ST_SIZE], 3)
85+
self.assertEquals(result.st_size, 3)
86+
87+
import sys
88+
89+
# Make sure all the attributes are there
90+
members = dir(result)
91+
for name in dir(stat):
92+
if name[:3] == 'ST_':
93+
attr = name.lower()
94+
self.assertEquals(getattr(result, attr),
95+
result[getattr(stat, name)])
96+
self.assert_(attr in members)
97+
98+
try:
99+
result[200]
100+
self.fail("No exception thrown")
101+
except IndexError:
102+
pass
103+
104+
# Make sure that assignment fails
105+
try:
106+
result.st_mode = 1
107+
self.fail("No exception thrown")
108+
except TypeError:
109+
pass
110+
111+
try:
112+
result.st_rdev = 1
113+
self.fail("No exception thrown")
114+
except TypeError:
115+
pass
116+
117+
try:
118+
result.parrot = 1
119+
self.fail("No exception thrown")
120+
except AttributeError:
121+
pass
122+
123+
# Use the stat_result constructor with a too-short tuple.
124+
try:
125+
result2 = os.stat_result((10,))
126+
self.fail("No exception thrown")
127+
except TypeError:
128+
pass
129+
130+
# Use the constructr with a too-long tuple.
131+
try:
132+
result2 = os.stat_result((0,1,2,3,4,5,6,7,8,9,10,11,12,13,14))
133+
except TypeError:
134+
pass
135+
136+
137+
def test_statvfs_attributes(self):
138+
if not hasattr(os, "statvfs"):
139+
return
140+
141+
import statvfs
142+
result = os.statvfs(self.fname)
143+
144+
# Make sure direct access works
145+
self.assertEquals(result.f_bfree, result[statvfs.F_BFREE])
146+
147+
# Make sure all the attributes are there
148+
members = dir(result)
149+
for name in dir(statvfs):
150+
if name[:2] == 'F_':
151+
attr = name.lower()
152+
self.assertEquals(getattr(result, attr),
153+
result[getattr(statvfs, name)])
154+
self.assert_(attr in members)
155+
156+
# Make sure that assignment really fails
157+
try:
158+
result.f_bfree = 1
159+
self.fail("No exception thrown")
160+
except TypeError:
161+
pass
162+
163+
try:
164+
result.parrot = 1
165+
self.fail("No exception thrown")
166+
except AttributeError:
167+
pass
168+
169+
# Use the constructor with a too-short tuple.
170+
try:
171+
result2 = os.statvfs_result((10,))
172+
self.fail("No exception thrown")
173+
except TypeError:
174+
pass
175+
176+
# Use the constructr with a too-long tuple.
177+
try:
178+
result2 = os.statvfs_result((0,1,2,3,4,5,6,7,8,9,10,11,12,13,14))
179+
except TypeError:
180+
pass
64181

65182
def test_main():
66183
run_unittest(TemporaryFileTests)
67-
184+
run_unittest(StatAttributeTests)
68185

69186
if __name__ == "__main__":
70187
test_main()

Mac/Modules/macmodule.c

Lines changed: 123 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
2525
/* Mac module implementation */
2626

2727
#include "Python.h"
28+
#include "structseq.h"
2829
#include "ceval.h"
2930

3031
#include <stdio.h>
@@ -460,11 +461,119 @@ mac_rmdir(self, args)
460461
return mac_1str(args, rmdir);
461462
}
462463

464+
static char stat_result__doc__[] =
465+
"stat_result: Result from stat or lstat.\n\n\
466+
This object may be accessed either as a tuple of\n\
467+
(mode,ino,dev,nlink,uid,gid,size,atime,mtime,ctime)\n\
468+
or via the attributes st_mode, st_ino, st_dev, st_nlink, st_uid, and so on.\n\
469+
\n\
470+
Macintosh: The fields st_rsize, st_creator, and st_type are available from\n\
471+
os.xstat.\n\
472+
\n\
473+
See os.stat for more information.\n";
474+
475+
#define COMMON_STAT_RESULT_FIELDS \
476+
{ "st_mode", "protection bits" }, \
477+
{ "st_ino", "inode" }, \
478+
{ "st_dev", "device" }, \
479+
{ "st_nlink", "number of hard links" }, \
480+
{ "st_uid", "user ID of owner" }, \
481+
{ "st_gid", "group ID of owner" }, \
482+
{ "st_size", "total size, in bytes" }, \
483+
{ "st_atime", "time of last access" }, \
484+
{ "st_mtime", "time of last modification" }, \
485+
{ "st_ctime", "time of last change" },
486+
487+
488+
489+
static PyStructSequence_Field stat_result_fields[] = {
490+
COMMON_STAT_RESULT_FIELDS
491+
{0}
492+
};
493+
494+
static PyStructSequence_Desc stat_result_desc = {
495+
"stat_result",
496+
stat_result__doc__,
497+
stat_result_fields,
498+
10
499+
};
500+
501+
static PyTypeObject StatResultType;
502+
503+
#ifdef TARGET_API_MAC_OS8
504+
static PyStructSequence_Field xstat_result_fields[] = {
505+
COMMON_XSTAT_RESULT_FIELDS
506+
{ "st_rsize" },
507+
{ "st_creator" },
508+
{ "st_type "},
509+
{0}
510+
};
511+
512+
static PyStructSequence_Desc xstat_result_desc = {
513+
"xstat_result",
514+
stat_result__doc__,
515+
xstat_result_fields,
516+
13
517+
};
518+
519+
static PyTypeObject XStatResultType;
520+
#endif
521+
522+
static PyObject *
523+
_pystat_from_struct_stat(struct stat st, void* _mst)
524+
{
525+
PyObject *v;
526+
527+
#if TARGET_API_MAC_OS8
528+
struct macstat *mst;
529+
530+
if (_mst != NULL)
531+
v = PyStructSequence_New(&XStatResultType);
532+
else
533+
#endif
534+
v = PyStructSequence_New(&StatResultType);
535+
PyStructSequence_SET_ITEM(v, 0, PyInt_FromLong((long)st.st_mode));
536+
PyStructSequence_SET_ITEM(v, 1, PyInt_FromLong((long)st.st_ino));
537+
PyStructSequence_SET_ITEM(v, 2, PyInt_FromLong((long)st.st_dev));
538+
PyStructSequence_SET_ITEM(v, 3, PyInt_FromLong((long)st.st_nlink));
539+
PyStructSequence_SET_ITEM(v, 4, PyInt_FromLong((long)st.st_uid));
540+
PyStructSequence_SET_ITEM(v, 5, PyInt_FromLong((long)st.st_gid));
541+
PyStructSequence_SET_ITEM(v, 6, PyInt_FromLong((long)st.st_size));
542+
PyStructSequence_SET_ITEM(v, 7,
543+
PyFloat_FromDouble((double)st.st_atime));
544+
PyStructSequence_SET_ITEM(v, 8,
545+
PyFloat_FromDouble((double)st.st_mtime));
546+
PyStructSequence_SET_ITEM(v, 9,
547+
PyFloat_FromDouble((double)st.st_ctime));
548+
#if TARGET_API_MAC_OS8
549+
if (_mst != NULL) {
550+
mst = (struct macstat *) _mst;
551+
PyStructSequence_SET_ITEM(v, 10,
552+
PyInt_FromLong((long)mst->st_rsize));
553+
PyStructSequence_SET_ITEM(v, 11,
554+
PyString_FromStringAndSize(mst->st_creator,
555+
4));
556+
PyStructSequence_SET_ITEM(v, 12,
557+
PyString_FromStringAndSize(mst->st_type,
558+
4));
559+
}
560+
#endif
561+
562+
if (PyErr_Occurred()) {
563+
Py_DECREF(v);
564+
return NULL;
565+
}
566+
567+
return v;
568+
}
569+
570+
463571
static PyObject *
464572
mac_stat(self, args)
465573
PyObject *self;
466574
PyObject *args;
467575
{
576+
PyObject *v;
468577
struct stat st;
469578
char *path;
470579
int res;
@@ -475,17 +584,8 @@ mac_stat(self, args)
475584
Py_END_ALLOW_THREADS
476585
if (res != 0)
477586
return mac_error();
478-
return Py_BuildValue("(lllllllddd)",
479-
(long)st.st_mode,
480-
(long)st.st_ino,
481-
(long)st.st_dev,
482-
(long)st.st_nlink,
483-
(long)st.st_uid,
484-
(long)st.st_gid,
485-
(long)st.st_size,
486-
(double)st.st_atime,
487-
(double)st.st_mtime,
488-
(double)st.st_ctime);
587+
588+
return _pystat_from_struct_stat(st, NULL);
489589
}
490590

491591
#ifdef WEHAVE_FSTAT
@@ -504,17 +604,8 @@ mac_fstat(self, args)
504604
Py_END_ALLOW_THREADS
505605
if (res != 0)
506606
return mac_error();
507-
return Py_BuildValue("(lllllllddd)",
508-
(long)st.st_mode,
509-
(long)st.st_ino,
510-
(long)st.st_dev,
511-
(long)st.st_nlink,
512-
(long)st.st_uid,
513-
(long)st.st_gid,
514-
(long)st.st_size,
515-
(double)st.st_atime,
516-
(double)st.st_mtime,
517-
(double)st.st_ctime);
607+
608+
return _pystat_from_struct_stat(st, NULL);
518609
}
519610
#endif /* WEHAVE_FSTAT */
520611

@@ -545,20 +636,8 @@ mac_xstat(self, args)
545636
Py_END_ALLOW_THREADS
546637
if (res != 0)
547638
return mac_error();
548-
return Py_BuildValue("(llllllldddls#s#)",
549-
(long)st.st_mode,
550-
(long)st.st_ino,
551-
(long)st.st_dev,
552-
(long)st.st_nlink,
553-
(long)st.st_uid,
554-
(long)st.st_gid,
555-
(long)st.st_size,
556-
(double)st.st_atime,
557-
(double)st.st_mtime,
558-
(double)st.st_ctime,
559-
(long)mst.st_rsize,
560-
mst.st_creator, 4,
561-
mst.st_type, 4);
639+
640+
return _pystat_from_struct_stat(st, (void*) &mst);
562641
}
563642
#endif
564643

@@ -766,4 +845,12 @@ initmac()
766845
/* Initialize mac.error exception */
767846
MacError = PyErr_NewException("mac.error", NULL, NULL);
768847
PyDict_SetItemString(d, "error", MacError);
848+
849+
PyStructSequence_InitType(&StatResultType, &stat_result_desc);
850+
PyDict_SetItemString(d, "stat_result", (PyObject*) &StatResultType);
851+
852+
#if TARGET_API_MAC_OS8
853+
PyStructSequence_InitType(&XStatResultType, &xstat_result_desc);
854+
PyDict_SetItemString(d, "xstat_result", (PyObject*) &XStatResultType);
855+
#endif
769856
}

Makefile.pre.in

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,7 @@ OBJECT_OBJS= \
263263
Objects/rangeobject.o \
264264
Objects/sliceobject.o \
265265
Objects/stringobject.o \
266+
Objects/structseq.o \
266267
Objects/tupleobject.o \
267268
Objects/typeobject.o \
268269
Objects/weakrefobject.o \
@@ -465,6 +466,7 @@ PYTHON_HEADERS= \
465466
Include/rangeobject.h \
466467
Include/sliceobject.h \
467468
Include/stringobject.h \
469+
Include/structseq.h \
468470
Include/structmember.h \
469471
Include/symtable.h \
470472
Include/sysmodule.h \

0 commit comments

Comments
 (0)