diff --git a/Doc/library/cgi.rst b/Doc/library/cgi.rst --- a/Doc/library/cgi.rst +++ b/Doc/library/cgi.rst @@ -152,16 +152,19 @@ return bytes):: if fileitem.file: # It's an uploaded file; count lines linecount = 0 while True: line = fileitem.file.readline() if not line: break linecount = linecount + 1 +:class:`FieldStorage` objects also support being used in a :keyword:`with` +statement, which will automatically close them when done. + If an error is encountered when obtaining the contents of an uploaded file (for example, when the user interrupts the form submission by clicking on a Back or Cancel button) the :attr:`~FieldStorage.done` attribute of the object for the field will be set to the value -1. The file upload draft standard entertains the possibility of uploading multiple files from one field (using a recursive :mimetype:`multipart/\*` encoding). When this occurs, the item will be a dictionary-like :class:`FieldStorage` item. @@ -177,16 +180,20 @@ actually be instances of the class :clas A form submitted via POST that also has a query string will contain both :class:`FieldStorage` and :class:`MiniFieldStorage` items. .. versionchanged:: 3.4 The :attr:`~FieldStorage.file` attribute is automatically closed upon the garbage collection of the creating :class:`FieldStorage` instance. +.. versionchanged:: 3.5 + Added support for the context management protocol to the :class:`FieldStorage` + class. + Higher Level Interface ---------------------- The previous section explains how to read CGI form data using the :class:`FieldStorage` class. This section describes a higher level interface which was added to this class to allow one to do it in a more readable and intuitive way. The interface doesn't make the techniques described in previous diff --git a/Lib/cgi.py b/Lib/cgi.py --- a/Lib/cgi.py +++ b/Lib/cgi.py @@ -561,16 +561,25 @@ class FieldStorage: self.read_single() def __del__(self): try: self.file.close() except AttributeError: pass + def __enter__(self): + return self + + def __exit__(self, *args): + try: + self.file.close() + except AttributeError: + pass + def __repr__(self): """Return a printable representation.""" return "FieldStorage(%r, %r, %r)" % ( self.name, self.filename, self.value) def __iter__(self): return iter(self.keys()) diff --git a/Lib/test/test_cgi.py b/Lib/test/test_cgi.py --- a/Lib/test/test_cgi.py +++ b/Lib/test/test_cgi.py @@ -1,9 +1,9 @@ -from test.support import run_unittest, check_warnings +from test.support import check_warnings import cgi import os import sys import tempfile import unittest import warnings from collections import namedtuple from io import StringIO, BytesIO @@ -302,16 +302,30 @@ Content-Type: text/plain self.assertEqual(len(files), 2) expect = [{'name': None, 'filename': 'file1.txt', 'value': b'... contents of file1.txt ...'}, {'name': None, 'filename': 'file2.gif', 'value': b'...contents of file2.gif...'}] for x in range(len(files)): for k, exp in expect[x].items(): got = getattr(files[x], k) self.assertEqual(got, exp) + def test_fieldstorage_as_context_manager(self): + f = tempfile.NamedTemporaryFile("wb+") + self.addCleanup(f.close) + f.write(b'x' * 10) + f.seek(0) + env = {'REQUEST_METHOD': 'PUT'} + with cgi.FieldStorage(fp=f, environ=env) as fs: + r = fs.file.read() + self.assertEqual(r, 'x' * 10) + with self.assertRaises(ValueError): + fs.file.read() + self.assertTrue(fs.file.closed) + f.close() + _qs_result = { 'key1': 'value1', 'key2': ['value2x', 'value2y'], 'key3': 'value3', 'key4': 'value4' } def testQSAndUrlEncode(self): data = "key2=value2x&key3=value3&key4=value4" @@ -476,14 +490,10 @@ Content-Disposition: file; filename="fil Content-Type: image/gif Content-Transfer-Encoding: binary ...contents of file2.gif... --BbC04y-- --AaB03x-- """ - -def test_main(): - run_unittest(CgiTests) - if __name__ == '__main__': - test_main() + unittest.main()