Index: Doc/lib/libmarshal.tex =================================================================== RCS file: /cvsroot/python/python/dist/src/Doc/lib/libmarshal.tex,v retrieving revision 1.25 diff -u -r1.25 libmarshal.tex --- Doc/lib/libmarshal.tex 20 Dec 2004 12:25:56 -0000 1.25 +++ Doc/lib/libmarshal.tex 4 Jun 2005 17:06:56 -0000 @@ -57,17 +57,16 @@ all but the least-significant 32 bits of the value were lost, and a warning message was printed.) -There are functions that read/write files as well as functions -operating on strings. +There are functions that read and write file-like objects, as well as +functions that operate on strings. The module defines these functions: -\begin{funcdesc}{dump}{value, file\optional{, version}} - Write the value on the open file. The value must be a supported - type. The file must be an open file object such as - \code{sys.stdout} or returned by \function{open()} or - \function{posix.popen()}. It must be opened in binary mode - (\code{'wb'} or \code{'w+b'}). +\begin{funcdesc}{dump}{value, destination\optional{, version}} + Write the value to the destination. The value must be a supported type. + The destination may be any object which supports a \method{write} method. + If it is a file, it must be opened in binary mode (\code{'wb'} or + \code{'w+b'}). If the value has (or contains an object that has) an unsupported type, a \exception{ValueError} exception is raised --- but garbage data @@ -76,17 +75,24 @@ \versionadded[The \var{version} argument indicates the data format that \code{dump} should use (see below)]{2.4} + + \versionchanged[Support for arbitrary destination objects with + \method{write} methods was added.]{2.5} \end{funcdesc} -\begin{funcdesc}{load}{file} - Read one value from the open file and return it. If no valid value - is read, raise \exception{EOFError}, \exception{ValueError} or - \exception{TypeError}. The file must be an open file object opened - in binary mode (\code{'rb'} or \code{'r+b'}). +\begin{funcdesc}{load}{source} + Read one value from the source object and return it. The source may be + any object with a \method{read} method. If the source is a file, it must + be opened in binary mode (\code{'rb'} or \code{'r+b'}). If no valid value + is read, \exception{EOFError}, \exception{ValueError} or + \exception{TypeError} is raised. \warning{If an object containing an unsupported type was marshalled with \function{dump()}, \function{load()} will substitute \code{None} for the unmarshallable type.} + + \versionchanged[Support for arbitrary source objects with \method{read} + methods was added.]{2.5} \end{funcdesc} \begin{funcdesc}{dumps}{value\optional{, version}} Index: Lib/test/test_marshal.py =================================================================== RCS file: /cvsroot/python/python/dist/src/Lib/test/test_marshal.py,v retrieving revision 1.11 diff -u -r1.11 test_marshal.py --- Lib/test/test_marshal.py 4 Jun 2005 12:55:32 -0000 1.11 +++ Lib/test/test_marshal.py 4 Jun 2005 17:06:56 -0000 @@ -6,21 +6,40 @@ import sys import unittest import os +import StringIO -class IntTestCase(unittest.TestCase): +class MarshalTestCase(unittest.TestCase): + def check_non_file(self, val, version=2): + sio = StringIO.StringIO() + marshal.dump(val, sio, version) + sio.seek(0) + new = marshal.load(sio) + self.assertEqual(val, new) + self.assertEqual(type(val), type(new)) + + def check_file(self, val, version=2): + marshal.dump(val, open(test_support.TESTFN, "wb"), version) + new = marshal.load(open(test_support.TESTFN, "rb")) + self.assertEqual(val, new) + self.assertEqual(type(val), type(new)) + os.unlink(test_support.TESTFN) + + def check_string(self, val, version=2): + s = marshal.dumps(val, version) + new = marshal.loads(s) + self.assertEqual(val, new) + self.assertEqual(type(val), type(new)) + +class IntTestCase(MarshalTestCase): def test_ints(self): # Test the full range of Python ints. n = sys.maxint while n: for expected in (-n, n): - s = marshal.dumps(expected) - got = marshal.loads(s) - self.assertEqual(expected, got) - marshal.dump(expected, file(test_support.TESTFN, "wb")) - got = marshal.load(file(test_support.TESTFN, "rb")) - self.assertEqual(expected, got) + self.check_string(expected) + self.check_file(expected) + self.check_non_file(expected) n = n >> 1 - os.unlink(test_support.TESTFN) def test_int64(self): # Simulate int marshaling on a 64-bit box. This is most interesting if @@ -48,15 +67,11 @@ def test_bool(self): for b in (True, False): - new = marshal.loads(marshal.dumps(b)) - self.assertEqual(b, new) - self.assertEqual(type(b), type(new)) - marshal.dump(b, file(test_support.TESTFN, "wb")) - new = marshal.load(file(test_support.TESTFN, "rb")) - self.assertEqual(b, new) - self.assertEqual(type(b), type(new)) + self.check_string(b) + self.check_file(b) + self.check_non_file(b) -class FloatTestCase(unittest.TestCase): +class FloatTestCase(MarshalTestCase): def test_floats(self): # Test a few floats small = 1e-25 @@ -64,68 +79,43 @@ while n > small: for expected in (-n, n): f = float(expected) - s = marshal.dumps(f) - got = marshal.loads(s) - self.assertEqual(f, got) - marshal.dump(f, file(test_support.TESTFN, "wb")) - got = marshal.load(file(test_support.TESTFN, "rb")) - self.assertEqual(f, got) + self.check_string(f) + self.check_file(f) + self.check_non_file(f) n /= 123.4567 f = 0.0 - s = marshal.dumps(f, 2) - got = marshal.loads(s) - self.assertEqual(f, got) + self.check_string(f, 2) # and with version <= 1 (floats marshalled differently then) - s = marshal.dumps(f, 1) - got = marshal.loads(s) - self.assertEqual(f, got) + self.check_string(f, 1) n = sys.maxint * 3.7e-250 while n < small: for expected in (-n, n): f = float(expected) - s = marshal.dumps(f) - got = marshal.loads(s) - self.assertEqual(f, got) + self.check_string(f) + self.check_string(f, 1) - s = marshal.dumps(f, 1) - got = marshal.loads(s) - self.assertEqual(f, got) + self.check_file(f) + self.check_file(f, 1) - marshal.dump(f, file(test_support.TESTFN, "wb")) - got = marshal.load(file(test_support.TESTFN, "rb")) - self.assertEqual(f, got) - - marshal.dump(f, file(test_support.TESTFN, "wb"), 1) - got = marshal.load(file(test_support.TESTFN, "rb")) - self.assertEqual(f, got) + self.check_non_file(f) + self.check_non_file(f, 1) n *= 123.4567 - os.unlink(test_support.TESTFN) -class StringTestCase(unittest.TestCase): +class StringTestCase(MarshalTestCase): def test_unicode(self): for s in [u"", u"Andrč Previn", u"abc", u" "*10000]: - new = marshal.loads(marshal.dumps(s)) - self.assertEqual(s, new) - self.assertEqual(type(s), type(new)) - marshal.dump(s, file(test_support.TESTFN, "wb")) - new = marshal.load(file(test_support.TESTFN, "rb")) - self.assertEqual(s, new) - self.assertEqual(type(s), type(new)) - os.unlink(test_support.TESTFN) + self.check_string(s) + self.check_file(s) + self.check_non_file(s) def test_string(self): for s in ["", "Andrč Previn", "abc", " "*10000]: - new = marshal.loads(marshal.dumps(s)) - self.assertEqual(s, new) - self.assertEqual(type(s), type(new)) - marshal.dump(s, file(test_support.TESTFN, "wb")) - new = marshal.load(file(test_support.TESTFN, "rb")) - self.assertEqual(s, new) - self.assertEqual(type(s), type(new)) - os.unlink(test_support.TESTFN) + self.check_string(s) + self.check_file(s) + self.check_non_file(s) def test_buffer(self): for s in ["", "Andrč Previn", "abc", " "*10000]: @@ -135,20 +125,25 @@ marshal.dump(b, file(test_support.TESTFN, "wb")) new = marshal.load(file(test_support.TESTFN, "rb")) self.assertEqual(s, new) + sio = StringIO.StringIO() + marshal.dump(b, sio) + sio.seek(0) + new = marshal.load(sio) + self.assertEqual(s, new) os.unlink(test_support.TESTFN) -class ExceptionTestCase(unittest.TestCase): +class ExceptionTestCase(MarshalTestCase): def test_exceptions(self): new = marshal.loads(marshal.dumps(StopIteration)) self.assertEqual(StopIteration, new) -class CodeTestCase(unittest.TestCase): +class CodeTestCase(MarshalTestCase): def test_code(self): co = ExceptionTestCase.test_exceptions.func_code new = marshal.loads(marshal.dumps(co)) self.assertEqual(co, new) -class ContainerTestCase(unittest.TestCase): +class ContainerTestCase(MarshalTestCase): d = {'astring': 'foo@bar.baz.spam', 'afloat': 7283.43, 'anint': 2**20, @@ -159,30 +154,21 @@ 'aunicode': u"Andrč Previn" } def test_dict(self): - new = marshal.loads(marshal.dumps(self.d)) - self.assertEqual(self.d, new) - marshal.dump(self.d, file(test_support.TESTFN, "wb")) - new = marshal.load(file(test_support.TESTFN, "rb")) - self.assertEqual(self.d, new) - os.unlink(test_support.TESTFN) + self.check_string(self.d) + self.check_file(self.d) + self.check_non_file(self.d) def test_list(self): lst = self.d.items() - new = marshal.loads(marshal.dumps(lst)) - self.assertEqual(lst, new) - marshal.dump(lst, file(test_support.TESTFN, "wb")) - new = marshal.load(file(test_support.TESTFN, "rb")) - self.assertEqual(lst, new) - os.unlink(test_support.TESTFN) + self.check_string(lst) + self.check_file(lst) + self.check_non_file(lst) def test_tuple(self): t = tuple(self.d.keys()) - new = marshal.loads(marshal.dumps(t)) - self.assertEqual(t, new) - marshal.dump(t, file(test_support.TESTFN, "wb")) - new = marshal.load(file(test_support.TESTFN, "rb")) - self.assertEqual(t, new) - os.unlink(test_support.TESTFN) + self.check_string(t) + self.check_file(t) + self.check_non_file(t) def test_sets(self): for constructor in (set, frozenset): @@ -194,7 +180,12 @@ marshal.dump(t, file(test_support.TESTFN, "wb")) new = marshal.load(file(test_support.TESTFN, "rb")) self.assertEqual(t, new) - os.unlink(test_support.TESTFN) + sio = StringIO.StringIO() + marshal.dump(t, sio) + sio.seek(0) + new = marshal.load(sio) + self.assertEqual(t, new) + os.unlink(test_support.TESTFN) class BugsTestCase(unittest.TestCase): def test_bug_5888452(self): Index: Python/marshal.c =================================================================== RCS file: /cvsroot/python/python/dist/src/Python/marshal.c,v retrieving revision 1.84 diff -u -r1.84 marshal.c --- Python/marshal.c 3 Jun 2005 15:17:16 -0000 1.84 +++ Python/marshal.c 4 Jun 2005 17:06:56 -0000 @@ -41,7 +41,7 @@ #define TYPE_FROZENSET '>' typedef struct { - FILE *fp; + PyObject *fp; int error; int depth; /* If fp == NULL, the following are valid: */ @@ -52,10 +52,6 @@ int version; } WFILE; -#define w_byte(c, p) if (((p)->fp)) putc((c), (p)->fp); \ - else if ((p)->ptr != (p)->end) *(p)->ptr++ = (c); \ - else w_more(c, p) - static void w_more(int c, WFILE *p) { @@ -76,16 +72,30 @@ } static void +w_byte(char c, WFILE *p) { + if (p->fp) { + PyObject *result = NULL; + char s[2]; + s[0] = c; + s[1] = NULL; + result = PyObject_CallMethod(p->fp, "write", "s#", s, 1); + if (result != NULL) { + Py_DECREF(result); + } + } else if (p->ptr != p->end) { + *p->ptr++ = c; + } else { + w_more(c, p); + } + return; +} + +static void w_string(char *s, int n, WFILE *p) { - if (p->fp != NULL) { - fwrite(s, 1, n, p->fp); - } - else { - while (--n >= 0) { - w_byte(*s, p); - s++; - } + while (--n >= 0) { + w_byte(*s, p); + s++; } } @@ -364,38 +374,70 @@ PyMarshal_WriteLongToFile(long x, FILE *fp, int version) { WFILE wf; - wf.fp = fp; + wf.fp = PyFile_FromFile(fp, "", "wb", NULL); + if (wf.fp == NULL) { + return; + } wf.error = 0; wf.depth = 0; wf.strings = NULL; wf.version = version; w_long(x, &wf); + Py_DECREF(wf.fp); } void PyMarshal_WriteObjectToFile(PyObject *x, FILE *fp, int version) { WFILE wf; - wf.fp = fp; + wf.fp = PyFile_FromFile(fp, "", "wb", NULL); + if (wf.fp == NULL) { + return; + } wf.error = 0; wf.depth = 0; wf.strings = (version > 0) ? PyDict_New() : NULL; wf.version = version; w_object(x, &wf); Py_XDECREF(wf.strings); + Py_DECREF(wf.fp); } typedef WFILE RFILE; /* Same struct with different invariants */ #define rs_byte(p) (((p)->ptr != (p)->end) ? (unsigned char)*(p)->ptr++ : EOF) -#define r_byte(p) ((p)->fp ? getc((p)->fp) : rs_byte(p)) +static unsigned char +r_byte(RFILE *p) { + if (p->fp) { + PyObject *result = NULL; + char c; + result = PyObject_CallMethod(p->fp, "read", "i", 1); + if (result == NULL) { + return EOF; + } + c = *PyString_AsString(result); + Py_DECREF(result); + return c; + } else { + return rs_byte(p); + } +} static int r_string(char *s, int n, RFILE *p) { - if (p->fp != NULL) - return fread(s, 1, n, p->fp); + if (p->fp != NULL) { + PyObject *result = PyObject_CallMethod(p->fp, "read", "i", n); + int sz; + if (result == NULL) { + return 0; + } + sz = PyString_GET_SIZE(result); + memcpy(s, PyString_AsString(result), sz); + Py_DECREF(result); + return sz; + } if (p->end - p->ptr < n) n = p->end - p->ptr; memcpy(s, p->ptr, n); @@ -418,19 +460,10 @@ r_long(RFILE *p) { register long x; - register FILE *fp = p->fp; - if (fp) { - x = getc(fp); - x |= (long)getc(fp) << 8; - x |= (long)getc(fp) << 16; - x |= (long)getc(fp) << 24; - } - else { - x = rs_byte(p); - x |= (long)rs_byte(p) << 8; - x |= (long)rs_byte(p) << 16; - x |= (long)rs_byte(p) << 24; - } + x = r_byte(p); + x |= (long)r_byte(p) << 8; + x |= (long)r_byte(p) << 16; + x |= (long)r_byte(p) << 24; #if SIZEOF_LONG > 4 /* Sign extension for 64-bit machines */ x |= -(x & 0x80000000L); @@ -882,18 +915,30 @@ PyMarshal_ReadShortFromFile(FILE *fp) { RFILE rf; - rf.fp = fp; + int result; + rf.fp = PyFile_FromFile(fp, "", "rb", NULL); + if (rf.fp == NULL) { + return EOF; + } rf.strings = NULL; - return r_short(&rf); + result = r_short(&rf); + Py_DECREF(rf.fp); + return result; } long PyMarshal_ReadLongFromFile(FILE *fp) { RFILE rf; - rf.fp = fp; + long result; + rf.fp = PyFile_FromFile(fp, "", "rb", NULL); + if (rf.fp == NULL) { + return EOF; + } rf.strings = NULL; - return r_long(&rf); + result = r_long(&rf); + Py_DECREF(rf.fp); + return result; } #ifdef HAVE_FSTAT @@ -959,11 +1004,17 @@ PyMarshal_ReadObjectFromFile(FILE *fp) { RFILE rf; - PyObject *result; - rf.fp = fp; + PyObject *result = NULL; + rf.fp = PyFile_FromFile(fp, "", "rb", NULL); + if (rf.fp == NULL) { + return NULL; + } rf.strings = PyList_New(0); - result = r_object(&rf); - Py_DECREF(rf.strings); + if (rf.strings != NULL) { + result = r_object(&rf); + Py_DECREF(rf.strings); + } + Py_DECREF(rf.fp); return result; } @@ -1022,12 +1073,7 @@ int version = Py_MARSHAL_VERSION; if (!PyArg_ParseTuple(args, "OO|i:dump", &x, &f, &version)) return NULL; - if (!PyFile_Check(f)) { - PyErr_SetString(PyExc_TypeError, - "marshal.dump() 2nd arg must be file"); - return NULL; - } - wf.fp = PyFile_AsFile(f); + wf.fp = f; wf.str = NULL; wf.ptr = wf.end = NULL; wf.error = 0; @@ -1052,12 +1098,7 @@ PyObject *f, *result; if (!PyArg_ParseTuple(args, "O:load", &f)) return NULL; - if (!PyFile_Check(f)) { - PyErr_SetString(PyExc_TypeError, - "marshal.load() arg must be file"); - return NULL; - } - rf.fp = PyFile_AsFile(f); + rf.fp = f; rf.strings = PyList_New(0); result = read_object(&rf); Py_DECREF(rf.strings);