Skip to content

Commit d916684

Browse files
gh-153521: Support structured arguments in imaplib commands
Command methods now accept a structured *message_set* (an integer, or a sequence of integers, (start, stop) ranges and range objects) and lists of flags or other atoms in place of preformatted parenthesized strings. The search, fetch, sort, thread and uid methods gain a keyword-only *params* argument that substitutes and quotes '?' placeholders in their value-bearing arguments, in the manner of sqlite3 parameter substitution. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 5aafbea commit d916684

5 files changed

Lines changed: 429 additions & 24 deletions

File tree

Doc/library/imaplib.rst

Lines changed: 89 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,62 @@ of message numbers (``'2:4'``), or a group of non-contiguous ranges separated by
217217
commas (``'1:3,6:9'``). A range can contain an asterisk to indicate an infinite
218218
upper bound (``'3:*'``).
219219

220+
It may also be given in a structured form:
221+
an integer,
222+
or a sequence whose items are integers,
223+
``(start, stop)`` range tuples (where ``None`` or ``'*'`` stands for the last
224+
message),
225+
or :class:`range` objects.
226+
For example, ``[1, (3, 5), 8]`` and ``[range(1, 6), 8]`` are both equivalent to
227+
``'1,3:5,8'``.
228+
229+
.. versionchanged:: next
230+
Added support for the structured *message_set*.
231+
232+
Arguments that are parenthesized lists of atoms ---
233+
such as the *flag_list* argument of :meth:`~IMAP4.store` and the *flags*
234+
argument of :meth:`~IMAP4.append`,
235+
the *names* argument of :meth:`~IMAP4.status`,
236+
the *sort_criteria* argument of :meth:`~IMAP4.sort`,
237+
or the *message_parts* argument of :meth:`~IMAP4.fetch` ---
238+
can be passed as a sequence of strings instead of a preformatted string.
239+
For example, ``[r'\Seen', r'\Answered']`` is sent as ``(\Seen \Answered)``.
240+
241+
.. versionchanged:: next
242+
Added support for passing these arguments as a sequence.
243+
244+
.. _imap4-params:
245+
246+
The value-bearing arguments of the search and fetch commands must otherwise be
247+
quoted by hand.
248+
Instead, they may contain ``?`` placeholders that are substituted, and quoted
249+
as required, from a *params* keyword argument,
250+
in the manner of :mod:`sqlite3` parameter substitution::
251+
252+
# SEARCH FROM me@example.com SUBJECT "trip report"
253+
M.search(None, 'FROM ? SUBJECT ?', params=['me@example.com', 'trip report'])
254+
255+
# FETCH 1:5 (FLAGS BODY[HEADER.FIELDS (DATE FROM)])
256+
M.fetch('1:5', 'FLAGS BODY[HEADER.FIELDS ?]', params=[['DATE', 'FROM']])
257+
258+
The placeholders are:
259+
260+
* ``?`` --- an astring: a string (quoted if necessary), an integer, or a
261+
list of them (sent as a parenthesized list);
262+
* ``?f`` --- a flag or a list of flags, sent verbatim without quoting;
263+
* ``?s`` --- a *message_set* in the structured form described above.
264+
265+
``??`` stands for a literal ``?``.
266+
267+
Substitution is only performed when *params* is given,
268+
so an existing call that contains a literal ``?`` is unaffected.
269+
The *params* keyword is accepted by :meth:`~IMAP4.search`,
270+
:meth:`~IMAP4.fetch`, :meth:`~IMAP4.sort`, :meth:`~IMAP4.thread` and
271+
:meth:`~IMAP4.uid`.
272+
273+
.. versionadded:: next
274+
The *params* keyword argument.
275+
220276
An :class:`IMAP4` instance has the following methods:
221277

222278

@@ -324,7 +380,7 @@ An :class:`IMAP4` instance has the following methods:
324380
Added the *message_set* and *uid* parameters.
325381

326382

327-
.. method:: IMAP4.fetch(message_set, message_parts, *, uid=False)
383+
.. method:: IMAP4.fetch(message_set, message_parts, *, uid=False, params=None)
328384

329385
Fetch (parts of) messages. *message_parts* should be a string of message part
330386
names enclosed within parentheses, eg: ``"(UID BODY[TEXT])"``. Returned data
@@ -333,8 +389,11 @@ An :class:`IMAP4` instance has the following methods:
333389
If *uid* is true, *message_set* is a set of UIDs and the message numbers in
334390
the response are UIDs (``UID FETCH``).
335391

392+
If *params* is given, ``?`` placeholders in *message_parts* are substituted
393+
with the quoted parameters (see :ref:`the placeholders <imap4-params>`).
394+
336395
.. versionchanged:: next
337-
Added the *uid* parameter.
396+
Added the *params* and *uid* parameters.
338397

339398

340399
.. method:: IMAP4.getacl(mailbox)
@@ -598,7 +657,7 @@ An :class:`IMAP4` instance has the following methods:
598657
code, instead of the usual type.
599658

600659

601-
.. method:: IMAP4.search(charset, criterion[, ...], *, uid=False)
660+
.. method:: IMAP4.search(charset, criterion[, ...], *, uid=False, params=None)
602661

603662
Search mailbox for matching messages. *charset* may be ``None``, in which case
604663
no ``CHARSET`` will be specified in the request to the server. The IMAP
@@ -617,18 +676,22 @@ An :class:`IMAP4` instance has the following methods:
617676
When *charset* is ``None`` (as it must be under ``UTF8=ACCEPT``),
618677
the criterion is sent using the connection's encoding instead.
619678

