Skip to content

Commit 2f72974

Browse files
authored
Merge branch 'main' into closed-loop-runtime-error
2 parents 075a7fd + 12a30bc commit 2f72974

File tree

8 files changed

+255
-4
lines changed

8 files changed

+255
-4
lines changed

Doc/library/mailbox.rst

Lines changed: 103 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,108 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF.
424424
remove the underlying message while the returned file remains open.
425425

426426

427+
.. method:: get_flags(key)
428+
429+
Return as a string the flags that are set on the message
430+
corresponding to *key*.
431+
This is the same as ``get_message(key).get_flags()`` but much
432+
faster, because it does not open the message file.
433+
Use this method when iterating over the keys to determine which
434+
messages are interesting to get.
435+
436+
If you do have a :class:`MaildirMessage` object, use
437+
its :meth:`~MaildirMessage.get_flags` method instead, because
438+
changes made by the message's :meth:`~MaildirMessage.set_flags`,
439+
:meth:`~MaildirMessage.add_flag` and :meth:`~MaildirMessage.remove_flag`
440+
methods are not reflected here until the mailbox's
441+
:meth:`__setitem__` method is called.
442+
443+
.. versionadded:: 3.13
444+
445+
446+
.. method:: set_flags(key, flags)
447+
448+
On the message corresponding to *key*, set the flags specified
449+
by *flags* and unset all others.
450+
Calling ``some_mailbox.set_flags(key, flags)`` is similar to ::
451+
452+
one_message = some_mailbox.get_message(key)
453+
one_message.set_flags(flags)
454+
some_mailbox[key] = one_message
455+
456+
but faster, because it does not open the message file.
457+
458+
If you do have a :class:`MaildirMessage` object, use
459+
its :meth:`~MaildirMessage.set_flags` method instead, because
460+
changes made with this mailbox method will not be visible to the
461+
message object's method, :meth:`~MaildirMessage.get_flags`.
462+
463+
.. versionadded:: 3.13
464+
465+
466+
.. method:: add_flag(key, flag)
467+
468+
On the message corresponding to *key*, set the flags specified
469+
by *flag* without changing other flags. To add more than one
470+
flag at a time, *flag* may be a string of more than one character.
471+
472+
Considerations for using this method versus the message object's
473+
:meth:`~MaildirMessage.add_flag` method are similar to
474+
those for :meth:`set_flags`; see the discussion there.
475+
476+
.. versionadded:: 3.13
477+
478+
479+
.. method:: remove_flag(key, flag)
480+
481+
On the message corresponding to *key*, unset the flags specified
482+
by *flag* without changing other flags. To remove more than one
483+
flag at a time, *flag* may be a string of more than one character.
484+
485+
Considerations for using this method versus the message object's
486+
:meth:`~MaildirMessage.remove_flag` method are similar to
487+
those for :meth:`set_flags`; see the discussion there.
488+
489+
.. versionadded:: 3.13
490+
491+
492+
.. method:: get_info(key)
493+
494+
Return a string containing the info for the message
495+
corresponding to *key*.
496+
This is the same as ``get_message(key).get_info()`` but much
497+
faster, because it does not open the message file.
498+
Use this method when iterating over the keys to determine which
499+
messages are interesting to get.
500+
501+
If you do have a :class:`MaildirMessage` object, use
502+
its :meth:`~MaildirMessage.get_info` method instead, because
503+
changes made by the message's :meth:`~MaildirMessage.set_info` method
504+
are not reflected here until the mailbox's :meth:`__setitem__` method
505+
is called.
506+
507+
.. versionadded:: 3.13
508+
509+
510+
.. method:: set_info(key, info)
511+
512+
Set the info of the message corresponding to *key* to *info*.
513+
Calling ``some_mailbox.set_info(key, flags)`` is similar to ::
514+
515+
one_message = some_mailbox.get_message(key)
516+
one_message.set_info(info)
517+
some_mailbox[key] = one_message
518+
519+
but faster, because it does not open the message file.
520+
521+
If you do have a :class:`MaildirMessage` object, use
522+
its :meth:`~MaildirMessage.set_info` method instead, because
523+
changes made with this mailbox method will not be visible to the
524+
message object's method, :meth:`~MaildirMessage.get_info`.
525+
526+
.. versionadded:: 3.13
527+
528+
427529
.. seealso::
428530

429531
`maildir man page from Courier <https://www.courier-mta.org/maildir.html>`_
@@ -838,7 +940,7 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF.
838940
.. note::
839941

840942
A message is typically moved from :file:`new` to :file:`cur` after its
841-
mailbox has been accessed, whether or not the message is has been
943+
mailbox has been accessed, whether or not the message has been
842944
read. A message ``msg`` has been read if ``"S" in msg.get_flags()`` is
843945
``True``.
844946

Lib/mailbox.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,56 @@ def get_file(self, key):
395395
f = open(os.path.join(self._path, self._lookup(key)), 'rb')
396396
return _ProxyFile(f)
397397

