Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow arbitrary data to be stored in the base notification. #1

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
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
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

METADATA = dict(
name='yell',
version=yell.__version__,
version="0.5.0",
author='Alen Mujezinovic',
author_email='[email protected]',
description='User notification library with pluggable backends. Compatible with popular frameworks such as Django, Flask, Celery.',
Expand Down
86 changes: 61 additions & 25 deletions yell/__init__.py
Original file line number Diff line number Diff line change
@@ -1,66 +1,102 @@
__version__ = "0.3.2"
# -*- coding: utf-8 -*-

"""
yell
~~~~~~~~~~~~~
Pluggable notifications.
"""

from __future__ import print_function
from __future__ import division
from __future__ import absolute_import
from __future__ import unicode_literals

from future import standard_library
standard_library.install_aliases()

from builtins import *
from builtins import object
from future.utils import with_metaclass

__version__ = "0.4.0"


class Reg(object):
""" Registry mapping notification names to backends """

notifications = {}

def __new__(cls):
obj = super(Reg, cls).__new__(cls)
obj.notifications = {}
return obj


registry = Reg()

import registry

class MetaNotification(type):
"""
"""
Metaclass that stores all notifications in the registry.
"""
def __new__(cls, name, bases, attrs):
Notification = super(MetaNotification, cls).__new__(cls, name, bases, attrs)

if Notification.name is not None:
registry.notifications[Notification.name] = registry.notifications.get(Notification.name, []) + [Notification]
def __init__(self, name, bases, attrs):
super(MetaNotification, self).__init__(name, bases, attrs)
reg = registry.notifications
if self.name is not None:
reg[self.name] = reg.get(self.name, []) + [self]

sendfn = lambda *args, **kwargs: notify(Notification.name, backends = [Notification], *args, **kwargs)
setattr(Notification, 'send', staticmethod(sendfn))
sendfn = lambda *args, **kwargs: notify(self.name, backends=[self], *args, **kwargs)
setattr(self, 'send', staticmethod(sendfn))

return Notification
return


class Notification(object):
class Notification(with_metaclass(MetaNotification, object)):
"""
Base class for any kind of notifications. Inherit from this class to create
your own notification types and backends.
your own notification types and backends.

Subclasses need to implement :meth:`notify`.
"""
__metaclass__ = MetaNotification

name = None
"""
A name for this notification.
"""


data = {}
"""
Allow arbitrary data to be stored in the base notification.
"""

def notify(self, *args, **kwargs):
"""
A method that delivers a notification.
"""
raise NotImplementedError


def notify(name, *args, **kwargs):
"""
Send notifications. If ``backends==None``, all backends with the same name
will be used to deliver a notification.
will be used to deliver a notification.

If ``backends`` is a list, only the specified backends will be used.

:param name: The notification to send
:param backends: A list of backends to be used or ``None`` to use all associated backends
"""
assert name in registry.notifications, "'{0}' is not a valid notification.".format(repr(name))

backends = kwargs.pop('backends', None)

if backends is None:
backends = registry.notifications[name]

results = []

for Backend in backends:
backend = Backend()
results.append(backend.notify(*args, **kwargs))

return results


return results
58 changes: 33 additions & 25 deletions yell/backends/celery.py
Original file line number Diff line number Diff line change
@@ -1,69 +1,77 @@
from __future__ import absolute_import
from celery.task import Task
from __future__ import print_function
from __future__ import division
from __future__ import unicode_literals

from future import standard_library
standard_library.install_aliases()
from builtins import *
from .celery.task import Task
from yell import Notification, notify, registry


class CeleryNotificationTask(Task):
""" Dispatch and run the notification. """
def run(self, name=None, backend=None, *args, **kwargs):
"""
The Celery task.
The Celery task.

Delivers the notification via all backends returned by :param:`backend`.
"""
assert name is not None, "No 'name' specified to notify"
assert backend is not None, "No 'backend' specified to notify with"

backends = backend().get_backends(*args, **kwargs)
notify(name, backends=backends, *args, **kwargs)



class CeleryNotification(Notification):
"""
Delivers notifications through Celery.
"""
Delivers notifications through Celery.

:example:

::

from yell import notify, Notification

class EmailNotification(Notification):
name = 'async'
def notify(self, *args, **kwargs):
# Deliver email
# Deliver email

class DBNotification(Notification):
name = 'async'
def notify(self, *args, **kwargs):
# Save to database

class AsyncNotification(CeleryNotification):
name = 'async'
name = 'async'

notify('async', backends = [AsyncNotification],
text = "This notification is routed through Celery before being sent and saved")

In the above example when calling :attr:`yell.notify` will invoke ``EmailNotification`` and
``DBNotification`` once the task was delivered through Celery.

"""
name = None
"""
"""
The name of this notification. Override in subclasses.
"""

def get_backends(self, *args, **kwargs):
"""
Return all backends the task should use to deliver notifications.
By default all backends with the same :attr:`name` except for subclasses
of :class:`CeleryNotifications` will be used.
"""
return filter(lambda cls: not issubclass(cls, self.__class__), registry.notifications[self.name])
return [
cls for cls in registry.notifications[self.name] if not issubclass(cls, self.__class__)
]


def notify(self, *args, **kwargs):
"""
"""
Dispatches the notification to Celery
"""
return CeleryNotificationTask.delay(name=self.name, backend=self.__class__, *args, **kwargs)


Loading