diff --git a/Doc/library/nntplib.rst b/Doc/library/nntplib.rst index 164f149096c..13257cc362b 100644 --- a/Doc/library/nntplib.rst +++ b/Doc/library/nntplib.rst @@ -70,10 +70,23 @@ The module itself defines the following classes: connecting to an NNTP server on the local machine and intend to call reader-specific commands, such as ``group``. If you get unexpected :exc:`NNTPPermanentError`\ s, you might need to set *readermode*. + :class:`NNTP` class supports the :keyword:`with` statement to + unconditionally consume :exc:`socket.error` exceptions and to close the NNTP + connection when done. Here is a sample on how using it: + + >>> from nntplib import NNTP + >>> with nntplib.NNTP('news.gmane.org') as n: + ... n.group('gmane.comp.python.committers') + ... + ('211 1454 1 1454 gmane.comp.python.committers', '1454', '1', '1454', 'gmane.comp.python.committers') + >>> + .. versionchanged:: 3.2 *usenetrc* is now False by default. + .. versionchanged:: 3.3 + Support for the :keyword:`with` statement was added. .. class:: NNTP_SSL(host, port=563, user=None, password=None, ssl_context=None, readermode=None, usenetrc=False, [timeout]) diff --git a/Doc/whatsnew/3.3.rst b/Doc/whatsnew/3.3.rst index c0cb7cf145b..d86826ca9ea 100644 --- a/Doc/whatsnew/3.3.rst +++ b/Doc/whatsnew/3.3.rst @@ -88,6 +88,22 @@ os (Patch submitted by Giampaolo RodolĂ  in :issue:`10784`.) +nntplib +------- + +The :class:`nntplib.NNTP` class now supports the context manager protocol to +unconditionally consume :exc:`socket.error` exceptions and to close the NNTP +connection when done:: + + >>> from nntplib import NNTP + >>> with nntplib.NNTP('news.gmane.org') as n: + ... n.group('gmane.comp.python.committers') + ... + ('211 1454 1 1454 gmane.comp.python.committers', '1454', '1', '1454', 'gmane.comp.python.committers') + >>> + +(Contributed by Giampaolo RodolĂ  in :issue:`9795`) + Optimizations ============= diff --git a/Lib/nntplib.py b/Lib/nntplib.py index 70a75b60d27..ba701920d19 100644 --- a/Lib/nntplib.py +++ b/Lib/nntplib.py @@ -346,6 +346,20 @@ class _NNTPBase: # Log in and encryption setup order is left to subclasses. self.authenticated = False + def __enter__(self): + return self + + def __exit__(self, *args): + is_connected = lambda: hasattr(self, "file") + if is_connected(): + try: + self.quit() + except (socket.error, EOFError): + pass + finally: + if is_connected(): + self._close() + def getwelcome(self): """Get the welcome message from the server (this is read and squirreled away by __init__()). diff --git a/Lib/test/test_nntplib.py b/Lib/test/test_nntplib.py index e463e5231d2..ec790ada52a 100644 --- a/Lib/test/test_nntplib.py +++ b/Lib/test/test_nntplib.py @@ -1,4 +1,5 @@ import io +import socket import datetime import textwrap import unittest @@ -252,6 +253,26 @@ class NetworkedNNTPTestsMixin: # value setattr(cls, name, wrap_meth(meth)) + def test_with_statement(self): + def is_connected(): + if not hasattr(server, 'file'): + return False + try: + server.help() + except (socket.error, EOFError): + return False + return True + + with self.NNTP_CLASS(self.NNTP_HOST, timeout=TIMEOUT, usenetrc=False) as server: + self.assertTrue(is_connected()) + self.assertTrue(server.help()) + self.assertFalse(is_connected()) + + with self.NNTP_CLASS(self.NNTP_HOST, timeout=TIMEOUT, usenetrc=False) as server: + server.quit() + self.assertFalse(is_connected()) + + NetworkedNNTPTestsMixin.wrap_methods()