679+
If *params* is given, ``?`` placeholders in the criteria are substituted
680+
with the quoted parameters (see :ref:`the placeholders <imap4-params>`).
681+
620682
Example::
621683

622684
# M is a connected IMAP4 instance...
623-
typ, msgnums = M.search(None, 'FROM', '"LDJ"')
685+
typ, msgnums = M.search(None, 'FROM', '"John Smith"')
624686

625687
# or:
626-
typ, msgnums = M.search(None, '(FROM "LDJ")')
688+
typ, msgnums = M.search(None, '(FROM "John Smith")')
627689

628-
.. versionchanged:: next
629-
Added the *uid* parameter.
690+
# or, letting the module quote the value:
691+
typ, msgnums = M.search(None, 'FROM ?', params=['John Smith'])
630692

631693
.. versionchanged:: next
694+
Added the *params* and *uid* parameters.
632695
``str`` search criteria are encoded to *charset*.
633696

634697

@@ -675,7 +738,7 @@ An :class:`IMAP4` instance has the following methods:
675738
Returns socket instance used to connect to server.
676739

677740

678-
.. method:: IMAP4.sort(sort_criteria, charset, search_criterion[, ...], *, uid=False)
741+
.. method:: IMAP4.sort(sort_criteria, charset, search_criterion[, ...], *, uid=False, params=None)
679742

680743
The ``sort`` command is a variant of ``search`` with sorting semantics for the
681744
results. Returned data contains a space separated list of matching message
@@ -696,12 +759,13 @@ An :class:`IMAP4` instance has the following methods:
696759
a *search_criterion* passed as :class:`str` is encoded to *charset*;
697760
pass :class:`bytes` to send one already encoded.
698761

699-
This is an ``IMAP4rev1`` extension command.
762+
If *params* is given, ``?`` placeholders in the search criteria are
763+
substituted with the quoted parameters (see :ref:`the placeholders <imap4-params>`).
700764

701-
.. versionchanged:: next
702-
Added the *uid* parameter.
765+
This is an ``IMAP4rev1`` extension command.
703766

704767
.. versionchanged:: next
768+
Added the *params* and *uid* parameters.
705769
``str`` search criteria are encoded to *charset*.
706770

707771

@@ -745,7 +809,7 @@ An :class:`IMAP4` instance has the following methods:
745809

746810
typ, data = M.search(None, 'ALL')
747811
for num in data[0].split():
748-
M.store(num, '+FLAGS', '\\Deleted')
812+
M.store(num, '+FLAGS', r'\Deleted')
749813
M.expunge()
750814

751815
.. note::
@@ -768,7 +832,7 @@ An :class:`IMAP4` instance has the following methods:
768832
Subscribe to new mailbox.
769833

770834

771-
.. method:: IMAP4.thread(threading_algorithm, charset, search_criterion[, ...], *, uid=False)
835+
.. method:: IMAP4.thread(threading_algorithm, charset, search_criterion[, ...], *, uid=False, params=None)
772836

773837
The ``thread`` command is a variant of ``search`` with threading semantics for
774838
the results. Returned data contains a space separated list of thread members.
@@ -793,22 +857,30 @@ An :class:`IMAP4` instance has the following methods:
793857
a *search_criterion* passed as :class:`str` is encoded to *charset*;
794858
pass :class:`bytes` to send one already encoded.
795859

796-
This is an ``IMAP4rev1`` extension command.
860+
If *params* is given, ``?`` placeholders in the search criteria are
861+
substituted with the quoted parameters (see :ref:`the placeholders <imap4-params>`).
797862

798-
.. versionchanged:: next
799-
Added the *uid* parameter.
863+
This is an ``IMAP4rev1`` extension command.
800864

801865
.. versionchanged:: next
866+
Added the *params* and *uid* parameters.
802867
``str`` search criteria are encoded to *charset*.
803868

804869

805-
.. method:: IMAP4.uid(command, arg[, ...])
870+
.. method:: IMAP4.uid(command, arg[, ...], *, params=None)
806871

807872
Execute command args with messages identified by UID, rather than message
808873
number. Returns response appropriate to command. At least one argument must be
809874
supplied; if none are provided, the server will return an error and an exception
810875
will be raised.
811876

877+
If *params* is given, ``?`` placeholders in the ``SEARCH``, ``SORT`` and
878+
``THREAD`` criteria or in the ``FETCH`` parts are substituted with the quoted
879+
parameters (see :ref:`the placeholders <imap4-params>`).
880+
881+
.. versionchanged:: next
882+
Added the *params* parameter.
883+
812884

813885
.. method:: IMAP4.unsubscribe(mailbox)
814886

Doc/whatsnew/3.16.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,17 @@ imaplib
284284
the criteria are sent using the connection encoding instead.
285285
(Contributed by Serhiy Storchaka in :gh:`153494`.)
286286

287+
* Command methods now accept structured arguments,
288+
so the module takes care of quoting instead of the caller.
289+
A *message_set* and lists of flags or other atoms
290+
can be passed as sequences instead of preformatted strings,
291+
and the :meth:`~imaplib.IMAP4.search`, :meth:`~imaplib.IMAP4.fetch`,
292+
:meth:`~imaplib.IMAP4.sort`, :meth:`~imaplib.IMAP4.thread` and
293+
:meth:`~imaplib.IMAP4.uid` methods accept a *params* keyword argument
294+
that substitutes and quotes ``?`` placeholders,
295+
in the manner of :mod:`sqlite3` parameter substitution.
296+
(Contributed by Serhiy Storchaka in :gh:`153521`.)
297+
287298

288299
ipaddress
289300
---------

0 commit comments

Comments
 (0)