Skip to content

Commit 9ef151e

Browse files
gh-98092: Add imaplib.IMAP4.id method
Add a wrapper for the IMAP ID command (RFC 2971). It takes a mapping of field names to values and returns the server identification information from the untagged ID response. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent d2d415b commit 9ef151e

5 files changed

Lines changed: 64 additions & 0 deletions

File tree

Doc/library/imaplib.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,19 @@ An :class:`IMAP4` instance has the following methods:
328328
of the IMAP4 QUOTA extension defined in rfc2087.
329329

330330

331+
.. method:: IMAP4.id(fields=None)
332+
333+
Send client identification information to the server
334+
and return the identification information sent back by the server
335+
(the ``ID`` command, defined in :rfc:`2971`).
336+
*fields* is a mapping of field names to values
337+
(for example, ``{'name': 'myclient', 'version': '1.0'}``);
338+
a value can be ``None``.
339+
The server must support the ``ID`` capability.
340+
341+
.. versionadded:: next
342+
343+
331344
.. method:: IMAP4.idle(duration=None)
332345

333346
Return an :class:`!Idler`: an iterable context manager implementing the

Doc/whatsnew/3.16.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,14 @@ io
228228
(Contributed by Marcel Martin in :gh:`90533`.)
229229

230230

231+
imaplib
232+
-------
233+
234+
* Add the :meth:`~imaplib.IMAP4.id` method,
235+
a wrapper for the ``ID`` command (:rfc:`2971`).
236+
(Contributed by Serhiy Storchaka in :gh:`98092`.)
237+
238+
231239
logging
232240
-------
233241

Lib/imaplib.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
'GETANNOTATION':('AUTH', 'SELECTED'),
7474
'GETQUOTA': ('AUTH', 'SELECTED'),
7575
'GETQUOTAROOT': ('AUTH', 'SELECTED'),
76+
'ID': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
7677
'IDLE': ('AUTH', 'SELECTED'),
7778
'MYRIGHTS': ('AUTH', 'SELECTED'),
7879
'LIST': ('AUTH', 'SELECTED'),
@@ -695,6 +696,28 @@ def getquotaroot(self, mailbox):
695696
return typ, [quotaroot, quota]
696697

697698

699+
def id(self, fields=None):
700+
"""Send client identification information to the server.
701+
702+
(typ, [data]) = <instance>.id(fields)
703+
704+
'fields' is a mapping of field names to values; a value can be
705+
None. 'data' is the identification information sent back by
706+
the server, in the same parenthesized list form.
707+
"""
708+
name = 'ID'
709+
if fields:
710+
items = []
711+
for field, value in fields.items():
712+
items.append(self._quote(field))
713+
items.append(b'NIL' if value is None else self._quote(value))
714+
arg = b'(' + b' '.join(items) + b')'
715+
else:
716+
arg = 'NIL'
717+
typ, dat = self._simple_command(name, arg)
718+
return self._untagged_response(typ, dat, name)
719+
720+
698721
def idle(self, duration=None):
699722
"""Return an iterable IDLE context manager producing untagged responses.
700723
If the argument is not None, limit iteration to 'duration' seconds.

Lib/test/test_imaplib.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1672,6 +1672,24 @@ def test_getquotaroot(self):
16721672
self.assertEqual(typ, 'OK')
16731673
self.assertEqual(server.args, ['"New folder"'])
16741674

1675+
def test_id(self):
1676+
client, server = self._setup(make_simple_handler('ID',
1677+
['* ID ("name" "Cyrus" "version" "1.5")']))
1678+
typ, data = client.id({'name': 'imaplib', 'version': '3.16'})
1679+
self.assertEqual(typ, 'OK')
1680+
self.assertEqual(data, [b'("name" "Cyrus" "version" "1.5")'])
1681+
self.assertEqual(server.args, ['("name" "imaplib" "version" "3.16")'])
1682+
1683+
typ, data = client.id()
1684+
self.assertEqual(typ, 'OK')
1685+
self.assertEqual(server.args, ['NIL'])
1686+
1687+
# Fields and values are quoted strings; a None value is sent
1688+
# as NIL.
1689+
typ, data = client.id({'name': 'my "client"', 'os': None})
1690+
self.assertEqual(typ, 'OK')
1691+
self.assertEqual(server.args, [r'("name" "my \"client\"" "os" NIL)'])
1692+
16751693
def test_setquota(self):
16761694
client, server = self._setup(make_simple_handler('SETQUOTA',
16771695
['* QUOTA "" (STORAGE 512)']))
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add :meth:`imaplib.IMAP4.id`, a wrapper for the IMAP ``ID`` command
2+
(:rfc:`2971`).

0 commit comments

Comments
 (0)