From e133cf59fd6008f3bdade2d1c6d06f7f3313cb14 Mon Sep 17 00:00:00 2001 From: Timo Kankare Date: Fri, 30 Sep 2016 22:52:22 +0300 Subject: [PATCH 1/6] Preliminary maildir implementation. Folder support is still incomplete --- Mailnag/backends/__init__.py | 4 +- Mailnag/backends/local.py | 65 +++++++++++++++++++- tests/test_backend_local.py | 114 +++++++++++++++++++++++++++++++++++ 3 files changed, 181 insertions(+), 2 deletions(-) diff --git a/Mailnag/backends/__init__.py b/Mailnag/backends/__init__.py index 52f75ad..990ceca 100644 --- a/Mailnag/backends/__init__.py +++ b/Mailnag/backends/__init__.py @@ -28,7 +28,7 @@ import re from Mailnag.backends.imap import IMAPMailboxBackend from Mailnag.backends.pop3 import POP3MailboxBackend -from Mailnag.backends.local import MBoxBackend +from Mailnag.backends.local import MBoxBackend, MaildirBackend from Mailnag.common.utils import splitstr @@ -80,6 +80,8 @@ _backends = { 'mbox' : Backend(MBoxBackend, [ Param('path', 'path', str, str, ''), ]), + 'maildir' : Backend(MaildirBackend, [ + ]), } diff --git a/Mailnag/backends/local.py b/Mailnag/backends/local.py index 2e75a13..c408bac 100644 --- a/Mailnag/backends/local.py +++ b/Mailnag/backends/local.py @@ -33,7 +33,7 @@ class MBoxBackend(MailboxBackend): """Implementation of mbox mail boxes.""" def __init__(self, name = '', path=None, **kw): - """Initialize mbox mailbox backens with a name and path.""" + """Initialize mbox mailbox backend with a name and path.""" self.name = name self.path = path self.opened = False @@ -82,3 +82,66 @@ class MBoxBackend(MailboxBackend): def cancel_notifications(self): raise NotImplementedError("mbox does not support notifications") + +class MaildirBackend(MailboxBackend): + """Implementation of maildir mail boxes.""" + + def __init__(self, name = '', path=None, **kw): + """Initialize maildir mailbox backend with a name and path.""" + self.name = name + self.path = path + self.opened = False + + + def open(self, reopen=False): + """'Open' mailbox. (Actually just checks that maildir directory exists.)""" + if not os.path.isdir(self.path): + raise IOError('Mailbox {} does not exist.'.format(self.path)) + self.opened = True + + + def close(self): + """Close mailbox.""" + self.opened = False + + + def is_open(self): + """Return True if mailbox is opened.""" + return self.opened + + + def list_messages(self): + """List unread messages from the mailbox. + Yields pairs (folder, message) where folder is always ''. + """ + maildir = mailbox.Maildir(self.path, factory=None, create=False) + folder = '' + try: + for msg in maildir: + if 'S' not in msg.get_flags(): + yield folder, msg + finally: + maildir.close() + + + def request_folders(self): + """Lists folder from maildir recursively.""" + + def list_folders(maildir, parent): + for folder in maildir.list_folders(): + this_folder = parent + folder + yield this_folder + for subfolder in list_folders(maildir.get_folder(folder), this_folder + '/'): + yield subfolder + + maildir = mailbox.Maildir(self.path, factory=None, create=False) + return [''] + list(list_folders(maildir, '')) + + + def notify_next_change(self, callback=None, timeout=None): + raise NotImplementedError("mbox does not support notifications") + + + def cancel_notifications(self): + raise NotImplementedError("mbox does not support notifications") + diff --git a/tests/test_backend_local.py b/tests/test_backend_local.py index 849b2c4..6821a4f 100644 --- a/tests/test_backend_local.py +++ b/tests/test_backend_local.py @@ -28,6 +28,8 @@ import pytest from Mailnag.backends import create_backend +# mbox tests + def test_create_mbox_backend(): be = create_backend('mbox') assert be is not None @@ -117,6 +119,103 @@ def test_mbox_open_should_fail_if_mailbox_does_not_exist(tmpdir): be.open() +# maildir tests + + +def test_create_maildir_backend(): + be = create_backend('maildir') + assert be is not None + + +def test_initially_maildir_should_be_closed(): + be = create_backend('maildir') + assert not be.is_open() + + +def test_when_opened_maildir_should_be_open(tmpdir): + path = str(tmpdir.join('sample')) + sample_maildir = mailbox.Maildir(path, create=True) + be = create_backend('maildir', path=path) + be.open() + assert be.is_open() + + +def test_closed_maildir_should_be_closed(tmpdir): + path = str(tmpdir.join('sample')) + sample_maildir = mailbox.Maildir(path, create=True) + be = create_backend('maildir', path=path) + be.open() + be.close() + assert not be.is_open() + + +def test_maildir_lists_no_messages_from_empty_mailbox(tmpdir): + path = str(tmpdir.join('sample')) + sample_maildir = mailbox.Maildir(path, create=True) + + be = create_backend('maildir', name='sample', path=path) + be.open() + try: + msgs = list(be.list_messages()) + assert len(msgs) == 0 + finally: + be.close() + + +def test_maildir_lists_two_messages_from_mailbox(tmpdir): + path = str(tmpdir.join('sample')) + sample_maildir = mailbox.Maildir(path, create=True) + add_maildir_message(sample_maildir, 'blaa-blaa-1', '', 'new') + add_maildir_message(sample_maildir, 'blaa-blaa-2', '', 'cur') + add_maildir_message(sample_maildir, 'blaa-blaa-3', 'S', 'cur') + sample_maildir.close() + + be = create_backend('maildir', name='sample', path=path) + be.open() + try: + msgs = list(be.list_messages()) + folders = [folder for folder, msg in msgs] + msg_ids = set(msg.get('message-id') for folder, msg in msgs) + finally: + be.close() + assert len(msgs) == 2 + assert all(folder == '' for folder in folders) + assert msg_ids == set(['blaa-blaa-1', 'blaa-blaa-2']) + + +def test_maildir_should_have_folders(tmpdir): + path = str(tmpdir.join('sample')) + sample_maildir = mailbox.Maildir(path, create=True) + f = sample_maildir.add_folder('folder1') + sample_maildir.add_folder('folder2') + f.add_folder('subfolder') + + be = create_backend('maildir', path=path) + be.open() + folders = be.request_folders() + assert set(['', 'folder1', 'folder2', 'folder1/subfolder']) == set(folders) + + +def test_maildir_does_not_support_notifications(tmpdir): # for now + path = str(tmpdir.join('sample')) + sample_maildir = mailbox.Maildir(path, create=True) + be = create_backend('maildir', path=path) + be.open() + with pytest.raises(NotImplementedError): + be.notify_next_change() + with pytest.raises(NotImplementedError): + be.cancel_notifications() + + +def test_maildir_open_should_fail_if_mailbox_does_not_exist(tmpdir): + path = str(tmpdir.join('not-exist')) + + be = create_backend('maildir', path=path) + with pytest.raises(IOError): + be.open() + + + # Helper fuctions def add_mbox_message(mbox, msg_id, flags): @@ -133,3 +232,18 @@ def add_mbox_message(mbox, msg_id, flags): finally: mbox.unlock() +def add_maildir_message(maildir, msg_id, flags, subdir): + m = mailbox.MaildirMessage() + m.set_payload('Hello world!', 'ascii') + m.add_header('from', 'me@example.org') + m.add_header('to', 'you@example.org') + m.add_header('subject', 'Hi!') + m.add_header('message-id', msg_id) + m.set_flags(flags) + m.set_subdir(subdir) + maildir.lock() + try: + maildir.add(m) + finally: + maildir.unlock() + From 1bb85ec7147553d7fb893809dfa4fd613e209945 Mon Sep 17 00:00:00 2001 From: Timo Kankare Date: Sun, 2 Oct 2016 09:59:20 +0300 Subject: [PATCH 2/6] Local backend tests refactored. Tests are now arraged to classes and sample mailboxes are created in fixtures. --- tests/test_backend_local.py | 380 +++++++++++++++++------------------- 1 file changed, 176 insertions(+), 204 deletions(-) diff --git a/tests/test_backend_local.py b/tests/test_backend_local.py index 6821a4f..a1655a7 100644 --- a/tests/test_backend_local.py +++ b/tests/test_backend_local.py @@ -28,222 +28,194 @@ import pytest from Mailnag.backends import create_backend -# mbox tests - -def test_create_mbox_backend(): - be = create_backend('mbox') - assert be is not None +@pytest.fixture +def sample_path(tmpdir): + """Temporary path for mailbox used in tests.""" + return str(tmpdir.join('sample')) -def test_initially_mailbox_should_be_closed(): - be = create_backend('mbox') - assert not be.is_open() +class TestMBox: + """Tests for mbox backend.""" + @pytest.fixture(autouse=True) + def sample_mbox(self, sample_path): + """Temporary mbox instance for tests.""" + return mailbox.mbox(sample_path, create=True) -def test_when_opened_mailbox_should_be_open(tmpdir): - tmpdir.join('sample').write('') - path = str(tmpdir.join('sample')) - be = create_backend('mbox', path=path) - be.open() - assert be.is_open() + def test_create_mbox_backend(self): + be = create_backend('mbox') + assert be is not None + def test_initially_mailbox_should_be_closed(self): + be = create_backend('mbox') + assert not be.is_open() -def test_closed_mailbox_should_be_closed(tmpdir): - tmpdir.join('sample').write('') - path = str(tmpdir.join('sample')) - be = create_backend('mbox', path=path) - be.open() - be.close() - assert not be.is_open() - - -def test_mbox_lists_no_messages_from_empty_mailbox(tmpdir): - path = str(tmpdir.join('sample')) - sample_mbox = mailbox.mbox(path, create=True) - - be = create_backend('mbox', name='sample', path=path) - be.open() - try: - msgs = list(be.list_messages()) - assert len(msgs) == 0 - finally: - be.close() - - -def test_mbox_lists_two_messages_from_mailbox(tmpdir): - path = str(tmpdir.join('sample')) - sample_mbox = mailbox.mbox(path, create=True) - add_mbox_message(sample_mbox, 'blaa-blaa-1', '') - add_mbox_message(sample_mbox, 'blaa-blaa-2', 'O') - add_mbox_message(sample_mbox, 'blaa-blaa-3', 'RO') - sample_mbox.close() - - be = create_backend('mbox', name='sample', path=path) - be.open() - try: - msgs = list(be.list_messages()) - folders = [folder for folder, msg in msgs] - msg_ids = set(msg.get('message-id') for folder, msg in msgs) - finally: - be.close() - assert len(msgs) == 2 - assert all(folder == '' for folder in folders) - assert msg_ids == set(['blaa-blaa-1', 'blaa-blaa-2']) - - -def test_mbox_should_not_have_folders(tmpdir): - tmpdir.join('sample').write('') - path = str(tmpdir.join('sample')) - be = create_backend('mbox', path=path) - be.open() - with pytest.raises(NotImplementedError): - be.request_folders() - - -def test_mbox_does_not_support_notifications(tmpdir): # for now - tmpdir.join('sample').write('') - path = str(tmpdir.join('sample')) - be = create_backend('mbox', path=path) - be.open() - with pytest.raises(NotImplementedError): - be.notify_next_change() - with pytest.raises(NotImplementedError): - be.cancel_notifications() - - -def test_mbox_open_should_fail_if_mailbox_does_not_exist(tmpdir): - path = str(tmpdir.join('not-exist')) - - be = create_backend('mbox', path=path) - with pytest.raises(IOError): + def test_mailbox_should_be_open_when_opened(self, sample_path): + be = create_backend('mbox', path=sample_path) be.open() + assert be.is_open() - -# maildir tests - - -def test_create_maildir_backend(): - be = create_backend('maildir') - assert be is not None - - -def test_initially_maildir_should_be_closed(): - be = create_backend('maildir') - assert not be.is_open() - - -def test_when_opened_maildir_should_be_open(tmpdir): - path = str(tmpdir.join('sample')) - sample_maildir = mailbox.Maildir(path, create=True) - be = create_backend('maildir', path=path) - be.open() - assert be.is_open() - - -def test_closed_maildir_should_be_closed(tmpdir): - path = str(tmpdir.join('sample')) - sample_maildir = mailbox.Maildir(path, create=True) - be = create_backend('maildir', path=path) - be.open() - be.close() - assert not be.is_open() - - -def test_maildir_lists_no_messages_from_empty_mailbox(tmpdir): - path = str(tmpdir.join('sample')) - sample_maildir = mailbox.Maildir(path, create=True) - - be = create_backend('maildir', name='sample', path=path) - be.open() - try: - msgs = list(be.list_messages()) - assert len(msgs) == 0 - finally: - be.close() - - -def test_maildir_lists_two_messages_from_mailbox(tmpdir): - path = str(tmpdir.join('sample')) - sample_maildir = mailbox.Maildir(path, create=True) - add_maildir_message(sample_maildir, 'blaa-blaa-1', '', 'new') - add_maildir_message(sample_maildir, 'blaa-blaa-2', '', 'cur') - add_maildir_message(sample_maildir, 'blaa-blaa-3', 'S', 'cur') - sample_maildir.close() - - be = create_backend('maildir', name='sample', path=path) - be.open() - try: - msgs = list(be.list_messages()) - folders = [folder for folder, msg in msgs] - msg_ids = set(msg.get('message-id') for folder, msg in msgs) - finally: - be.close() - assert len(msgs) == 2 - assert all(folder == '' for folder in folders) - assert msg_ids == set(['blaa-blaa-1', 'blaa-blaa-2']) - - -def test_maildir_should_have_folders(tmpdir): - path = str(tmpdir.join('sample')) - sample_maildir = mailbox.Maildir(path, create=True) - f = sample_maildir.add_folder('folder1') - sample_maildir.add_folder('folder2') - f.add_folder('subfolder') - - be = create_backend('maildir', path=path) - be.open() - folders = be.request_folders() - assert set(['', 'folder1', 'folder2', 'folder1/subfolder']) == set(folders) - - -def test_maildir_does_not_support_notifications(tmpdir): # for now - path = str(tmpdir.join('sample')) - sample_maildir = mailbox.Maildir(path, create=True) - be = create_backend('maildir', path=path) - be.open() - with pytest.raises(NotImplementedError): - be.notify_next_change() - with pytest.raises(NotImplementedError): - be.cancel_notifications() - - -def test_maildir_open_should_fail_if_mailbox_does_not_exist(tmpdir): - path = str(tmpdir.join('not-exist')) - - be = create_backend('maildir', path=path) - with pytest.raises(IOError): + def test_closed_mailbox_should_be_closed(self, sample_path): + be = create_backend('mbox', path=sample_path) be.open() + be.close() + assert not be.is_open() + + def test_lists_no_messages_from_empty_mailbox(self, sample_path): + be = create_backend('mbox', name='sample', path=sample_path) + be.open() + try: + msgs = list(be.list_messages()) + assert len(msgs) == 0 + finally: + be.close() + + def test_lists_unread_messages_from_mailbox(self, sample_path, sample_mbox): + self._add_message(sample_mbox, 'blaa-blaa-1', '') + self._add_message(sample_mbox, 'blaa-blaa-2', 'O') + self._add_message(sample_mbox, 'blaa-blaa-3', 'RO') + sample_mbox.close() + + be = create_backend('mbox', name='sample', path=sample_path) + be.open() + try: + msgs = list(be.list_messages()) + folders = [folder for folder, msg in msgs] + msg_ids = set(msg.get('message-id') for folder, msg in msgs) + finally: + be.close() + assert len(msgs) == 2 + assert all(folder == '' for folder in folders) + assert msg_ids == set(['blaa-blaa-1', 'blaa-blaa-2']) + + def test_mbox_should_not_have_folders(self, sample_path): + be = create_backend('mbox', path=sample_path) + be.open() + with pytest.raises(NotImplementedError): + be.request_folders() + + def test_mbox_does_not_support_notifications(self, sample_path): + be = create_backend('mbox', path=sample_path) + be.open() + with pytest.raises(NotImplementedError): + be.notify_next_change() + with pytest.raises(NotImplementedError): + be.cancel_notifications() + + def test_open_should_fail_if_mailbox_does_not_exist(self, tmpdir): + path = str(tmpdir.join('not-exist')) + + be = create_backend('mbox', path=path) + with pytest.raises(IOError): + be.open() + + def _add_message(self, mbox, msg_id, flags): + m = mailbox.mboxMessage() + m.set_payload('Hello world!', 'ascii') + m.add_header('from', 'me@example.org') + m.add_header('to', 'you@example.org') + m.add_header('subject', 'Hi!') + m.add_header('message-id', msg_id) + m.set_flags(flags) + mbox.lock() + try: + mbox.add(m) + finally: + mbox.unlock() +class TestMaildir: + """Tests for maildir backend.""" -# Helper fuctions + @pytest.fixture(autouse=True) + def sample_maildir(self, sample_path): + """Temporary maildir instance for tests.""" + return mailbox.Maildir(sample_path, create=True) -def add_mbox_message(mbox, msg_id, flags): - m = mailbox.mboxMessage() - m.set_payload('Hello world!', 'ascii') - m.add_header('from', 'me@example.org') - m.add_header('to', 'you@example.org') - m.add_header('subject', 'Hi!') - m.add_header('message-id', msg_id) - m.set_flags(flags) - mbox.lock() - try: - mbox.add(m) - finally: - mbox.unlock() + def test_create_maildir_backend(self): + be = create_backend('maildir') + assert be is not None -def add_maildir_message(maildir, msg_id, flags, subdir): - m = mailbox.MaildirMessage() - m.set_payload('Hello world!', 'ascii') - m.add_header('from', 'me@example.org') - m.add_header('to', 'you@example.org') - m.add_header('subject', 'Hi!') - m.add_header('message-id', msg_id) - m.set_flags(flags) - m.set_subdir(subdir) - maildir.lock() - try: - maildir.add(m) - finally: - maildir.unlock() + def test_initially_mailbox_should_be_closed(self): + be = create_backend('maildir') + assert not be.is_open() + + def test_mailbox_should_be_open_when_opened(self, sample_path): + be = create_backend('maildir', path=sample_path) + be.open() + assert be.is_open() + + def test_mailbox_should_be_closed(self, sample_path): + be = create_backend('maildir', path=sample_path) + be.open() + be.close() + assert not be.is_open() + + def test_lists_no_messages_from_empty_mailbox(self, sample_path): + be = create_backend('maildir', name='sample', path=sample_path) + be.open() + try: + msgs = list(be.list_messages()) + assert len(msgs) == 0 + finally: + be.close() + + def test_lists_unread_messages_from_mailbox(self, sample_path, sample_maildir): + self._add_message(sample_maildir, 'blaa-blaa-1', None, 'new') + self._add_message(sample_maildir, 'blaa-blaa-2', None, 'cur') + self._add_message(sample_maildir, 'blaa-blaa-3', 'S', 'cur') + sample_maildir.close() + + be = create_backend('maildir', name='sample', path=sample_path) + be.open() + try: + msgs = list(be.list_messages()) + folders = [folder for folder, msg in msgs] + msg_ids = set(msg.get('message-id') for folder, msg in msgs) + finally: + be.close() + assert len(msgs) == 2 + assert all(folder == '' for folder in folders) + assert msg_ids == set(['blaa-blaa-1', 'blaa-blaa-2']) + + def test_folders_should_be_listed(self, sample_path, sample_maildir): + f = sample_maildir.add_folder('folder1') + sample_maildir.add_folder('folder2') + f.add_folder('subfolder') + + be = create_backend('maildir', path=sample_path) + be.open() + folders = be.request_folders() + assert set(['', 'folder1', 'folder2', 'folder1/subfolder']) == set(folders) + + def test_maildir_does_not_support_notifications(self, sample_path): + be = create_backend('maildir', path=sample_path) + be.open() + with pytest.raises(NotImplementedError): + be.notify_next_change() + with pytest.raises(NotImplementedError): + be.cancel_notifications() + + def test_open_should_fail_if_mailbox_does_not_exist(self, tmpdir): + path = str(tmpdir.join('not-exist')) + + be = create_backend('maildir', path=path) + with pytest.raises(IOError): + be.open() + + def _add_message(self, maildir, msg_id, flags, subdir): + m = mailbox.MaildirMessage() + m.set_payload('Hello world!', 'ascii') + m.add_header('from', 'me@example.org') + m.add_header('to', 'you@example.org') + m.add_header('subject', 'Hi!') + m.add_header('message-id', msg_id) + if flags: + m.set_flags(flags) + m.set_subdir(subdir) + maildir.lock() + try: + maildir.add(m) + finally: + maildir.unlock() From 0700575997525886c5c58d27b43312b485ab90ac Mon Sep 17 00:00:00 2001 From: Timo Kankare Date: Sun, 2 Oct 2016 14:43:53 +0300 Subject: [PATCH 3/6] Implementation for listing messages from the subfolders of maildir. --- Mailnag/backends/local.py | 30 +++++++++++++++++++++--------- tests/test_backend_local.py | 21 +++++++++++++++++++++ 2 files changed, 42 insertions(+), 9 deletions(-) diff --git a/Mailnag/backends/local.py b/Mailnag/backends/local.py index c408bac..caaad5f 100644 --- a/Mailnag/backends/local.py +++ b/Mailnag/backends/local.py @@ -86,10 +86,11 @@ class MBoxBackend(MailboxBackend): class MaildirBackend(MailboxBackend): """Implementation of maildir mail boxes.""" - def __init__(self, name = '', path=None, **kw): - """Initialize maildir mailbox backend with a name and path.""" + def __init__(self, name = '', path=None, folders=[], **kw): + """Initialize maildir mailbox backend with a name, path and folders.""" self.name = name self.path = path + self.folders = folders self.opened = False @@ -112,16 +113,18 @@ class MaildirBackend(MailboxBackend): def list_messages(self): """List unread messages from the mailbox. - Yields pairs (folder, message) where folder is always ''. + Yields pairs (folder, message). """ - maildir = mailbox.Maildir(self.path, factory=None, create=False) - folder = '' + folders = self.folders if len(self.folders) != 0 else [''] + root_maildir = mailbox.Maildir(self.path, factory=None, create=False) try: - for msg in maildir: - if 'S' not in msg.get_flags(): - yield folder, msg + for folder in folders: + maildir = self._get_folder(root_maildir, folder) + for msg in maildir: + if 'S' not in msg.get_flags(): + yield folder, msg finally: - maildir.close() + root_maildir.close() def request_folders(self): @@ -145,3 +148,12 @@ class MaildirBackend(MailboxBackend): def cancel_notifications(self): raise NotImplementedError("mbox does not support notifications") + + def _get_folder(self, maildir, folder): + """Returns folder instance of the given maildir.""" + f = maildir + for subfolder in folder.split('/'): + if subfolder != '': + f = f.get_folder(subfolder) + return f + diff --git a/tests/test_backend_local.py b/tests/test_backend_local.py index a1655a7..db58e5d 100644 --- a/tests/test_backend_local.py +++ b/tests/test_backend_local.py @@ -178,6 +178,27 @@ class TestMaildir: assert all(folder == '' for folder in folders) assert msg_ids == set(['blaa-blaa-1', 'blaa-blaa-2']) + def test_lists_unread_messages_from_selected_folders(self, sample_path, sample_maildir): + f = sample_maildir.add_folder('folder1') + sample_maildir.add_folder('folder2') + s = f.add_folder('subfolder') + self._add_message(sample_maildir, 'blaa-blaa-1', None, 'new') + self._add_message(f, 'blaa-blaa-2', None, 'cur') + self._add_message(s, 'blaa-blaa-3', None, 'cur') + sample_maildir.close() + + be = create_backend('maildir', name='sample', path=sample_path, folders=['', 'folder1/subfolder']) + be.open() + try: + msgs = list(be.list_messages()) + folders = [folder for folder, msg in msgs] + msg_ids = set(msg.get('message-id') for folder, msg in msgs) + finally: + be.close() + assert len(msgs) == 2 + assert set(['', 'folder1/subfolder']) == set(folders) + assert msg_ids == set(['blaa-blaa-1', 'blaa-blaa-3']) + def test_folders_should_be_listed(self, sample_path, sample_maildir): f = sample_maildir.add_folder('folder1') sample_maildir.add_folder('folder2') From cc995bea6b062a1e3f01448766cc926c2f75f6f9 Mon Sep 17 00:00:00 2001 From: Timo Kankare Date: Sun, 2 Oct 2016 18:10:38 +0300 Subject: [PATCH 4/6] Specified maildir configuration paramters. "Fixed" unicode error in folder handling. Maildir requires folder names to be str, but when folders are configured in json format they are read as a unicode. --- Mailnag/backends/__init__.py | 2 ++ Mailnag/backends/local.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/Mailnag/backends/__init__.py b/Mailnag/backends/__init__.py index 990ceca..331be52 100644 --- a/Mailnag/backends/__init__.py +++ b/Mailnag/backends/__init__.py @@ -81,6 +81,8 @@ _backends = { Param('path', 'path', str, str, ''), ]), 'maildir' : Backend(MaildirBackend, [ + Param('path', 'path', str, str, ''), + Param('folders', 'folder', _str_to_folders, _folders_to_str, []), ]), } diff --git a/Mailnag/backends/local.py b/Mailnag/backends/local.py index caaad5f..da2ca22 100644 --- a/Mailnag/backends/local.py +++ b/Mailnag/backends/local.py @@ -119,6 +119,8 @@ class MaildirBackend(MailboxBackend): root_maildir = mailbox.Maildir(self.path, factory=None, create=False) try: for folder in folders: + if isinstance(folder, unicode): + folder = folder.encode('utf-8') maildir = self._get_folder(root_maildir, folder) for msg in maildir: if 'S' not in msg.get_flags(): From 459e57a4b02762729cd02623d13d6abbc9118f35 Mon Sep 17 00:00:00 2001 From: Timo Kankare Date: Sun, 2 Oct 2016 18:11:02 +0300 Subject: [PATCH 5/6] Fixed json folder test case. --- tests/test_accountmanager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_accountmanager.py b/tests/test_accountmanager.py index 657c693..1fb994e 100644 --- a/tests/test_accountmanager.py +++ b/tests/test_accountmanager.py @@ -72,7 +72,7 @@ folder = folderA, folderB, folderC [account6] enabled = 1 name = Imap config with json folder option -folder = folderA, folderB, folderC +folder = ["folderA", "folderB", "folderC"] """ @pytest.fixture From 94047bc3f6349d7d875612731eceb6dff10945aa Mon Sep 17 00:00:00 2001 From: Timo Kankare Date: Sun, 2 Oct 2016 22:22:26 +0300 Subject: [PATCH 6/6] Added test case to ensure that unicode folder names work. --- Mailnag/backends/local.py | 2 ++ tests/test_backend_local.py | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/Mailnag/backends/local.py b/Mailnag/backends/local.py index da2ca22..86cc59c 100644 --- a/Mailnag/backends/local.py +++ b/Mailnag/backends/local.py @@ -120,6 +120,8 @@ class MaildirBackend(MailboxBackend): try: for folder in folders: if isinstance(folder, unicode): + # Python2 maildir folders must be str not unicode. + # TODO: Python3 probably does not need this. folder = folder.encode('utf-8') maildir = self._get_folder(root_maildir, folder) for msg in maildir: diff --git a/tests/test_backend_local.py b/tests/test_backend_local.py index db58e5d..5003d43 100644 --- a/tests/test_backend_local.py +++ b/tests/test_backend_local.py @@ -199,6 +199,24 @@ class TestMaildir: assert set(['', 'folder1/subfolder']) == set(folders) assert msg_ids == set(['blaa-blaa-1', 'blaa-blaa-3']) + def test_should_support_unicode_folder_names(self, sample_path, sample_maildir): + """Python2 maildir folders must be str, not unicode. + However folder in Mailnag configuration is represented as a json + list, and json.loads converts strings to unicode. This test is here + to ensure that unicode folder names work. + Note: This is probably not needed with Python3. + """ + f = sample_maildir.add_folder('folder1') + self._add_message(f, 'blaa-blaa-2', 'S', 'cur') + sample_maildir.close() + + be = create_backend('maildir', name='sample', path=sample_path, folders=[u'', u'folder1']) + be.open() + try: + msgs = list(be.list_messages()) + finally: + be.close() + def test_folders_should_be_listed(self, sample_path, sample_maildir): f = sample_maildir.add_folder('folder1') sample_maildir.add_folder('folder2')