398+
def get_info(self, key):
399+
"""Get the keyed message's "info" as a string."""
400+
subpath = self._lookup(key)
401+
if self.colon in subpath:
402+
return subpath.split(self.colon)[-1]
403+
return ''
404+
405+
def set_info(self, key, info: str):
406+
"""Set the keyed message's "info" string."""
407+
if not isinstance(info, str):
408+
raise TypeError(f'info must be a string: {type(info)}')
409+
old_subpath = self._lookup(key)
410+
new_subpath = old_subpath.split(self.colon)[0]
411+
if info:
412+
new_subpath += self.colon + info
413+
if new_subpath == old_subpath:
414+
return
415+
old_path = os.path.join(self._path, old_subpath)
416+
new_path = os.path.join(self._path, new_subpath)
417+
os.rename(old_path, new_path)
418+
self._toc[key] = new_subpath
419+
420+
def get_flags(self, key):
421+
"""Return as a string the standard flags that are set on the keyed message."""
422+
info = self.get_info(key)
423+
if info.startswith('2,'):
424+
return info[2:]
425+
return ''
426+
427+
def set_flags(self, key, flags: str):
428+
"""Set the given flags and unset all others on the keyed message."""
429+
if not isinstance(flags, str):
430+
raise TypeError(f'flags must be a string: {type(flags)}')
431+
# TODO: check if flags are valid standard flag characters?
432+
self.set_info(key, '2,' + ''.join(sorted(set(flags))))
433+
434+
def add_flag(self, key, flag: str):
435+
"""Set the given flag(s) without changing others on the keyed message."""
436+
if not isinstance(flag, str):
437+
raise TypeError(f'flag must be a string: {type(flag)}')
438+
# TODO: check that flag is a valid standard flag character?
439+
self.set_flags(key, ''.join(set(self.get_flags(key)) | set(flag)))
440+
441+
def remove_flag(self, key, flag: str):
442+
"""Unset the given string flag(s) without changing others on the keyed message."""
443+
if not isinstance(flag, str):
444+
raise TypeError(f'flag must be a string: {type(flag)}')
445+
if self.get_flags(key):
446+
self.set_flags(key, ''.join(set(self.get_flags(key)) - set(flag)))
447+
398448
def iterkeys(self):
399449
"""Return an iterator over keys."""
400450
self._refresh()

Lib/test/test_mailbox.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -847,6 +847,92 @@ def test_lock_unlock(self):
847847
self._box.lock()
848848
self._box.unlock()
849849

850+
def test_get_info(self):
851+
# Test getting message info from Maildir, not the message.
852+
msg = mailbox.MaildirMessage(self._template % 0)
853+
key = self._box.add(msg)
854+
self.assertEqual(self._box.get_info(key), '')
855+
msg.set_info('OurTestInfo')
856+
self._box[key] = msg
857+
self.assertEqual(self._box.get_info(key), 'OurTestInfo')
858+
859+
def test_set_info(self):
860+
# Test setting message info from Maildir, not the message.
861+
# This should immediately rename the message file.
862+
msg = mailbox.MaildirMessage(self._template % 0)
863+
key = self._box.add(msg)
864+
def check_info(oldinfo, newinfo):
865+
oldfilename = os.path.join(self._box._path, self._box._lookup(key))
866+
newsubpath = self._box._lookup(key).split(self._box.colon)[0]
867+
if newinfo:
868+
newsubpath += self._box.colon + newinfo
869+
newfilename = os.path.join(self._box._path, newsubpath)
870+
# assert initial conditions
871+
self.assertEqual(self._box.get_info(key), oldinfo)
872+
if not oldinfo:
873+
self.assertNotIn(self._box._lookup(key), self._box.colon)
874+
self.assertTrue(os.path.exists(oldfilename))
875+
if oldinfo != newinfo:
876+
self.assertFalse(os.path.exists(newfilename))
877+
# do the rename
878+
self._box.set_info(key, newinfo)
879+
# assert post conditions
880+
if not newinfo:
881+
self.assertNotIn(self._box._lookup(key), self._box.colon)
882+
if oldinfo != newinfo:
883+
self.assertFalse(os.path.exists(oldfilename))
884+
self.assertTrue(os.path.exists(newfilename))
885+
self.assertEqual(self._box.get_info(key), newinfo)
886+
# none -> has info
887+
check_info('', 'info1')
888+
# has info -> same info
889+
check_info('info1', 'info1')
890+
# has info -> different info
891+
check_info('info1', 'info2')
892+
# has info -> none
893+
check_info('info2', '')
894+
# none -> none
895+
check_info('', '')
896+
897+
def test_get_flags(self):
898+
# Test getting message flags from Maildir, not the message.
899+
msg = mailbox.MaildirMessage(self._template % 0)
900+
key = self._box.add(msg)
901+
self.assertEqual(self._box.get_flags(key), '')
902+
msg.set_flags('T')
903+
self._box[key] = msg
904+
self.assertEqual(self._box.get_flags(key), 'T')
905+
906+
def test_set_flags(self):
907+
msg = mailbox.MaildirMessage(self._template % 0)
908+
key = self._box.add(msg)
909+
self.assertEqual(self._box.get_flags(key), '')
910+
self._box.set_flags(key, 'S')
911+
self.assertEqual(self._box.get_flags(key), 'S')
912+
913+
def test_add_flag(self):
914+
msg = mailbox.MaildirMessage(self._template % 0)
915+
key = self._box.add(msg)
916+
self.assertEqual(self._box.get_flags(key), '')
917+
self._box.add_flag(key, 'B')
918+
self.assertEqual(self._box.get_flags(key), 'B')
919+
self._box.add_flag(key, 'B')
920+
self.assertEqual(self._box.get_flags(key), 'B')
921+
self._box.add_flag(key, 'AC')
922+
self.assertEqual(self._box.get_flags(key), 'ABC')
923+
924+
def test_remove_flag(self):
925+
msg = mailbox.MaildirMessage(self._template % 0)
926+
key = self._box.add(msg)
927+
self._box.set_flags(key, 'abc')
928+
self.assertEqual(self._box.get_flags(key), 'abc')
929+
self._box.remove_flag(key, 'b')
930+
self.assertEqual(self._box.get_flags(key), 'ac')
931+
self._box.remove_flag(key, 'b')
932+
self.assertEqual(self._box.get_flags(key), 'ac')
933+
self._box.remove_flag(key, 'ac')
934+
self.assertEqual(self._box.get_flags(key), '')
935+
850936
def test_folder (self):
851937
# Test for bug #1569790: verify that folders returned by .get_folder()
852938
# use the same factory function.

