diff --git a/Lib/tempfile.py b/Lib/tempfile.py index 6146235..083d9e4 100644 --- a/Lib/tempfile.py +++ b/Lib/tempfile.py @@ -447,7 +447,12 @@ class _TemporaryFileCloser: # Need to ensure the file is deleted on __del__ def __del__(self): - self.close() + if not self.close_called and self.file is not None: + _warnings.warn( + 'unclosed file %r' % self, ResourceWarning, + stacklevel=2, + source=self) + self.close() else: def close(self): diff --git a/Lib/test/test_tempfile.py b/Lib/test/test_tempfile.py index 51df1ec..d918658 100644 --- a/Lib/test/test_tempfile.py +++ b/Lib/test/test_tempfile.py @@ -957,6 +957,15 @@ class TestNamedTemporaryFile(BaseTestCase): tempfile.NamedTemporaryFile(mode=2, dir=dir) self.assertEqual(os.listdir(dir), []) + def test_resource_warning_on_destructor(self): + def f(): + tempfile.NamedTemporaryFile() + with warnings.catch_warnings(record=True) as w: + f() + self.assertEqual(len(w), 1) + self.assertEqual(w[-1].category, ResourceWarning) + self.assertIn('unclosed file', str(w[-1].message)) + # How to test the mode and bufsize parameters? class TestSpooledTemporaryFile(BaseTestCase): diff --git a/Lib/test/test_urllib_response.py b/Lib/test/test_urllib_response.py index 0eb5942..4de4aaf 100644 --- a/Lib/test/test_urllib_response.py +++ b/Lib/test/test_urllib_response.py @@ -16,8 +16,6 @@ class TestResponse(unittest.TestCase): def test_with(self): addbase = urllib.response.addbase(self.fp) - self.assertIsInstance(addbase, tempfile._TemporaryFileWrapper) - def f(): with addbase as spam: pass diff --git a/Lib/urllib/error.py b/Lib/urllib/error.py index c5b675d..0b5fea4 100644 --- a/Lib/urllib/error.py +++ b/Lib/urllib/error.py @@ -44,14 +44,8 @@ class HTTPError(URLError, urllib.response.addinfourl): self.code = code self.msg = msg self.hdrs = hdrs - self.fp = fp self.filename = url - # The addinfourl classes depend on fp being a valid file - # object. In some cases, the HTTPError may not have a valid - # file object. If this happens, the simplest workaround is to - # not initialize the base classes. - if fp is not None: - self.__super_init(fp, hdrs, url, code) + self.__super_init(fp, hdrs, url, code) def __str__(self): return 'HTTP Error %s: %s' % (self.code, self.msg) diff --git a/Lib/urllib/response.py b/Lib/urllib/response.py index 4778118..ae559f6 100644 --- a/Lib/urllib/response.py +++ b/Lib/urllib/response.py @@ -6,18 +6,15 @@ addinfourl instance, which defines an info() method that returns headers and a geturl() method that returns the url. """ -import tempfile - __all__ = ['addbase', 'addclosehook', 'addinfo', 'addinfourl'] -class addbase(tempfile._TemporaryFileWrapper): +class addbase: """Base class for addinfo and addclosehook. Is a good idea for garbage collection.""" # XXX Add a method to expose the timeout on the underlying socket? def __init__(self, fp): - super(addbase, self).__init__(fp, '', delete=False) # Keep reference around as this was part of the original API. self.fp = fp @@ -33,6 +30,23 @@ class addbase(tempfile._TemporaryFileWrapper): def __exit__(self, type, value, traceback): self.close() + def close(self): + self.fp.close() + + def __getattr__(self, name): + # Attribute lookups are delegated to the underlying file. + return getattr(self.fp, name) + + # iter() doesn't use __getattr__ to find the __iter__ method + def __iter__(self): + # Don't return iter(self.file), but yield from it to avoid closing file + # as long as it's being used as iterator (see issue #23700). We can't + # use 'yield from' here because iter(file) returns the file object + # itself, which has a close method, and thus the file would get closed + # when the generator is finalized, due to PEP380 semantics. + for line in self.fp: + yield line + class addclosehook(addbase): """Class to add a close hook to an open file."""