diff --git a/Lib/tempfile.py b/Lib/tempfile.py index 6146235..733ca5f 100644 --- a/Lib/tempfile.py +++ b/Lib/tempfile.py @@ -421,10 +421,11 @@ class _TemporaryFileCloser: file = None # Set here since __del__ checks it close_called = False - def __init__(self, file, name, delete=True): + def __init__(self, file, name, delete=True, warn_on_unclosed_del=True): self.file = file self.name = name self.delete = delete + self._warn_on_unclosed_del = warn_on_unclosed_del # NT provides delete-on-close as a primitive, so we don't need # the wrapper to do anything special. We still use it so that @@ -447,7 +448,11 @@ 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: + if self._warn_on_unclosed_del: + _warnings.warn('unclosed file %r' % self, ResourceWarning, + stacklevel=2, source=self) + self.close() else: def close(self): @@ -463,12 +468,17 @@ class _TemporaryFileWrapper: temporary use. In particular, it seeks to automatically remove the file when it is no longer needed. """ + _warn_on_unclosed_del = True def __init__(self, file, name, delete=True): self.file = file self.name = name self.delete = delete - self._closer = _TemporaryFileCloser(file, name, delete) + self._closer = _TemporaryFileCloser( + file, + name, + delete, + self._warn_on_unclosed_del) def __getattr__(self, name): # Attribute lookups are delegated to the underlying file 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.py b/Lib/test/test_urllib.py index 43ea6b8..5a7c56a 100644 --- a/Lib/test/test_urllib.py +++ b/Lib/test/test_urllib.py @@ -299,11 +299,11 @@ class urlopen_HttpTests(unittest.TestCase, FakeHTTPMixin, FakeFTPMixin): def check_read(self, ver): self.fakehttp(b"HTTP/" + ver + b" 200 OK\r\n\r\nHello!") try: - fp = urlopen("http://python.org/") - self.assertEqual(fp.readline(), b"Hello!") - self.assertEqual(fp.readline(), b"") - self.assertEqual(fp.geturl(), 'http://python.org/') - self.assertEqual(fp.getcode(), 200) + with urlopen("http://python.org/") as fp: + self.assertEqual(fp.readline(), b"Hello!") + self.assertEqual(fp.readline(), b"") + self.assertEqual(fp.geturl(), 'http://python.org/') + self.assertEqual(fp.getcode(), 200) finally: self.unfakehttp() @@ -320,8 +320,8 @@ class urlopen_HttpTests(unittest.TestCase, FakeHTTPMixin, FakeFTPMixin): def test_willclose(self): self.fakehttp(b"HTTP/1.1 200 OK\r\n\r\nHello!") try: - resp = urlopen("http://www.python.org") - self.assertTrue(resp.fp.will_close) + with urlopen("http://www.python.org") as resp: + self.assertTrue(resp.fp.will_close) finally: self.unfakehttp() @@ -427,7 +427,8 @@ Connection: close self.fakeftp() try: urllib.request.ftpcache['test'] = urllib.request.ftpwrapper('user', 'pass', 'localhost', 21, []) - urlopen('ftp://localhost') + with urlopen('ftp://localhost'): + pass finally: self.unfakeftp() @@ -435,11 +436,11 @@ Connection: close def test_userpass_inurl(self): self.fakehttp(b"HTTP/1.0 200 OK\r\n\r\nHello!") try: - fp = urlopen("http://user:pass@python.org/") - self.assertEqual(fp.readline(), b"Hello!") - self.assertEqual(fp.readline(), b"") - self.assertEqual(fp.geturl(), 'http://user:pass@python.org/') - self.assertEqual(fp.getcode(), 200) + with urlopen("http://user:pass@python.org/") as fp: + self.assertEqual(fp.readline(), b"Hello!") + self.assertEqual(fp.readline(), b"") + self.assertEqual(fp.geturl(), 'http://user:pass@python.org/') + self.assertEqual(fp.getcode(), 200) finally: self.unfakehttp() @@ -451,14 +452,14 @@ Connection: close fakehttp_wrapper = http.client.HTTPConnection authorization = ("Authorization: Basic %s\r\n" % b64encode(userpass.encode("ASCII")).decode("ASCII")) - fp = urlopen(url) - # The authorization header must be in place - self.assertIn(authorization, fakehttp_wrapper.buf.decode("UTF-8")) - self.assertEqual(fp.readline(), b"Hello!") - self.assertEqual(fp.readline(), b"") - # the spaces are quoted in URL so no match - self.assertNotEqual(fp.geturl(), url) - self.assertEqual(fp.getcode(), 200) + with urlopen(url) as fp: + # The authorization header must be in place + self.assertIn(authorization, fakehttp_wrapper.buf.decode("UTF-8")) + self.assertEqual(fp.readline(), b"Hello!") + self.assertEqual(fp.readline(), b"") + # the spaces are quoted in URL so no match + self.assertNotEqual(fp.geturl(), url) + self.assertEqual(fp.getcode(), 200) finally: self.unfakehttp() @@ -506,6 +507,10 @@ class urlopen_DataTests(unittest.TestCase): self.text_url_base64) self.image_url_resp = urllib.request.urlopen(self.image_url) + self.addCleanup(self.text_url_resp.close) + self.addCleanup(self.text_url_base64_resp.close) + self.addCleanup(self.image_url_resp.close) + def test_interface(self): # Make sure object returned by urlopen() has the specified methods for attr in ("read", "readline", "readlines", @@ -520,8 +525,9 @@ class urlopen_DataTests(unittest.TestCase): [('text/plain', ''), ('charset', 'ISO-8859-1')]) self.assertEqual(self.image_url_resp.info()['content-length'], str(len(self.image))) - self.assertEqual(urllib.request.urlopen("data:,").info().get_params(), - [('text/plain', ''), ('charset', 'US-ASCII')]) + with urllib.request.urlopen("data:,") as fp: + self.assertEqual(fp.info().get_params(), + [('text/plain', ''), ('charset', 'US-ASCII')]) def test_geturl(self): self.assertEqual(self.text_url_resp.geturl(), self.text_url) diff --git a/Lib/test/test_urllib2.py b/Lib/test/test_urllib2.py index 34329f8..21f35a7 100644 --- a/Lib/test/test_urllib2.py +++ b/Lib/test/test_urllib2.py @@ -739,18 +739,18 @@ class HandlerTests(unittest.TestCase): ]: req = Request(url) req.timeout = None - r = h.ftp_open(req) - # ftp authentication not yet implemented by FTPHandler - self.assertEqual(h.user, user) - self.assertEqual(h.passwd, passwd) - self.assertEqual(h.host, socket.gethostbyname(host)) - self.assertEqual(h.port, port) - self.assertEqual(h.dirs, dirs) - self.assertEqual(h.ftpwrapper.filename, filename) - self.assertEqual(h.ftpwrapper.filetype, type_) - headers = r.info() - self.assertEqual(headers.get("Content-type"), mimetype) - self.assertEqual(int(headers["Content-length"]), len(data)) + with h.ftp_open(req) as r: + # ftp authentication not yet implemented by FTPHandler + self.assertEqual(h.user, user) + self.assertEqual(h.passwd, passwd) + self.assertEqual(h.host, socket.gethostbyname(host)) + self.assertEqual(h.port, port) + self.assertEqual(h.dirs, dirs) + self.assertEqual(h.ftpwrapper.filename, filename) + self.assertEqual(h.ftpwrapper.filetype, type_) + headers = r.info() + self.assertEqual(headers.get("Content-type"), mimetype) + self.assertEqual(int(headers["Content-length"]), len(data)) def test_file(self): import email.utils diff --git a/Lib/urllib/error.py b/Lib/urllib/error.py index c5b675d..708f192 100644 --- a/Lib/urllib/error.py +++ b/Lib/urllib/error.py @@ -39,6 +39,9 @@ class URLError(OSError): class HTTPError(URLError, urllib.response.addinfourl): """Raised when HTTP error occurs, but also acts like non-error return""" __super_init = urllib.response.addinfourl.__init__ + # HTTPError wraps an existing resource. Therefore, don't warn if __del__() + # is called before close(). + _warn_on_unclosed_del = False def __init__(self, url, code, msg, hdrs, fp): self.code = code