Skip to content

Commit 38684c3

Browse files
imaplib.IMAP4 now supports the context manager protocol.
Original patch by Tarek Ziadé.
1 parent bb1e3f1 commit 38684c3

File tree

5 files changed

+67
-0
lines changed

5 files changed

+67
-0
lines changed

Doc/library/imaplib.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,19 @@ base class:
3737
initialized. If *host* is not specified, ``''`` (the local host) is used. If
3838
*port* is omitted, the standard IMAP4 port (143) is used.
3939

40+
The :class:`IMAP4` class supports the :keyword:`with` statement. When used
41+
like this, the IMAP4 ``LOGOUT`` command is issued automatically when the
42+
:keyword:`with` statement exits. E.g.::
43+
44+
>>> from imaplib import IMAP4
45+
>>> with IMAP4("domain.org") as M:
46+
... M.noop()
47+
...
48+
('OK', [b'Nothing Accomplished. d25if65hy903weo.87'])
49+
50+
.. versionchanged:: 3.5
51+
Support for the :keyword:`with` statement was added.
52+
4053
Three exceptions are defined as attributes of the :class:`IMAP4` class:
4154

4255

Doc/whatsnew/3.5.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,14 @@ doctest
141141
*module* contains no docstrings instead of raising :exc:`ValueError`
142142
(contributed by Glenn Jones in :issue:`15916`).
143143

144+
imaplib
145+
-------
146+
147+
* :class:`IMAP4` now supports the context management protocol. When used in a
148+
:keyword:`with` statement, the IMAP4 ``LOGOUT`` command will be called
149+
automatically at the end of the block. (Contributed by Tarek Ziadé and
150+
Serhiy Storchaka in :issue:`4972`).
151+
144152
imghdr
145153
------
146154

Lib/imaplib.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,14 @@ def __getattr__(self, attr):
238238
return getattr(self, attr.lower())
239239
raise AttributeError("Unknown IMAP4 command: '%s'" % attr)
240240

241+
def __enter__(self):
242+
return self
243+
244+
def __exit__(self, *args):
245+
try:
246+
self.logout()
247+
except OSError:
248+
pass
241249

242250

243251
# Overridable methods

Lib/test/test_imaplib.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ class SimpleIMAPHandler(socketserver.StreamRequestHandler):
9898
continuation = None
9999
capabilities = ''
100100

101+
def setup(self):
102+
super().setup()
103+
self.server.logged = None
104+
101105
def _send(self, message):
102106
if verbose:
103107
print("SENT: %r" % message.strip())
@@ -162,9 +166,14 @@ def cmd_CAPABILITY(self, tag, args):
162166
self._send_tagged(tag, 'OK', 'CAPABILITY completed')
163167

164168
def cmd_LOGOUT(self, tag, args):
169+
self.server.logged = None
165170
self._send_textline('* BYE IMAP4ref1 Server logging out')
166171
self._send_tagged(tag, 'OK', 'LOGOUT completed')
167172

173+
def cmd_LOGIN(self, tag, args):
174+
self.server.logged = args[0]
175+
self._send_tagged(tag, 'OK', 'LOGIN completed')
176+
168177

169178
class ThreadedNetworkedTests(unittest.TestCase):
170179
server_class = socketserver.TCPServer
@@ -345,6 +354,32 @@ def handle(self):
345354
self.assertRaises(imaplib.IMAP4.error,
346355
self.imap_class, *server.server_address)
347356

357+
@reap_threads
358+
def test_simple_with_statement(self):
359+
# simplest call
360+
with self.reaped_server(SimpleIMAPHandler) as server:
361+
with self.imap_class(*server.server_address):
362+
pass
363+
364+
@reap_threads
365+
def test_with_statement(self):
366+
with self.reaped_server(SimpleIMAPHandler) as server:
367+
with self.imap_class(*server.server_address) as imap:
368+
imap.login('user', 'pass')
369+
self.assertEqual(server.logged, 'user')
370+
self.assertIsNone(server.logged)
371+
372+
@reap_threads
373+
def test_with_statement_logout(self):
374+
# what happens if already logout in the block?
375+
with self.reaped_server(SimpleIMAPHandler) as server:
376+
with self.imap_class(*server.server_address) as imap:
377+
imap.login('user', 'pass')
378+
self.assertEqual(server.logged, 'user')
379+
imap.logout()
380+
self.assertIsNone(server.logged)
381+
self.assertIsNone(server.logged)
382+
348383

349384
@unittest.skipUnless(ssl, "SSL not available")
350385
class ThreadedNetworkedTestsSSL(ThreadedNetworkedTests):

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,9 @@ Core and Builtins
132132
Library
133133
-------
134134

135+
- Issue #12410: imaplib.IMAP4 now supports the context manager protocol.
136+
Original patch by Tarek Ziadé.
137+
135138
- Issue #16662: load_tests() is now unconditionally run when it is present in
136139
a package's __init__.py. TestLoader.loadTestsFromModule() still accepts
137140
use_load_tests, but it is deprecated and ignored. A new keyword-only

0 commit comments

Comments
 (0)