Skip to content

Commit

Permalink
Merge d9f59bf into 99e8519
Browse files Browse the repository at this point in the history
  • Loading branch information
icgood committed Feb 1, 2019
2 parents 99e8519 + d9f59bf commit d34b1ae
Show file tree
Hide file tree
Showing 52 changed files with 2,588 additions and 1,083 deletions.
2 changes: 1 addition & 1 deletion doc/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ Table of Contents
pymap.mime
pymap.search
pymap.selected
pymap.server
pymap.sockinfo
pymap.interfaces
pymap.parsing
pymap.backend
pymap.imap
pymap.admin
pymap.sieve

Expand Down
6 changes: 6 additions & 0 deletions doc/source/pymap.imap.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

``pymap.imap``
==============

.. automodule:: pymap.imap
:members:
6 changes: 0 additions & 6 deletions doc/source/pymap.server.rst

This file was deleted.

14 changes: 13 additions & 1 deletion pymap/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,13 @@
"""Root package for the pymap project."""
"""Contains the package version string.
See Also:
`PEP 396 <https://www.python.org/dev/peps/pep-0396/>`_
"""

import pkg_resources

__all__ = ['__version__']

#: The package version string.
__version__: str = pkg_resources.require(__package__)[0].version
6 changes: 4 additions & 2 deletions pymap/admin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,11 @@ def __init__(self, path: str, server: Server) -> None:

