diff -r aa17a0dab86a Doc/library/imaplib.rst --- a/Doc/library/imaplib.rst Sat Feb 16 21:25:40 2013 +0200 +++ b/Doc/library/imaplib.rst Sun Feb 17 00:09:59 2013 +0200 @@ -37,6 +37,20 @@ initialized. If *host* is not specified, ``''`` (the local host) is used. If *port* is omitted, the standard IMAP4 port (143) is used. + The :class:`IMAP4` class supports the :keyword:`with` statement. When used + like this, the IMAP4 ``LOGOUT`` command is issued automatically when the + :keyword:`with` statement exits. E.g.:: + + >>> from imaplib import IMAP4 + >>> with IMAP4("domain.org") as M: + ... M.noop() + ... + ('OK', [b'Nothing Accomplished. d25if65hy903weo.87']) + >>> + + .. versionchanged:: 3.4 + Support for the :keyword:`with` statement was added. + Three exceptions are defined as attributes of the :class:`IMAP4` class: diff -r aa17a0dab86a Lib/imaplib.py --- a/Lib/imaplib.py Sat Feb 16 21:25:40 2013 +0200 +++ b/Lib/imaplib.py Sun Feb 17 00:09:59 2013 +0200 @@ -227,6 +227,17 @@ return getattr(self, attr.lower()) raise AttributeError("Unknown IMAP4 command: '%s'" % attr) + def __enter__(self): + """Context management protocol. Returns self.""" + return self + + def __exit__(self, *args): + """Context management protocol. + logout() will close properly even if not logged in.""" + try: + self.logout() + except OSError: + pass # Overridable methods diff -r aa17a0dab86a Lib/test/test_imaplib.py --- a/Lib/test/test_imaplib.py Sat Feb 16 21:25:40 2013 +0200 +++ b/Lib/test/test_imaplib.py Sun Feb 17 00:09:59 2013 +0200 @@ -10,6 +10,7 @@ import socketserver import time import calendar +import sys from test.support import reap_threads, verbose, transient_internet, run_with_tz, run_with_locale import unittest @@ -95,6 +96,10 @@ timeout = 1 + def setup(self): + super().setup() + self.server.logged = None + def _send(self, message): if verbose: print("SENT: %r" % message.strip()) self.wfile.write(message) @@ -135,6 +140,14 @@ self._send(b'* CAPABILITY IMAP4rev1\r\n') self._send('{} OK CAPABILITY completed\r\n'.format(tag).encode('ASCII')) + def cmd_LOGOUT(self, tag, args): + self.server.logged = None + self._send('{} OK LOGOUT\r\n'.format(tag).encode('ASCII')) + + def cmd_LOGIN(self, tag, args): + self.server.logged = args[0] + self._send('{} OK LOGIN\r\n'.format(tag).encode('ASCII')) + class BaseThreadedNetworkedTests(unittest.TestCase): @@ -214,6 +227,32 @@ self.assertRaises(imaplib.IMAP4.abort, self.imap_class, *server.server_address) + @reap_threads + def test_simple_with_statement(self): + # simplest call + with self.reaped_server(SimpleIMAPHandler) as server: + with self.imap_class(*server.server_address) as imap: + pass + + @reap_threads + def test_with_statement(self): + # doing something + with self.reaped_server(SimpleIMAPHandler) as server: + with self.imap_class(*server.server_address) as imap: + imap.login('user', 'pass') + self.assertEqual(server.logged, b'user') + self.assertIsNone(server.logged) + + @reap_threads + def test_with_statement_logout(self): + # what happens if already logout in the block ? + with self.reaped_server(SimpleIMAPHandler) as server: + with self.imap_class(*server.server_address) as imap: + imap.login('user', 'pass') + self.assertEqual(server.logged, b'user') + imap.logout() + self.assertIsNone(server.logged) + self.assertIsNone(server.logged) class ThreadedNetworkedTests(BaseThreadedNetworkedTests):