mirror of https://github.com/python/cpython
gh-90890: New methods to access mailbox.Maildir message info and flags (#103905)
New methods to access mailbox.Maildir message info and flags: get_info, set_info, get_flags, set_flags, add_flag, remove_flag. These methods speed up accessing a message's info and/or flags and are useful when it is not necessary to access the message's contents, as when iterating over a Maildir to find messages with specific flags. --------- * Add more str type checking * modernize to f-strings instead of % Co-authored-by: Gregory P. Smith <greg@krypto.org>
This commit is contained in:
parent
fa84e5fe0a
commit
38035fed9b
|
@ -424,6 +424,108 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF.
|
|||
remove the underlying message while the returned file remains open.
|
||||
|
||||
|
||||
.. method:: get_flags(key)
|
||||
|
||||
Return as a string the flags that are set on the message
|
||||
corresponding to *key*.
|
||||
This is the same as ``get_message(key).get_flags()`` but much
|
||||
faster, because it does not open the message file.
|
||||
Use this method when iterating over the keys to determine which
|
||||
messages are interesting to get.
|
||||
|
||||
If you do have a :class:`MaildirMessage` object, use
|
||||
its :meth:`~MaildirMessage.get_flags` method instead, because
|
||||
changes made by the message's :meth:`~MaildirMessage.set_flags`,
|
||||
:meth:`~MaildirMessage.add_flag` and :meth:`~MaildirMessage.remove_flag`
|
||||
methods are not reflected here until the mailbox's
|
||||
:meth:`__setitem__` method is called.
|
||||
|
||||
.. versionadded:: 3.13
|
||||
|
||||
|
||||
.. method:: set_flags(key, flags)
|
||||
|
||||
On the message corresponding to *key*, set the flags specified
|
||||
by *flags* and unset all others.
|
||||
Calling ``some_mailbox.set_flags(key, flags)`` is similar to ::
|
||||
|
||||
one_message = some_mailbox.get_message(key)
|
||||
one_message.set_flags(flags)
|
||||
some_mailbox[key] = one_message
|
||||
|
||||
but faster, because it does not open the message file.
|
||||
|
||||
If you do have a :class:`MaildirMessage` object, use
|
||||
its :meth:`~MaildirMessage.set_flags` method instead, because
|
||||
changes made with this mailbox method will not be visible to the
|
||||
message object's method, :meth:`~MaildirMessage.get_flags`.
|
||||
|
||||
.. versionadded:: 3.13
|
||||
|
||||
|
||||
.. method:: add_flag(key, flag)
|
||||
|
||||
On the message corresponding to *key*, set the flags specified
|
||||
by *flag* without changing other flags. To add more than one
|
||||
flag at a time, *flag* may be a string of more than one character.
|
||||
|
||||
Considerations for using this method versus the message object's
|
||||
:meth:`~MaildirMessage.add_flag` method are similar to
|
||||
those for :meth:`set_flags`; see the discussion there.
|
||||
|
||||
.. versionadded:: 3.13
|
||||
|
||||
|
||||
.. method:: remove_flag(key, flag)
|
||||
|
||||
On the message corresponding to *key*, unset the flags specified
|
||||
by *flag* without changing other flags. To remove more than one
|
||||
flag at a time, *flag* may be a string of more than one character.
|
||||
|
||||
Considerations for using this method versus the message object's
|
||||
:meth:`~MaildirMessage.remove_flag` method are similar to
|
||||
those for :meth:`set_flags`; see the discussion there.
|
||||
|
||||
.. versionadded:: 3.13
|
||||
|
||||
|
||||
.. method:: get_info(key)
|
||||
|
||||
Return a string containing the info for the message
|
||||
corresponding to *key*.
|
||||
This is the same as ``get_message(key).get_info()`` but much
|
||||
faster, because it does not open the message file.
|
||||
Use this method when iterating over the keys to determine which
|
||||
messages are interesting to get.
|
||||
|
||||
If you do have a :class:`MaildirMessage` object, use
|
||||
its :meth:`~MaildirMessage.get_info` method instead, because
|
||||
changes made by the message's :meth:`~MaildirMessage.set_info` method
|
||||
are not reflected here until the mailbox's :meth:`__setitem__` method
|
||||
is called.
|
||||
|
||||
.. versionadded:: 3.13
|
||||
|
||||
|
||||
.. method:: set_info(key, info)
|
||||
|
||||
Set the info of the message corresponding to *key* to *info*.
|
||||
Calling ``some_mailbox.set_info(key, flags)`` is similar to ::
|
||||
|
||||
one_message = some_mailbox.get_message(key)
|
||||
one_message.set_info(info)
|
||||
some_mailbox[key] = one_message
|
||||
|
||||
but faster, because it does not open the message file.
|
||||
|
||||
If you do have a :class:`MaildirMessage` object, use
|
||||
its :meth:`~MaildirMessage.set_info` method instead, because
|
||||
changes made with this mailbox method will not be visible to the
|
||||
message object's method, :meth:`~MaildirMessage.get_info`.
|
||||
|
||||
.. versionadded:: 3.13
|
||||
|
||||
|
||||
.. seealso::
|
||||
|
||||
`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.
|
|||
.. note::
|
||||
|
||||
A message is typically moved from :file:`new` to :file:`cur` after its
|
||||
mailbox has been accessed, whether or not the message is has been
|
||||
mailbox has been accessed, whether or not the message has been
|
||||
read. A message ``msg`` has been read if ``"S" in msg.get_flags()`` is
|
||||
``True``.
|
||||
|
||||
|
|
|
@ -395,6 +395,56 @@ class Maildir(Mailbox):
|
|||
f = open(os.path.join(self._path, self._lookup(key)), 'rb')
|
||||
return _ProxyFile(f)
|
||||
|
||||
def get_info(self, key):
|
||||
"""Get the keyed message's "info" as a string."""
|
||||
subpath = self._lookup(key)
|
||||
if self.colon in subpath:
|
||||
return subpath.split(self.colon)[-1]
|
||||
return ''
|
||||
|
||||
def set_info(self, key, info: str):
|
||||
"""Set the keyed message's "info" string."""
|
||||
if not isinstance(info, str):
|
||||
raise TypeError(f'info must be a string: {type(info)}')
|
||||
old_subpath = self._lookup(key)
|
||||
new_subpath = old_subpath.split(self.colon)[0]
|
||||
if info:
|
||||
new_subpath += self.colon + info
|
||||
if new_subpath == old_subpath:
|
||||
return
|
||||
old_path = os.path.join(self._path, old_subpath)
|
||||
new_path = os.path.join(self._path, new_subpath)
|
||||
os.rename(old_path, new_path)
|
||||
self._toc[key] = new_subpath
|
||||
|
||||
def get_flags(self, key):
|
||||
"""Return as a string the standard flags that are set on the keyed message."""
|
||||
info = self.get_info(key)
|
||||
if info.startswith('2,'):
|
||||
return info[2:]
|
||||
return ''
|
||||
|
||||
def set_flags(self, key, flags: str):
|
||||
"""Set the given flags and unset all others on the keyed message."""
|
||||
if not isinstance(flags, str):
|
||||
raise TypeError(f'flags must be a string: {type(flags)}')
|
||||
# TODO: check if flags are valid standard flag characters?
|
||||
self.set_info(key, '2,' + ''.join(sorted(set(flags))))
|
||||
|
||||
def add_flag(self, key, flag: str):
|
||||
"""Set the given flag(s) without changing others on the keyed message."""
|
||||
if not isinstance(flag, str):
|
||||
raise TypeError(f'flag must be a string: {type(flag)}')
|
||||
# TODO: check that flag is a valid standard flag character?
|
||||
self.set_flags(key, ''.join(set(self.get_flags(key)) | set(flag)))
|
||||
|
||||
def remove_flag(self, key, flag: str):
|
||||
"""Unset the given string flag(s) without changing others on the keyed message."""
|
||||
if not isinstance(flag, str):
|
||||
raise TypeError(f'flag must be a string: {type(flag)}')
|
||||
if self.get_flags(key):
|
||||
self.set_flags(key, ''.join(set(self.get_flags(key)) - set(flag)))
|
||||
|
||||
def iterkeys(self):
|
||||
"""Return an iterator over keys."""
|
||||
self._refresh()
|
||||
|
|
|
@ -847,6 +847,92 @@ class TestMaildir(TestMailbox, unittest.TestCase):
|
|||
self._box.lock()
|
||||
self._box.unlock()
|
||||
|
||||
def test_get_info(self):
|
||||
# Test getting message info from Maildir, not the message.
|
||||
msg = mailbox.MaildirMessage(self._template % 0)
|
||||
key = self._box.add(msg)
|
||||
self.assertEqual(self._box.get_info(key), '')
|
||||
msg.set_info('OurTestInfo')
|
||||
self._box[key] = msg
|
||||
self.assertEqual(self._box.get_info(key), 'OurTestInfo')
|
||||
|
||||
def test_set_info(self):
|
||||
# Test setting message info from Maildir, not the message.
|
||||
# This should immediately rename the message file.
|
||||
msg = mailbox.MaildirMessage(self._template % 0)
|
||||
key = self._box.add(msg)
|
||||
def check_info(oldinfo, newinfo):
|
||||
oldfilename = os.path.join(self._box._path, self._box._lookup(key))
|
||||
newsubpath = self._box._lookup(key).split(self._box.colon)[0]
|
||||
if newinfo:
|
||||
newsubpath += self._box.colon + newinfo
|
||||
newfilename = os.path.join(self._box._path, newsubpath)
|
||||
# assert initial conditions
|
||||
self.assertEqual(self._box.get_info(key), oldinfo)
|
||||
if not oldinfo:
|
||||
self.assertNotIn(self._box._lookup(key), self._box.colon)
|
||||
self.assertTrue(os.path.exists(oldfilename))
|
||||
if oldinfo != newinfo:
|
||||
self.assertFalse(os.path.exists(newfilename))
|
||||
# do the rename
|
||||
self._box.set_info(key, newinfo)
|
||||
# assert post conditions
|
||||
if not newinfo:
|
||||
self.assertNotIn(self._box._lookup(key), self._box.colon)
|
||||
if oldinfo != newinfo:
|
||||
self.assertFalse(os.path.exists(oldfilename))
|
||||
self.assertTrue(os.path.exists(newfilename))
|
||||
self.assertEqual(self._box.get_info(key), newinfo)
|
||||
# none -> has info
|
||||
check_info('', 'info1')
|
||||
# has info -> same info
|
||||
check_info('info1', 'info1')
|
||||
# has info -> different info
|
||||
check_info('info1', 'info2')
|
||||
# has info -> none
|
||||
check_info('info2', '')
|
||||
# none -> none
|
||||
check_info('', '')
|
||||
|
||||
def test_get_flags(self):
|
||||
# Test getting message flags from Maildir, not the message.
|
||||
msg = mailbox.MaildirMessage(self._template % 0)
|
||||
key = self._box.add(msg)
|
||||
self.assertEqual(self._box.get_flags(key), '')
|
||||
msg.set_flags('T')
|
||||
self._box[key] = msg
|
||||
self.assertEqual(self._box.get_flags(key), 'T')
|
||||
|
||||
def test_set_flags(self):
|
||||
msg = mailbox.MaildirMessage(self._template % 0)
|
||||
key = self._box.add(msg)
|
||||
self.assertEqual(self._box.get_flags(key), '')
|
||||
self._box.set_flags(key, 'S')
|
||||
self.assertEqual(self._box.get_flags(key), 'S')
|
||||
|
||||
def test_add_flag(self):
|
||||
msg = mailbox.MaildirMessage(self._template % 0)
|
||||
key = self._box.add(msg)
|
||||
self.assertEqual(self._box.get_flags(key), '')
|
||||
self._box.add_flag(key, 'B')
|
||||
self.assertEqual(self._box.get_flags(key), 'B')
|
||||
self._box.add_flag(key, 'B')
|
||||
self.assertEqual(self._box.get_flags(key), 'B')
|
||||
self._box.add_flag(key, 'AC')
|
||||
self.assertEqual(self._box.get_flags(key), 'ABC')
|
||||
|
||||
def test_remove_flag(self):
|
||||
msg = mailbox.MaildirMessage(self._template % 0)
|
||||
key = self._box.add(msg)
|
||||
self._box.set_flags(key, 'abc')
|
||||
self.assertEqual(self._box.get_flags(key), 'abc')
|
||||
self._box.remove_flag(key, 'b')
|
||||
self.assertEqual(self._box.get_flags(key), 'ac')
|
||||
self._box.remove_flag(key, 'b')
|
||||
self.assertEqual(self._box.get_flags(key), 'ac')
|
||||
self._box.remove_flag(key, 'ac')
|
||||
self.assertEqual(self._box.get_flags(key), '')
|
||||
|
||||
def test_folder (self):
|
||||
# Test for bug #1569790: verify that folders returned by .get_folder()
|
||||
# use the same factory function.
|
||||
|
|
|
@ -630,6 +630,7 @@ Dinu Gherman
|
|||
Subhendu Ghosh
|
||||
Jonathan Giddy
|
||||
Johannes Gijsbers
|
||||
Stephen Gildea
|
||||
Michael Gilfix
|
||||
Julian Gindi
|
||||
Yannick Gingras
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
New methods :meth:`mailbox.Maildir.get_info`,
|
||||
:meth:`mailbox.Maildir.set_info`, :meth:`mailbox.Maildir.get_flags`,
|
||||
:meth:`mailbox.Maildir.set_flags`, :meth:`mailbox.Maildir.add_flag`,
|
||||
:meth:`mailbox.Maildir.remove_flag`. These methods speed up accessing a
|
||||
message's info and/or flags and are useful when it is not necessary to
|
||||
access the message's contents, as when iterating over a Maildir to find
|
||||
messages with specific flags.
|
Loading…
Reference in New Issue