gh-90890: New methods to access mailbox.Maildir message info and flags by gildea · Pull Request #103905 · python/cpython · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 103 additions & 1 deletion Doc/library/mailbox.rst
50 changes: 50 additions & 0 deletions Lib/mailbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,56 @@ def get_file(self, key):
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()
Expand Down
86 changes: 86 additions & 0 deletions Lib/test/test_mailbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -847,6 +847,92 @@ def test_lock_unlock(self):
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.
Expand Down
1 change: 1 addition & 0 deletions Misc/ACKS
Original file line number Diff line number Diff line change
Expand Up @@ -630,6 +630,7 @@ Dinu Gherman
Subhendu Ghosh
Jonathan Giddy
Johannes Gijsbers
Stephen Gildea
Michael Gilfix
Julian Gindi
Yannick Gingras
Expand Down