# -*- coding: utf-8 -*-
#---Header---------------------------------------------------------------------
# This file is part of Message For You Sir (m4us).
# Copyright © 2009-2012 Krys Lawrence
#
# Message For You Sir is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or (at your
# option) any later version.
#
# Message For You Sir is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
# for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with Message For You Sir. If not, see <http://www.gnu.org/licenses/>.
"""Provides various kinds of `post office` classes for `message` routing.
`Post offices` are responsible for receiving posted `messages` from
`coroutines` and routing them to message queues of other `coroutines` based on
the `links` that have been registered.
"""
#---Imports--------------------------------------------------------------------
#--- Standard library imports
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
## pylint: disable=W0622, W0611
from future_builtins import ascii, filter, hex, map, oct, zip ## NOQA
## pylint: enable=W0622, W0611
#--- Third-party imports
## pylint: disable=F0401
from zope import interface
## pylint: enable=F0401
#--- Project imports
from m4us.core import exceptions, interfaces
#---Globals--------------------------------------------------------------------
#---Functions------------------------------------------------------------------
#---Classes--------------------------------------------------------------------
@interface.implementer(interfaces.IPostOffice)
@interface.provider(interfaces.IPostOfficeFactory)
[docs]class PostOffice(object):
"""The standard `post office` class used for `message` routing.
This is a simple and reasonably efficient implementation of
:class:`~m4us.core.interfaces.IPostOffice`. Unless you have a special
need, this is this `post office` you want to use.
:implements: :class:`m4us.core.interfaces.IPostOffice`
:provides: :class:`m4us.core.interfaces.IPostOfficeFactory`
"""
def __init__(self, link_ignores_duplicates=False,
unlink_ignores_missing=False):
"""See class docstring for this method's documentation."""
self._ignore_duplicates = link_ignores_duplicates
self._ignore_missing = unlink_ignores_missing
# Note: collections.defaultdict is not used here because keys should
# not be added through casual querying, but only explicitly, like
# through link() for example.
self._links = {}
self._message_queues = {}
self._registered_sinks = set()
[docs] def register(self, first_link, *other_links):
"""Register `links` between `coroutines`.
.. seealso::
The :class:`~m4us.core.interfaces.IPostOffice` `interface` for
details about this method.
"""
for source, outbox, sink, inbox in (first_link,) + other_links:
source_outbox, sink_inbox = (source, outbox), (sink, inbox)
if sink_inbox in self._links.setdefault(source_outbox, set()):
if self._ignore_duplicates:
continue
## pylint: disable=E1101
raise exceptions.LinkExistsError(link=sink_inbox)
## pylint: enable=E1101
self._links[source_outbox].add(sink_inbox)
self._message_queues.setdefault(sink, [])
self._registered_sinks.add(sink)
[docs] def unregister(self, first_link, *other_links):
"""Unregister previously registered `links`.
.. seealso::
The :class:`~m4us.core.interfaces.IPostOffice` `interface` for
details about this method.
"""
for source, outbox, sink, inbox in (first_link,) + other_links:
source_outbox, sink_inbox = (source, outbox), (sink, inbox)
try:
self._links[source_outbox].remove(sink_inbox)
except KeyError:
if self._ignore_missing:
continue
## pylint: disable=E1101
raise exceptions.NoLinkError(source_outbox=source_outbox,
sink_inbox=sink_inbox)
## pylint: enable=E1101
if not self._links[source_outbox]:
del self._links[source_outbox]
# If no sources use the sink anymore, unregister it.
self._registered_sinks = set(sink for source_outbox in self._links
for sink, _ in self._links[source_outbox])
[docs] def post(self, source, outbox, message):
"""Post a `message` from the `source` `outbox`.
.. seealso::
The :class:`~m4us.core.interfaces.IPostOffice` `interface` for
details about this method.
"""
source_outbox = (source, outbox)
if source_outbox not in self._links:
## pylint: disable=E1101
raise exceptions.NoLinkError('The source/outbox '
'"{source_outbox!r}" has not been linked.',
source_outbox=source_outbox)
## pylint: enable=E1101
for sink, inbox in self._links[source_outbox]:
self._message_queues[sink].append((inbox, message))
[docs] def retrieve(self, sink):
"""Retrieve all outstanding `messages` for a `sink` `coroutine`.
.. seealso::
The :class:`~m4us.core.interfaces.IPostOffice` `interface` for
details about this method.
"""
try:
messages = self._message_queues[sink]
except KeyError:
## pylint: disable=E1101
raise exceptions.NotASinkError(coroutine=sink)
## pylint: enable=E1101
# If the component has been unregistered, remove it's message queue.
if sink not in self._registered_sinks:
del self._message_queues[sink]
else:
# Note: We replace the message queue with a new empty one so that
# the existing messages can be processed as a batch, but that any
# new messages that come in will be handled the next time
# retrieve() is called. This ensures that the returned
# messages iterable is always of finite length, preventing
# infinite looping.
self._message_queues[sink] = []
return messages
#---Module initialization------------------------------------------------------
#---Late Imports---------------------------------------------------------------
#---Late Globals---------------------------------------------------------------
#---Late Functions-------------------------------------------------------------
#---Late Classes---------------------------------------------------------------
#---Late Module initialization-------------------------------------------------