@classmethod
def add_arguments(cls, parser: ArgumentParser) -> None:
admin = parser.add_argument_group('admin arguments')
admin.add_argument('--admin-path', metavar='PATH',
group = parser.add_argument_group('admin service')
group.add_argument('--admin-path', metavar='PATH',
help='path to POSIX socket file')
group.add_argument('--no-filter', action='store_true',
help='do not filter appended messages')

@classmethod
async def init(cls, backend: BackendInterface) -> 'AdminService':
Expand Down
2 changes: 1 addition & 1 deletion pymap/admin/client/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from argparse import ArgumentParser, Namespace

from grpclib.client import Channel # type: ignore
from pymap.core import __version__
from pymap import __version__

from .append import AppendCommand
from .command import ClientCommand
Expand Down
11 changes: 8 additions & 3 deletions pymap/admin/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ async def _login_as(self, user: str) -> SessionInterface:
creds = ExternalResult(user)
return await self.login(creds, self.config)

@property
def _with_filter(self) -> bool:
return not self.config.args.no_filter

async def Append(self, stream) -> None:
"""Append a message directly to a user's mailbox.
Expand Down Expand Up @@ -62,9 +66,10 @@ async def Append(self, stream) -> None:
ExtensionOptions.empty())
try:
session = await self._login_as(request.user)
if session.filter_set is not None:
filter_ = await session.filter_set.get_active()
if filter_ is not None:
if self._with_filter and session.filter_set is not None:
filter_value = await session.filter_set.get_active()
if filter_value is not None:
filter_ = await session.filter_set.compile(filter_value)
new_mailbox, append_msg = await filter_.apply(
request.sender, request.recipient, mailbox, append_msg)
if new_mailbox is None:
Expand Down
64 changes: 28 additions & 36 deletions pymap/backend/dict/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,43 @@
from argparse import Namespace, ArgumentDefaultsHelpFormatter
from contextlib import closing
from datetime import datetime, timezone
from typing import Any, Optional, Tuple, Mapping, Dict
from typing import Any, Tuple, Mapping, Dict

from pkg_resources import resource_listdir, resource_stream
from pysasl import AuthenticationCredentials

from pymap.config import IMAPConfig
from pymap.exceptions import InvalidAuth
from pymap.filter import EntryPointFilterSet, SingleFilterSet
from pymap.interfaces.backend import BackendInterface
from pymap.interfaces.filter import FilterInterface
from pymap.interfaces.session import LoginProtocol
from pymap.parsing.specials.flag import Flag, Recent
from pymap.server import IMAPServer

from .filter import FilterSet
from .mailbox import Message, MailboxData, MailboxSet
from ..session import BaseSession

__all__ = ['DictBackend', 'Config', 'Session']


class DictBackend(IMAPServer, BackendInterface):
class DictBackend(BackendInterface):
"""Defines a backend that uses an in-memory dictionary for example usage
and integration testing.
"""

def __init__(self, login: LoginProtocol, config: IMAPConfig) -> None:
super().__init__()
self._login = login
self._config = config

@property
def login(self) -> LoginProtocol:
return self._login

@property
def config(self) -> IMAPConfig:
return self._config

@classmethod
def add_subparser(cls, subparsers) -> None:
parser = subparsers.add_parser(
Expand All @@ -54,7 +66,7 @@ def __init__(self, args: Namespace, *, demo_data: bool,
self._demo_data = demo_data
self._demo_user = demo_user
self._demo_password = demo_password
self.set_cache: Dict[str, Tuple[MailboxSet, 'FilterSet']] = {}
self.set_cache: Dict[str, Tuple[MailboxSet, FilterSet]] = {}

@property
def demo_data(self) -> bool:
Expand Down Expand Up @@ -84,37 +96,14 @@ def parse_args(cls, args: Namespace) -> Mapping[str, Any]:
'demo_password': args.demo_password}


class FilterSet(EntryPointFilterSet[bytes], SingleFilterSet):

def __init__(self) -> None:
super().__init__()
self._sieve: Optional[bytes] = None

@property
def entry_point(self) -> str:
return 'sieve'

def set_sieve(self, sieve: bytes) -> None:
self._sieve = sieve

async def get_active(self) -> Optional[FilterInterface]:
if self._sieve is None:
return None
else:
try:
return self.get_filter(self._sieve)
except LookupError:
return None


class Session(BaseSession[Message]):
"""The session implementation for the dict backend."""

resource = __name__

def __init__(self, config: Config, mailbox_set: MailboxSet,
def __init__(self, owner: str, config: Config, mailbox_set: MailboxSet,
filter_set: FilterSet) -> None:
super().__init__()
super().__init__(owner)
self._config = config
self._mailbox_set = mailbox_set
self._filter_set = filter_set
Expand All @@ -141,7 +130,9 @@ async def login(cls, credentials: AuthenticationCredentials,
"""
user = credentials.authcid
password = cls._get_password(config, user)
if not credentials.check_secret(password):
if user != credentials.identity:
raise InvalidAuth()
elif not credentials.check_secret(password):
raise InvalidAuth()
mailbox_set, filter_set = config.set_cache.get(user, (None, None))
if not mailbox_set or not filter_set:
Expand All @@ -150,7 +141,7 @@ async def login(cls, credentials: AuthenticationCredentials,
if config.demo_data:
await cls._load_demo(mailbox_set, filter_set)
config.set_cache[user] = (mailbox_set, filter_set)
return cls(config, mailbox_set, filter_set)
return cls(credentials.identity, config, mailbox_set, filter_set)

@classmethod
def _get_password(cls, config: Config, user: str) -> str:
Expand All @@ -168,18 +159,19 @@ async def _load_demo(cls, mailbox_set: MailboxSet,
mbx_names = sorted(resource_listdir(cls.resource, 'demo'))
for name in mbx_names:
if name == 'sieve':
cls._load_demo_sieve(name, filter_set)
await cls._load_demo_sieve(name, filter_set)
elif name != 'INBOX':
mbx = await mailbox_set.add_mailbox(name)
await cls._load_demo_mailbox(name, mbx)

@classmethod
def _load_demo_sieve(cls, name: str, filter_set: FilterSet) -> None:
async def _load_demo_sieve(cls, name: str, filter_set: FilterSet) -> None:
path = os.path.join('demo', name)
sieve_stream = resource_stream(cls.resource, path)
with closing(sieve_stream):
sieve = sieve_stream.read()
filter_set.set_sieve(sieve)
await filter_set.put('demo', sieve)
await filter_set.set_active('demo')

@classmethod
async def _load_demo_mailbox(cls, name: str, mbx: MailboxData) -> None:
Expand Down
54 changes: 54 additions & 0 deletions pymap/backend/dict/filter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@

from typing import Optional, Tuple, Sequence, Dict

from pymap.filter import EntryPointFilterSet

__all__ = ['FilterSet']


class FilterSet(EntryPointFilterSet[bytes]):

def __init__(self) -> None:
super().__init__('sieve')
self._filters: Dict[str, bytes] = {}
self._active: Optional[str] = None

async def put(self, name: str, value: bytes) -> None:
self._filters[name] = value

async def delete(self, name: str) -> None:
if name not in self._filters:
raise KeyError(name)
elif name == self._active:
raise ValueError(name)
del self._filters[name]

async def rename(self, before_name: str, after_name: str) -> None:
if before_name not in self._filters:
raise KeyError(before_name)
elif after_name in self._filters:
raise KeyError(after_name)
self._filters[after_name] = self._filters[before_name]
del self._filters[before_name]
if self._active == before_name:
self._active = after_name

async def set_active(self, name: Optional[str]) -> None:
if name is None:
self._active = None
elif name not in self._filters:
raise KeyError(name)
else:
self._active = name

async def get(self, name: str) -> bytes:
return self._filters[name]

async def get_active(self) -> Optional[bytes]:
if self._active is None:
return None
else:
return self._filters[self._active]

async def get_all(self) -> Tuple[Optional[str], Sequence[str]]:
return self._active, list(self._filters.keys())
Loading

0 comments on commit d34b1ae

Please sign in to comment.