Skip to content

Commit 28a8c18

Browse files
pythongh-98092: Add imaplib.IMAP4.id method (pythonGH-153136)
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 a47a66c commit 28a8c18

5 files changed

Lines changed: 60 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: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,10 @@ io
231231
imaplib
232232
-------
233233

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+
234238
* Add the :meth:`~imaplib.IMAP4.move` method,
235239
a wrapper for the ``MOVE`` command (:rfc:`6851`).
236240
(Contributed by Serhiy Storchaka in :gh:`77508`.)

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'),
@@ -697,6 +698,28 @@ def getquotaroot(self, mailbox):
697698
return typ, [quotaroot, quota]
698699

699700

701+
def id(self, fields=None):
702+
"""Send client identification information to the server.
703+
704+
(typ, [data]) = <instance>.id(fields)
705+
706+
'fields' is a mapping of field names to values; a value can be
707+
None. 'data' is the identification information sent back by
708+
the server, in the same parenthesized list form.
709+
"""
710+
name = 'ID'
711+
if fields:
712+
items = []
713+
for field, value in fields.items():
714+
items.append(self._quote(field))
715+
items.append(b'NIL' if value is None else self._quote(value))
716+
arg = b'(' + b' '.join(items) + b')'
717+
else:
718+
arg = 'NIL'
719+
typ, dat = self._simple_command(name, arg)
720+
return self._untagged_response(typ, dat, name)
721+
722+
700723
def idle(self, duration=None):
701724
"""Return an iterable IDLE context manager producing untagged responses.
702725
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
@@ -1701,6 +1701,24 @@ def test_getquotaroot(self):
17011701
self.assertEqual(typ, 'OK')
17021702
self.assertEqual(server.args, ['"New folder"'])
17031703

1704+
def test_id(self):
1705+
client, server = self._setup(make_simple_handler('ID',
1706+
['* ID ("name" "Cyrus" "version" "1.5")']))
1707+
typ, data = client.id({'name': 'imaplib', 'version': '3.16'})
1708+
self.assertEqual(typ, 'OK')
1709+
self.assertEqual(data, [b'("name" "Cyrus" "version" "1.5")'])
1710+
self.assertEqual(server.args, ['("name" "imaplib" "version" "3.16")'])
1711+
1712+
typ, data = client.id()
1713+
self.assertEqual(typ, 'OK')
1714+
self.assertEqual(server.args, ['NIL'])
1715+
1716+
# Fields and values are quoted strings; a None value is sent
1717+
# as NIL.
1718+
typ, data = client.id({'name': 'my "client"', 'os': None})
1719+
self.assertEqual(typ, 'OK')
1720+
self.assertEqual(server.args, [r'("name" "my \"client\"" "os" NIL)'])
1721+
17041722
def test_setquota(self):
17051723
client, server = self._setup(make_simple_handler('SETQUOTA',
17061724
['* 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)