From c879132be9a491652c50a41a61ab0313d55969e5 Mon Sep 17 00:00:00 2001 From: Alen Mujezinovic Date: Fri, 29 Jun 2012 16:12:57 +0100 Subject: [PATCH] Fixed the API to be a bit saner --- README.rst | 45 ++++++++++++++++++++++------------- docs/index.rst | 49 ++++++++++++++++---------------------- yell/__init__.py | 34 +++++++++++++-------------- yell/backends/celery.py | 52 ++++++++++++++++++++--------------------- yell/backends/django.py | 8 +++---- yell/decorators.py | 36 ++++++++++++++-------------- yell/registry.py | 4 ++-- yell/tests.py | 42 ++++++++++++++++----------------- 8 files changed, 137 insertions(+), 133 deletions(-) diff --git a/README.rst b/README.rst index ae4ac33..7cf72c4 100644 --- a/README.rst +++ b/README.rst @@ -9,50 +9,63 @@ Pluggable notifications for your Python apps. The full documentation is available `here `_. -Using yelling decorators ------------------------- +Using notification decorators +----------------------------- :: - from yell.decorators import yelling + from yell import notify + from yell.decorators import notification - @yelling(name = 'buffalo') + @notification(name = 'buffalo') def buffalo_printer(message): print message - @yelling(name = 'buffalo') + @notification(name = 'buffalo') def buffalo_saver(message): save(message) - yell("buffalo", _("Buffalo buffalo Buffalo buffalo buffalo buffalo Buffalo buffalo")) + notify("buffalo", _("Buffalo buffalo Buffalo buffalo buffalo buffalo Buffalo buffalo")) -Using yelling classes ---------------------- +Using notification classes +-------------------------- -:: +:: - from yell import Yell, yell + from yell import Notification, notify - class Buffalo(Yell): + class Buffalo(Notification): name = "buffalo" message = _("Buffalo buffalo Buffalo buffalo buffalo buffalo Buffalo buffalo") - def yell(self, *args, **kwargs): + def notify(self, *args, **kwargs): print self.message class BuffaloEmail(Buffalo): - def yell(self, *args, **kwargs): + def notify(self, *args, **kwargs): send_mail("Buffalo", self.message, 'buffalo@example.com', [kwargs.get('user').email]) class BuffaloDatabase(Buffalo): - def yell(self, *args, **kwargs): + def notify(self, *args, **kwargs): BuffaloModel.objects.create(user = kwargs.get('user')) # The default behaviour is to use every notification backend with the same # name - yell("buffalo", user = User.objects.get(id=1)) + notify("buffalo", user = User.objects.get(id=1)) # Only send emails - yell("buffalo", user = User.objects.get(id=1), backends = [BuffaloEmail]) + notify("buffalo", user = User.objects.get(id=1), backends = [BuffaloEmail]) + + +Changelog +--------- + +**v0.2** + +* Made the API saner to use (*backwards incompatible*): + - ``yell.Yell`` became ``yell.Notification`` + - ``yell.yell`` became ``yell.notify`` + - ``yell.decorators.yelling`` became ``yell.decorators.notification`` + diff --git a/docs/index.rst b/docs/index.rst index 26e31bc..3882ff5 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -4,65 +4,56 @@ yell Pluggable notifications for your Python apps. -`yell` is not a notification storage or delivery backend but a set of APIs that make it easy to add your own delivery mechanisms. +`yell` is not a notification storage or delivery backend but a set of APIs that make it easy to add your own delivery mechanisms. +The full documentation is available `here `_. -Using yelling decorators ------------------------- + +Using notification decorators +----------------------------- :: - from yell.decorators import yelling + from yell import notify + from yell.decorators import notification - @yelling(name = 'buffalo') + @notification(name = 'buffalo') def buffalo_printer(message): print message - @yelling(name = 'buffalo') + @notification(name = 'buffalo') def buffalo_saver(message): save(message) - yell("buffalo", _("Buffalo buffalo Buffalo buffalo buffalo buffalo Buffalo buffalo")) + notify("buffalo", _("Buffalo buffalo Buffalo buffalo buffalo buffalo Buffalo buffalo")) -Using yelling classes ---------------------- +Using notification classes +-------------------------- -:: +:: - from yell import Yell, yell + from yell import Notification, notify - class Buffalo(Yell): + class Buffalo(Notification): name = "buffalo" message = _("Buffalo buffalo Buffalo buffalo buffalo buffalo Buffalo buffalo") - def yell(self, *args, **kwargs): + def notify(self, *args, **kwargs): print self.message class BuffaloEmail(Buffalo): - def yell(self, *args, **kwargs): + def notify(self, *args, **kwargs): send_mail("Buffalo", self.message, 'buffalo@example.com', [kwargs.get('user').email]) class BuffaloDatabase(Buffalo): - def yell(self, *args, **kwargs): + def notify(self, *args, **kwargs): BuffaloModel.objects.create(user = kwargs.get('user')) # The default behaviour is to use every notification backend with the same # name - yell("buffalo", user = User.objects.get(id=1)) + notify("buffalo", user = User.objects.get(id=1)) # Only send emails - yell("buffalo", user = User.objects.get(id=1), backends = [BuffaloEmail]) - - - -API -=== - -.. toctree:: - :maxdepth: 5 - - yell - yell.backends - + notify("buffalo", user = User.objects.get(id=1), backends = [BuffaloEmail]) diff --git a/yell/__init__.py b/yell/__init__.py index 4d9fba5..c4396b5 100644 --- a/yell/__init__.py +++ b/yell/__init__.py @@ -1,62 +1,62 @@ -__version__ = "0.1" +__version__ = "0.2" import registry -class MetaYell(type): +class MetaNotification(type): """ - Metaclass that stores all yells in the registry. + Metaclass that stores all notifications in the registry. """ def __new__(cls, name, bases, attrs): - Yell = super(MetaYell, cls).__new__(cls, name, bases, attrs) + Notification = super(MetaNotification, cls).__new__(cls, name, bases, attrs) - if Yell.name is not None: - registry.yells[Yell.name] = registry.yells.get(Yell.name, []) + [Yell] + if Notification.name is not None: + registry.notifications[Notification.name] = registry.notifications.get(Notification.name, []) + [Notification] - return Yell + return Notification -class Yell(object): +class Notification(object): """ Base class for any kind of notifications. Inherit from this class to create your own notification types and backends. - Subclasses need to implement :meth:`yell`. + Subclasses need to implement :meth:`notify`. """ - __metaclass__ = MetaYell + __metaclass__ = MetaNotification name = None """ - A name for this yell. + A name for this notification. """ - def yell(self, *args, **kwargs): + def notify(self, *args, **kwargs): """ A method that delivers a notification. """ raise NotImplementedError -def yell(name, *args, **kwargs): +def notify(name, *args, **kwargs): """ Send notifications. If ``backends==None``, all backends with the same name will be used to deliver a notification. If ``backends`` is a list, only the specified backends will be used. - :param name: The yell to send + :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.yells, "'{0}' is not a valid yell.".format(repr(name)) + 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.yells[name] + backends = registry.notifications[name] results = [] for Backend in backends: backend = Backend() - results.append(backend.yell(*args, **kwargs)) + results.append(backend.notify(*args, **kwargs)) return results diff --git a/yell/backends/celery.py b/yell/backends/celery.py index e97eeaf..c547f72 100644 --- a/yell/backends/celery.py +++ b/yell/backends/celery.py @@ -1,22 +1,22 @@ from __future__ import absolute_import from celery.task import Task -from yell import Yell, yell, registry +from yell import Notification, notify, registry -class CeleryYellingTask(Task): - """ Dispatch and run the yelling """ - def run(self, name=None, yeller=None, *args, **kwargs): +class CeleryNotificationTask(Task): + """ Dispatch and run the notification. """ + def run(self, name=None, backend=None, *args, **kwargs): """ The Celery task. - Delivers the notification via all backends returned by :param:`yeller`. + Delivers the notification via all backends returned by :param:`backend`. """ - assert name is not None, "No 'name' specified to yell" - assert yeller is not None, "No 'yeller' specified to yell with" + assert name is not None, "No 'name' specified to notify" + assert backend is not None, "No 'backend' specified to notify with" - backends = yeller().get_backends(*args, **kwargs) - yell(name, backends=backends, *args, **kwargs) + backends = backend().get_backends(*args, **kwargs) + notify(name, backends=backends, *args, **kwargs) -class CeleryYell(Yell): +class CeleryNotification(Notification): """ Delivers notifications through Celery. @@ -24,26 +24,26 @@ class CeleryYell(Yell): :: - from yell import yell, Yell + from yell import notify, Notification - class EmailYell(Yell): - yell = 'async' - def yell(self, *args, **kwargs): + class EmailNotification(Notification): + name = 'async' + def notify(self, *args, **kwargs): # Deliver email - class DBYell(Yell): - yell = 'async' - def yell(self, *args, **kwargs): + class DBNotification(Notification): + name = 'async' + def notify(self, *args, **kwargs): # Save to database - class AsyncYell(CeleryYell): - yell = 'async' + class AsyncNotification(CeleryNotification): + name = 'async' - yell('async', backends = [AsyncYell], + notify('async', backends = [AsyncNotification], text = "This notification is routed through Celery before being sent and saved") - In the above example when calling :attr:`yell.yell` will invoke ``EmailYell`` and - ``DBYell`` once the task was delivered through Celery. + In the above example when calling :attr:`yell.notify` will invoke ``EmailNotification`` and + ``DBNotification`` once the task was delivered through Celery. """ name = None @@ -55,14 +55,14 @@ 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:`CeleryYell` will be used. + of :class:`CeleryNotifications` will be used. """ - return filter(lambda cls: not isinstance(cls, self.__class__), registry.yells[self.yell]) + return filter(lambda cls: not isinstance(cls, self.__class__), registry.notifications[self.name]) - def yell(self, *args, **kwargs): + def notify(self, *args, **kwargs): """ Dispatches the notification to Celery """ - return CeleryYellingTask.delay(name=self.name, yeller=self.__class__, *args, **kwargs) + return CeleryNotificationTask.delay(name=self.name, backend=self.__class__, *args, **kwargs) diff --git a/yell/backends/django.py b/yell/backends/django.py index 6214573..5a15534 100644 --- a/yell/backends/django.py +++ b/yell/backends/django.py @@ -3,11 +3,11 @@ from django.conf import settings from django import template from django.core.mail import send_mail, EmailMultiAlternatives -from yell import Yell +from yell import Notification import mimetypes -class EmailBackend(Yell): +class EmailBackend(Notification): """ Send emails via :attr:`django.core.mail.send_mail` """ @@ -36,7 +36,7 @@ def get_from(self, *args, **kwargs): def get_to(self, *args, **kwargs): return kwargs.get('to') - def yell(self, *args, **kwargs): + def notify(self, *args, **kwargs): return send_mail( self.get_subject(*args, **kwargs), @@ -68,7 +68,7 @@ def get_body(self, *args, **kwargs): def get_default_body(self, *args, **kwargs): return self.get_body(*args, **kwargs)[self.default_content_type] - def yell(self, *args, **kwargs): + def notify(self, *args, **kwargs): message = EmailMultiAlternatives( self.get_subject(*args, **kwargs), self.get_default_body(*args, **kwargs), diff --git a/yell/decorators.py b/yell/decorators.py index b158930..f6aefd9 100644 --- a/yell/decorators.py +++ b/yell/decorators.py @@ -1,15 +1,15 @@ -from yell import Yell, yell +from yell import Notification, notify -class DecoratedYell(Yell): +class DecoratedNotification(Notification): func = None """ The function that has been decorated. """ - def yell(self, *args, **kwargs): + def notify(self, *args, **kwargs): return self.func(*args, **kwargs) -def yelling(name=None, backends=None): +def notification(name=None, backends=None): """ Decorator to simplify creation of notification backends. @@ -18,7 +18,7 @@ def yelling(name=None, backends=None): :: from yell.decorators import notification - from yell import yell + from yell import notify @notification('like') def like_email(user, obj): @@ -34,38 +34,38 @@ def like(*args, **kwargs): pass # Use all backends - yell('like', user = user, obj = obj) - like.yell(user = user, obj = obj) + notify('like', user = user, obj = obj) + like.notify(user = user, obj = obj) # Only use the email backend - like_email.yell_once(user = user, obj = obj) + like_email.notify_once(user = user, obj = obj) """ def wrapper(func): def funcwrapper(self, *args, **kwargs): - """ Wrapping the yelling function so it doesn't receive `self` """ + """ Wrapping the notification function so it doesn't receive `self` """ return func(*args, **kwargs) - YellCls = type('%sYell' % name.lower().title(), (DecoratedYell,), { + NotificationCls = type('%sNotification' % name.lower().title(), (DecoratedNotification,), { 'func': funcwrapper, 'name': name }) - def yell_all(*args, **kwargs): + def notify_all(*args, **kwargs): """ - Sends this yell off to every backend with the configured name. + Sends this notification off to every backend with the configured name. """ - return yell(name, *args, **kwargs) + return notify(name, *args, **kwargs) - def yell_once(*args, **kwargs): + def notify_once(*args, **kwargs): """ - Sends this yell off only to the current backend. + Sends this notification off only to the current backend. """ - return yell(name, backends=[YellCls], *args, **kwargs) + return notify(name, backends=[NotificationCls], *args, **kwargs) - func.yell = yell_all - func.yell_once = yell_once + func.notify = notify_all + func.notify_once = notify_once return func diff --git a/yell/registry.py b/yell/registry.py index a59f0f2..9e33cce 100644 --- a/yell/registry.py +++ b/yell/registry.py @@ -1,2 +1,2 @@ -yells = {} -""" Registry mapping yell names to backends """ +notifications = {} +""" Registry mapping notification names to backends """ diff --git a/yell/tests.py b/yell/tests.py index f92e472..eeeae0d 100644 --- a/yell/tests.py +++ b/yell/tests.py @@ -1,5 +1,5 @@ -from yell import yell, Yell -from yell.decorators import yelling +from yell import notify, Notification +from yell.decorators import notification try: @@ -7,24 +7,24 @@ except ImportError: import unittest -@yelling(name='decorator') -def decorator_yell0(*args, **kwargs): +@notification(name='decorator') +def decorator_notification0(*args, **kwargs): return 'Decorator 0', args, kwargs -@yelling(name='decorator') -def decorator_yell1(*args, **kwargs): +@notification(name='decorator') +def decorator_notification1(*args, **kwargs): return 'Decorator 1', args, kwargs -class ClassYell0(Yell): +class ClassNotification0(Notification): name = 'class' - def yell(self, *args, **kwargs): + def notify(self, *args, **kwargs): return 'Class 0', args, kwargs -class ClassYell1(Yell): +class ClassNotification1(Notification): name = 'class' - def yell(self, *args, **kwargs): + def notify(self, *args, **kwargs): return 'Class 1', args, kwargs class AssertMixin(object): @@ -40,35 +40,35 @@ def _assert_results(self, results): self.assertTrue('Kwarg2' in result[2].values()) -class TestDecoratorYell(AssertMixin, unittest.TestCase): +class TestDecoratorNotification(AssertMixin, unittest.TestCase): retval = 'Decorator' - def test_yelling_with_decorator(self): - results = yell('decorator', 'Arg1', 'Arg2', kwarg1='Kwarg1', kwarg2='Kwarg2') + def test_notifying_with_decorator(self): + results = notify('decorator', 'Arg1', 'Arg2', kwarg1='Kwarg1', kwarg2='Kwarg2') self.assertEqual(2, len(results)) self._assert_results(results) - results = decorator_yell0.yell('Arg1', 'Arg2', kwarg1='Kwarg1', kwarg2='Kwarg2') + results = decorator_notification0.notify('Arg1', 'Arg2', kwarg1='Kwarg1', kwarg2='Kwarg2') self._assert_results(results) self.assertEqual(2, len(results)) - def test_yelling_once_with_decorator(self): - results = decorator_yell0.yell_once('Arg1', 'Arg2', kwarg1='Kwarg1', kwarg2='Kwarg2') + def test_notifying_once_with_decorator(self): + results = decorator_notification0.notify_once('Arg1', 'Arg2', kwarg1='Kwarg1', kwarg2='Kwarg2') self.assertEqual(1, len(results)) self._assert_results(results) -class TestClassYell(AssertMixin, unittest.TestCase): +class TestClassNotification(AssertMixin, unittest.TestCase): retval = 'Class' - def test_yelling_with_class(self): - results = yell('class', 'Arg1', 'Arg2', kwarg1='Kwarg1', kwarg2='Kwarg2') + def test_notifying_with_class(self): + results = notify('class', 'Arg1', 'Arg2', kwarg1='Kwarg1', kwarg2='Kwarg2') self.assertEqual(2, len(results)) self._assert_results(results) - def test_yelling_once_with_class(self): - results = yell('class', 'Arg1', 'Arg2', kwarg1='Kwarg1', kwarg2='Kwarg2', backends=[ClassYell0]) + def test_notifying_once_with_class(self): + results = notify('class', 'Arg1', 'Arg2', kwarg1='Kwarg1', kwarg2='Kwarg2', backends=[ClassNotification0]) self.assertEqual(1, len(results)) self._assert_results(results)