Browse files

Merge pull request #335 from growbots/special-folders

Locate special folders like Sent or Trash
  • Loading branch information...
mjs committed Jan 15, 2018
2 parents 6dea381 + 052209e commit ad4c23a73b5e36e82c1d60975030c5d0549a593f
Showing with 85 additions and 0 deletions.
  1. +57 −0 imapclient/
  2. +28 −0 tests/
@@ -76,6 +76,27 @@
DRAFT = br'\Draft'
RECENT = br'\Recent' # This flag is read-only
# Special folders, see RFC6154
# \Flagged is omitted because it is the same as the flag defined above
ALL = br'\All'
ARCHIVE = br'\Archive'
DRAFTS = br'\Drafts'
JUNK = br'\Junk'
SENT = br'\Sent'
TRASH = br'\Trash'
# Personal namespaces that are common among providers
# used as a fallback when the server does not support the NAMESPACE capability
# Names of special folders that are common among providers
SENT: ("Sent", "Sent Items", "Sent items"),
DRAFTS: ("Drafts",),
ARCHIVE: ("Archive",),
TRASH: ("Trash", "Deleted Items", "Deleted Messages"),
JUNK: ("Junk", "Spam")
class Namespace(tuple):
@@ -565,6 +586,42 @@ def _proc_folder_list(self, folder_data):
ret.append((flags, delim, name))
return ret
def find_special_folder(self, folder_flag):
"""Try to locate a special folder, like the Sent or Trash folder.
>>> server.find_special_folder(imapclient.SENT)
This function tries its best to find the correct folder (if any) but
uses heuristics when the server is unable to precisely tell where
special folders are located.
Returns the name of the folder if found, or None otherwise.
# Detect folder by looking for known attributes
# TODO: avoid listing all folders by using extended LIST (RFC6154)
if self.has_capability('SPECIAL-USE'):
for folder in self.list_folders():
if folder and len(folder[0]) > 1 and folder[0][1] == folder_flag:
return folder[2]
# Detect folder by looking for common names
# We only look for folders in the "personal" namespace of the user
if self.has_capability('NAMESPACE'):
personal_namespaces = self.namespace().personal
personal_namespaces = _POPULAR_PERSONAL_NAMESPACES
for personal_namespace in personal_namespaces:
for pattern in _POPULAR_SPECIAL_FOLDERS.get(folder_flag, tuple()):
pattern = personal_namespace[0] + pattern
sent_folders = self.list_folders(pattern=pattern)
if sent_folders:
return sent_folders[0][2]
return None
def select_folder(self, folder, readonly=False):
"""Set the current folder on the server.
@@ -152,6 +152,34 @@ def test_blanks(self):
self.assertEqual(folders, [((br'\HasNoChildren',), b'/', 'last')])
class TestFindSpecialFolder(IMAPClientTest):
def test_find_special_folder_with_special_use(self):
self.client._cached_capabilities = (b'SPECIAL-USE',)
self.client._imap._simple_command.return_value = ('OK', [b'something'])
self.client._imap._untagged_response.return_value = (
'LIST', [
b'(\\HasNoChildren) "/" "INBOX"',
b'(\\HasNoChildren \\Sent) "/" "Sent"',
folder = self.client.find_special_folder(b'\\Sent')
self.assertEqual(folder, "Sent")
def test_find_special_folder_without_special_use_nor_namespace(self):
self.client._cached_capabilities = (b'FOO',)
self.client._imap._simple_command.return_value = ('OK', [b'something'])
self.client._imap._untagged_response.return_value = (
'LIST', [
b'(\\HasNoChildren) "/" "Sent Items"',
folder = self.client.find_special_folder(b'\\Sent')
self.assertEqual(folder, "Sent Items")
class TestSelectFolder(IMAPClientTest):
def test_normal(self):

0 comments on commit ad4c23a

Please sign in to comment.