Misc/ACKS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -630,6 +630,7 @@ Dinu Gherman
630630
Subhendu Ghosh
631631
Jonathan Giddy
632632
Johannes Gijsbers
633+
Stephen Gildea
633634
Michael Gilfix
634635
Julian Gindi
635636
Yannick Gingras
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
New methods :meth:`mailbox.Maildir.get_info`,
2+
:meth:`mailbox.Maildir.set_info`, :meth:`mailbox.Maildir.get_flags`,
3+
:meth:`mailbox.Maildir.set_flags`, :meth:`mailbox.Maildir.add_flag`,
4+
:meth:`mailbox.Maildir.remove_flag`. These methods speed up accessing a
5+
message's info and/or flags and are useful when it is not necessary to
6+
access the message's contents, as when iterating over a Maildir to find
7+
messages with specific flags.

Modules/_datetimemodule.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4630,7 +4630,7 @@ time_fromisoformat(PyObject *cls, PyObject *tstr) {
46304630
}
46314631

46324632
int hour = 0, minute = 0, second = 0, microsecond = 0;
4633-
int tzoffset, tzimicrosecond = 0;
4633+
int tzoffset = 0, tzimicrosecond = 0;
46344634
int rv = parse_isoformat_time(p, len,
46354635
&hour, &minute, &second, &microsecond,
46364636
&tzoffset, &tzimicrosecond);

Modules/gcmodule.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2396,14 +2396,16 @@ PyObject_GC_Del(void *op)
23962396
size_t presize = _PyType_PreHeaderSize(((PyObject *)op)->ob_type);
23972397
PyGC_Head *g = AS_GC(op);
23982398
if (_PyObject_GC_IS_TRACKED(op)) {
2399+
gc_list_remove(g);
23992400
#ifdef Py_DEBUG
2401+
PyObject *exc = PyErr_GetRaisedException();
24002402
if (PyErr_WarnExplicitFormat(PyExc_ResourceWarning, "gc", 0,
24012403
"gc", NULL, "Object of type %s is not untracked before destruction",
24022404
((PyObject*)op)->ob_type->tp_name)) {
24032405
PyErr_WriteUnraisable(NULL);
24042406
}
2407+
PyErr_SetRaisedException(exc);
24052408
#endif
2406-
gc_list_remove(g);
24072409
}
24082410
GCState *gcstate = get_gc_state();
24092411
if (gcstate->generations[0].count > 0) {

Python/bltinmodule.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2611,7 +2611,10 @@ builtin_sum_impl(PyObject *module, PyObject *iterable, PyObject *start)
26112611
}
26122612
if (PyFloat_CheckExact(item)) {
26132613
// Improved Kahan–Babuška algorithm by Arnold Neumaier
2614-
// https://www.mat.univie.ac.at/~neum/scan/01.pdf
2614+
// Neumaier, A. (1974), Rundungsfehleranalyse einiger Verfahren
2615+
// zur Summation endlicher Summen. Z. angew. Math. Mech.,
2616+
// 54: 39-51. https://doi.org/10.1002/zamm.19740540106
2617+
// https://en.wikipedia.org/wiki/Kahan_summation_algorithm#Further_enhancements
26152618
double x = PyFloat_AS_DOUBLE(item);
26162619
double t = f_result + x;
26172620
if (fabs(f_result) >= fabs(x)) {

0 commit comments

Comments
 (0)