diff --git a/Doc/library/imaplib.rst b/Doc/library/imaplib.rst index 6b7c02f54e90af..0684820ccc6916 100644 --- a/Doc/library/imaplib.rst +++ b/Doc/library/imaplib.rst @@ -328,6 +328,19 @@ An :class:`IMAP4` instance has the following methods: of the IMAP4 QUOTA extension defined in rfc2087. +.. method:: IMAP4.id(fields=None) + + Send client identification information to the server + and return the identification information sent back by the server + (the ``ID`` command, defined in :rfc:`2971`). + *fields* is a mapping of field names to values + (for example, ``{'name': 'myclient', 'version': '1.0'}``); + a value can be ``None``. + The server must support the ``ID`` capability. + + .. versionadded:: next + + .. method:: IMAP4.idle(duration=None) Return an :class:`!Idler`: an iterable context manager implementing the diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst index cf105a26d98d45..e8c530e19d2b53 100644 --- a/Doc/whatsnew/3.16.rst +++ b/Doc/whatsnew/3.16.rst @@ -231,6 +231,10 @@ io imaplib ------- +* Add the :meth:`~imaplib.IMAP4.id` method, + a wrapper for the ``ID`` command (:rfc:`2971`). + (Contributed by Serhiy Storchaka in :gh:`98092`.) + * Add the :meth:`~imaplib.IMAP4.move` method, a wrapper for the ``MOVE`` command (:rfc:`6851`). (Contributed by Serhiy Storchaka in :gh:`77508`.) diff --git a/Lib/imaplib.py b/Lib/imaplib.py index adfd8afb9c053b..40d2b7a309b640 100644 --- a/Lib/imaplib.py +++ b/Lib/imaplib.py @@ -73,6 +73,7 @@ 'GETANNOTATION':('AUTH', 'SELECTED'), 'GETQUOTA': ('AUTH', 'SELECTED'), 'GETQUOTAROOT': ('AUTH', 'SELECTED'), + 'ID': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'), 'IDLE': ('AUTH', 'SELECTED'), 'MYRIGHTS': ('AUTH', 'SELECTED'), 'LIST': ('AUTH', 'SELECTED'), @@ -697,6 +698,28 @@ def getquotaroot(self, mailbox): return typ, [quotaroot, quota] + def id(self, fields=None): + """Send client identification information to the server. + + (typ, [data]) = .id(fields) + + 'fields' is a mapping of field names to values; a value can be + None. 'data' is the identification information sent back by + the server, in the same parenthesized list form. + """ + name = 'ID' + if fields: + items = [] + for field, value in fields.items(): + items.append(self._quote(field)) + items.append(b'NIL' if value is None else self._quote(value)) + arg = b'(' + b' '.join(items) + b')' + else: + arg = 'NIL' + typ, dat = self._simple_command(name, arg) + return self._untagged_response(typ, dat, name) + + def idle(self, duration=None): """Return an iterable IDLE context manager producing untagged responses. If the argument is not None, limit iteration to 'duration' seconds. diff --git a/Lib/test/test_imaplib.py b/Lib/test/test_imaplib.py index 046d28f4d30c8a..aba3f5e44f2566 100644 --- a/Lib/test/test_imaplib.py +++ b/Lib/test/test_imaplib.py @@ -1701,6 +1701,24 @@ def test_getquotaroot(self): self.assertEqual(typ, 'OK') self.assertEqual(server.args, ['"New folder"']) + def test_id(self): + client, server = self._setup(make_simple_handler('ID', + ['* ID ("name" "Cyrus" "version" "1.5")'])) + typ, data = client.id({'name': 'imaplib', 'version': '3.16'}) + self.assertEqual(typ, 'OK') + self.assertEqual(data, [b'("name" "Cyrus" "version" "1.5")']) + self.assertEqual(server.args, ['("name" "imaplib" "version" "3.16")']) + + typ, data = client.id() + self.assertEqual(typ, 'OK') + self.assertEqual(server.args, ['NIL']) + + # Fields and values are quoted strings; a None value is sent + # as NIL. + typ, data = client.id({'name': 'my "client"', 'os': None}) + self.assertEqual(typ, 'OK') + self.assertEqual(server.args, [r'("name" "my \"client\"" "os" NIL)']) + def test_setquota(self): client, server = self._setup(make_simple_handler('SETQUOTA', ['* QUOTA "" (STORAGE 512)'])) diff --git a/Misc/NEWS.d/next/Library/2026-07-05-20-15-00.gh-issue-98092.iDcmd1.rst b/Misc/NEWS.d/next/Library/2026-07-05-20-15-00.gh-issue-98092.iDcmd1.rst new file mode 100644 index 00000000000000..9854e996a942a5 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-07-05-20-15-00.gh-issue-98092.iDcmd1.rst @@ -0,0 +1,2 @@ +Add :meth:`imaplib.IMAP4.id`, a wrapper for the IMAP ``ID`` command +(:rfc:`2971`).