1. Chris McDonough
  2. pyramid_mailer

Commits

Chris McDonough  committed 3ec67dc

- ``pyramid_mailer.includeme`` function added for
``config.include('pyramid_mailer')`` support

- ``pyramid_mailer.testing`` module added for
``config.include('pyramid_mailer.testing')`` support.

- ``pyramid_mailer.get_mailer`` API added (see docs).

- ``pyramid_mailer.interfaces`` module readded (with marker IMailer interface
for ZCA registration).

- ``setup.cfg`` added with coverage parameters to allow for ``setup.py
nosetests --with-coverage``.

  • Participants
  • Parent commits adb38e5
  • Branches default

Comments (0)

Files changed (8)

File .hgignore

View file
  • Ignore whitespace
 *.orig
 *.cfg
 *.tox
-
+env26/
 
 *~
 

File CHANGES.txt

View file
  • Ignore whitespace
+Next release
+------------
+
+- ``pyramid_mailer.includeme`` function added for
+  ``config.include('pyramid_mailer')`` support
+
+- ``pyramid_mailer.testing`` module added for
+  ``config.include('pyramid_mailer.testing')`` support.
+
+- ``pyramid_mailer.get_mailer`` API added (see docs).
+
+- ``pyramid_mailer.interfaces`` module readded (with marker IMailer interface
+  for ZCA registration).
+
+- ``setup.cfg`` added with coverage parameters to allow for ``setup.py
+  nosetests --with-coverage``.

File docs/index.rst

View file
  • Ignore whitespace
 pyramid_mailer
 ==================
 
-**pyramid_mailer** is a package for the Pyramid framework to take the pain out of sending emails. It has the following features:
+**pyramid_mailer** is a package for the Pyramid framework to take the pain
+  out of sending emails. It has the following features:
 
-1. A wrapper around the low-level email functionality of standard Python. This includes handling multipart emails with both text and
-   HTML content, and file attachments.
+1. A wrapper around the low-level email functionality of standard
+   Python. This includes handling multipart emails with both text and HTML
+   content, and file attachments.
 
-2. The option of directly sending an email or adding it to the queue in your maildir.
+2. The option of directly sending an email or adding it to the queue in your
+maildir.
 
-3. Wrapping email sending in the transaction manager. If you have a view that sends a customer an email for example, and there is an
-   error in that view (for example, a database error) then this ensures that the email is not sent.
+3. Wrapping email sending in the transaction manager. If you have a view that
+   sends a customer an email for example, and there is an error in that view
+   (for example, a database error) then this ensures that the email is not
+   sent.
 
-4. A ``DummyMailer`` class to help with writing unit tests, or other situations where you want to avoid emails being sent accidentally
-   from a non-production install.
+4. A ``DummyMailer`` class to help with writing unit tests, or other
+   situations where you want to avoid emails being sent accidentally from a
+   non-production install.
 
-**pyramid_mailer** uses the `repoze_sendmail`_ package for general email sending, queuing and transaction management, and the `Lamson`_
-library for low-level multipart message encoding and wrapping. You do not have to install or run a Lamson mail service.
+**pyramid_mailer** uses the `repoze_sendmail`_ package for general email
+sending, queuing and transaction management, and the `Lamson`_ library for
+low-level multipart message encoding and wrapping. You do not have to install
+or run a Lamson mail service.
 
 Installation
 ------------
 
-Install using **pip install pyramid_mailer** or **easy_install pyramid_mailer**.
+Install using **pip install pyramid_mailer** or **easy_install
+pyramid_mailer**.
 
-If installing from source, untar/unzip, cd into the directory and do **python setup.py install**.
+If installing from source, untar/unzip, cd into the directory and do **python
+setup.py install**.
 
-The source repository is on `Bitbucket`_. Please report any bugs, issues or queries there. 
+The source repository is on `Bitbucket`_. Please report any bugs, issues or
+queries there.
 
 Installing on Windows
 ---------------------
 
-Some Windows users have reported issues installing `Lamson`_ due to some dependencies that do not work on Windows.
+Some Windows users have reported issues installing `Lamson`_ due to some
+dependencies that do not work on Windows.
 
-The best way to install on Windows is to install the individual packages using the `no dependencies` option::
+The best way to install on Windows is to install the individual packages
+using the `no dependencies` option::
 
     easy_install -N lamson chardet repoze.sendmail pyramid_mailer
 
 
-Getting started
----------------
+Getting Started (The Easier Way)
+--------------------------------
 
-To get started create an instance of :class:`pyramid_mailer.mailer.Mailer`::
+In your application's configuration stanza (where you create a Pyramid
+"Configurator"), use the ``config.include`` method::
+
+   config.include('pyramid_mailer')
+
+Thereafter in view code, use the ``pyramid_mailer.get_mailer`` API to obtain
+the configured mailer::
+
+   from pyramid_mailer import get_mailer
+   mailer = get_mailer(request)
+
+To send a message, you must first create a
+:class:`pyramid_mailer.message.Message` instance::
+
+    from pyramid_mailer.message import Message
+
+    message = Message(subject="hello world",
+                      sender="admin@mysite.com",
+                      recipients=["arthur.dent@gmail.com"],
+                      body="hello, arthur")
+
+The ``Message`` is then passed to the ``Mailer`` instance. You can either
+send the message right away::
+
+    mailer.send(message)
+
+or add it to your mail queue (a maildir on disk)::
+
+    mailer.send_to_queue(message)
+
+Usually you provide the ``sender`` to your ``Message`` instance. Often
+however a site might just use a single from address. If that is the case you
+can provide the ``default_sender`` to your ``Mailer`` and this will be used
+in throughout your application as the default if the ``sender`` is not
+otherwise provided.
+
+
+If you don't want to use transactions, you can side-step them by using
+**send_immediately**::
+
+    mailer.send_immediately(message, fail_silently=False)
+
+This will send the email immediately, outwith the transaction, so if it fails
+you have to deal with it manually. The ``fail_silently`` flag will swallow
+any connection errors silently - if it's not important whether the email gets
+sent.
+
+Getting Started (The Harder Way)
+--------------------------------
+
+To get started the harder way (without using ``config.include``), create an
+instance of :class:`pyramid_mailer.mailer.Mailer`::
 
     from pyramid_mailer.mailer import Mailer
 
     mailer = Mailer()
 
-The ``Mailer`` class can take a number of optional settings, detailed in :ref:`configuration`. It's a good idea to create a single ``Mailer`` instance for your application, and add it to your registry in your configuration setup::
+The ``Mailer`` class can take a number of optional settings, detailed in
+:ref:`configuration`. It's a good idea to create a single ``Mailer`` instance
+for your application, and add it to your registry in your configuration
+setup::
 
     config = Configurator(settings=settings)
     config.registry['mailer'] = Mailer.from_settings(settings)
     def my_view(request):
         mailer = request.registry['mailer']
 
-To send a message, you must first create a :class:`pyramid_mailer.message.Message` instance::
-
-    from pyramid_mailer.message import Message
-
-    message = Message(subject="hello world",
-                      sender="admin@mysite.com",
-                      recipients=["arthur.dent@gmail.com"],
-                      body="hello, arthur")
-
-The ``Message`` is then passed to the ``Mailer`` instance. You can either send the message right away::
-
-    mailer.send(message)
-
-or add it to your mail queue (a maildir on disk)::
-
-    mailer.send_to_queue(message)
-
-
-Usually you provide the ``sender`` to your ``Message`` instance. Often however a site might just use a single from address. If that is the case you can provide the ``default_sender`` to your ``Mailer`` and this will be used in throughout your application as the default if the ``sender`` is not otherwise provided.
-
-
-If you don't want to use transactions, you can side-step them by using **send_immediately**::
-
-    mailer.send_immediately(message, fail_silently=False)
-
-This will send the email immediately, outwith the transaction, so if it fails you have to deal with it manually. The ``fail_silently`` flag will swallow any connection errors silently - if it's not important whether the email gets sent.
+Note that the ``pyramid_mailer.get_mailer()`` API will not work if you
+construct and seat your own mailer in this way.
 
 .. _configuration:
 
 Configuration
 -------------
 
-If you create your ``Mailer`` instance using :meth:`pyramid_mailer.mailer.Mailer.from_settings`, you can pass the settings from your .ini file or other source. By default, the prefix is assumed to be `mail.` although you can use another prefix if you wish.
+If you create your ``Mailer`` instance using
+:meth:`pyramid_mailer.mailer.Mailer.from_settings` or
+``config.include('pyramid_mailer')``, you can pass the settings from your
+.ini file or other source. By default, the prefix is assumed to be `mail.`.
+If you use the ``config.include`` mechanism, to set another prefix, use the
+``pyramid_mailer.prefix`` key in the config file,
+e.g. ``pyramid_mailer.prefix = foo.``.  If you use the
+:meth:`pyramid_mailer.Mailer.Mailer.from_settings` or
+:func:`pyramid_mailer.mailer_factory_from_settings` API, these accept a
+prefix directly.
 
 =========================  ===============    =====================
 Setting                    Default            Description              
 **mail.debug**             **False**          SMTP debug level         
 =========================  ===============    =====================
 
-**Note:** SSL will only work with **pyramid_mailer** if you are using Python **2.6** or higher, as it uses the SSL additions to the ``smtplib`` package. While it may be possible to work around this if you have to use Python 2.5 or lower, **pyramid_mailer** does not support this out of the box.
+**Note:** SSL will only work with **pyramid_mailer** if you are using Python
+  **2.6** or higher, as it uses the SSL additions to the ``smtplib``
+  package. While it may be possible to work around this if you have to use
+  Python 2.5 or lower, **pyramid_mailer** does not support this out of the
+  box.
 
 Transactions
 ------------
 
-If you are using transaction management with your Pyramid application then **pyramid_mailer** will only send the emails (or add them to the mail queue) when the transactions are committed. 
+If you are using transaction management with your Pyramid application then
+**pyramid_mailer** will only send the emails (or add them to the mail queue)
+when the transactions are committed.
 
 For example::
 
 Attachments
 -----------
 
-Attachments are added using the :class:`pyramid_mailer.message.Attachment` class::
+Attachments are added using the :class:`pyramid_mailer.message.Attachment`
+class::
 
     from pyramid_mailer.message import Attachment
     from pyramid_mailer.message import Message
 
     message.attach(attachment)
 
-You can pass the data either as a string or file object, so the above code could be rewritten::
+You can pass the data either as a string or file object, so the above code
+could be rewritten::
 
     from pyramid_mailer.message import Attachment
     from pyramid_mailer.message import Message
 Unit tests
 ----------
 
-When running unit tests you probably don't want to actually send any emails inadvertently. However it's still useful to keep track of what emails would be sent in your tests. 
+When running unit tests you probably don't want to actually send any emails
+inadvertently. However it's still useful to keep track of what emails would
+be sent in your tests.
 
-Another case is if your site is in development and you want to avoid accidental sending of any emails to customers.
+Another case is if your site is in development and you want to avoid
+accidental sending of any emails to customers.
 
-In either case, the :class:`pyramid_mailer.mailer.DummyMailer` can be used::
+In either case, ``config.include('pyramid_mailer.testing')`` can be used to
+make the current mailer an instance of the
+:class:`pyramid_mailer.mailer.DummyMailer`::
+
+    from pyramid import testing
 
     class TestViews(unittest.TestCase):
+        def setUp(self):
+            self.config = testing.setUp()
+            self.config.include('pyramid_mailer.testing')
+
+        def tearDown(self):
+            testing.tearDown()
 
         def test_some_view(self):
-            
-            from pyramid.config import Configurator
             from pyramid.testing import DummyRequest
-            from pyramid_mailer.mailer import DummyMailer
-
-            config = Configurator()
-            mailer = DummyMailer()
-            config.registry['mailer'] = mailer
-
             request = DummyRequest()
+            mailer = get_mailer(request)
             response = some_view(request)
 
-The ``DummyMailer`` instance keeps track of emails "sent" in two properties: `queue` for emails send via :meth:`pyramid_mailer.mailer.Mailer.send_to_queue` and `outbox` for emails sent via :meth:`pyramid_mailer.mailer.Mailer.send`. Each stores the individual ``Message`` instances::
+The ``DummyMailer`` instance keeps track of emails "sent" in two properties:
+`queue` for emails send via
+:meth:`pyramid_mailer.mailer.Mailer.send_to_queue` and `outbox` for emails
+sent via :meth:`pyramid_mailer.mailer.Mailer.send`. Each stores the
+individual ``Message`` instances::
 
     self.assertEqual(len(mailer.outbox) == 1)
     self.assertEqual(mailer.outbox[0].subject == "hello world")
 
 When you send mail to a queue via
 :meth:`pyramid_mailer.Mailer.send_to_queue`, the mail will be placed into a
-``maildir`` directory specified by the ``queue_path`` parameter or setting to :class:`pyramid_mailer.mailer.Mailer`.  A
-separate process will need to be launched to monitor this maildir and take
-actions based on its state.  Such a program comes as part of
-`repoze_sendmail`_ (a dependency of the ``pyramid_mailer`` package).  It is
-known as ``qp``.  ``qp`` will be installed into your Python (or virtualenv)
-``bin`` or ``Scripts`` directory when you install ``repoze_sendmail``.
+``maildir`` directory specified by the ``queue_path`` parameter or setting to
+:class:`pyramid_mailer.mailer.Mailer`.  A separate process will need to be
+launched to monitor this maildir and take actions based on its state.  Such a
+program comes as part of `repoze_sendmail`_ (a dependency of the
+``pyramid_mailer`` package).  It is known as ``qp``.  ``qp`` will be
+installed into your Python (or virtualenv) ``bin`` or ``Scripts`` directory
+when you install ``repoze_sendmail``.
 
 You'll need to arrange for ``qp`` to be a long-running process that monitors
 the maildir state.::
     
 .. autofunction:: mailer_factory_from_settings
 
+.. autofunction:: get_mailer
+
 .. module:: pyramid_mailer.mailer
 
 .. autoclass:: Mailer

File pyramid_mailer/__init__.py

View file
  • Ignore whitespace
 from pyramid_mailer.mailer import Mailer
+from pyramid_mailer.interfaces import IMailer
 
 def mailer_factory_from_settings(settings, prefix='mail.'):
     """
     :versionadded: 0.2.2
     """
     return Mailer.from_settings(settings, prefix)
+
+def includeme(config):
+    settings = config.settings
+    prefix = settings.get('pyramid_mailer.prefix', 'mail.')
+    mailer = mailer_factory_from_settings(settings, prefix=prefix)
+    config.registry.registerUtility(mailer, IMailer)
+
+def get_mailer(request):
+    """Obtain a mailer previously registered via
+    ``config.include('pyramid_mailer')`` or
+    ``config.include('pyramid_mailer.testing')``.
+
+    :versionadded: 0.2.3
+    """
+    registry = getattr(request, 'registry', None)
+    if registry is None:
+        registry = request
+    return registry.getUtility(IMailer)

File pyramid_mailer/interfaces.py

View file
  • Ignore whitespace
+from zope.interface import Interface
+
+class IMailer(Interface):
+    pass
+    

File pyramid_mailer/mailer.py

View file
  • Ignore whitespace
         # support disabled if pre-2.6
         smtp = smtplib.SMTP_SSL
         ssl_support = True
-    except AttributeError:
+    except AttributeError: # pragma: no cover
         smtp = smtplib.SMTP
         ssl_support = False
 
 
     def smtp_factory(self):
 
-        if self.ssl_support is False:
+        if self.ssl_support is False: # pragma: no cover
             return super(SMTP_SSLMailer, self).smtp_factory()
 
         connection = self.smtp(self.hostname, str(self.port),
         kwarg_names = [prefix + k for k in (
                        'host', 'port', 'username',
                        'password', 'tls', 'ssl', 'keyfile', 
-                       'certfile', 'queue_path', 'debug')]
+                       'certfile', 'queue_path', 'debug', 'default_sender')]
         
         size = len(prefix)
 

File pyramid_mailer/testing.py

View file
  • Ignore whitespace
+from pyramid_mailer.interfaces import IMailer
+from pyramid_mailer.mailer import DummyMailer
+
+def includeme(config):
+    mailer = DummyMailer()
+    config.registry.registerUtility(mailer, IMailer)

File pyramid_mailer/tests.py

View file
  • Ignore whitespace
 
 import unittest
 
+from pyramid import testing
+
 class TestAttachment(unittest.TestCase):
 
     def test_data_from_string(self):
         from pyramid_mailer.mailer import Mailer
         from pyramid_mailer.message import Message
 
-        mailer = Mailer()
+        mailer = Mailer(host='localhost', port='28322')
 
         msg = Message(subject="testing",
                       sender="sender@example.com",
         self.assert_(mailer.queue_delivery.queuePath == '/tmp')
         self.assert_(mailer.direct_delivery.mailer.debug_smtp == 1)
 
+class Test_get_mailer(unittest.TestCase):
+    def _callFUT(self, arg):
+        from pyramid_mailer import get_mailer
+        return get_mailer(arg)
+
+    def test_arg_is_registry(self):
+        mailer = object()
+        registry = DummyRegistry(mailer)
+        result = self._callFUT(registry)
+        self.assertEqual(result, mailer)
+
+    def test_arg_is_request(self):
+        class Dummy(object):
+            pass
+        mailer = object()
+        registry = DummyRegistry(mailer)
+        request = Dummy()
+        request.registry = registry
+        result = self._callFUT(request)
+        self.assertEqual(result, mailer)
+
+class Test_includeme(unittest.TestCase):
+    def _callFUT(self, config):
+        from pyramid_mailer import includeme
+        includeme(config)
+
+    def test_it_default_prefix(self):
+        from pyramid_mailer.interfaces import IMailer
+        registry = DummyRegistry()
+        settings = {'mail.default_sender':'sender'}
+        config = DummyConfig(registry, settings)
+        self._callFUT(config)
+        self.assertEqual(registry.registered[IMailer].default_sender, 'sender')
+
+    def test_it_specified_prefix(self):
+        from pyramid_mailer.interfaces import IMailer
+        registry = DummyRegistry()
+        settings = {'pyramid_mailer.prefix':'foo.',
+                    'foo.default_sender':'sender'}
+        config = DummyConfig(registry, settings)
+        self._callFUT(config)
+        self.assertEqual(registry.registered[IMailer].default_sender, 'sender')
+
+class Test_testing_includeme(unittest.TestCase):
+    def _callFUT(self, config):
+        from pyramid_mailer.testing import includeme
+        includeme(config)
+
+    def test_it(self):
+        from pyramid_mailer.interfaces import IMailer
+        from pyramid_mailer.mailer import DummyMailer
+        registry = DummyRegistry()
+        config = DummyConfig(registry, {})
+        self._callFUT(config)
+        self.assertEqual(registry.registered[IMailer].__class__, DummyMailer)
+        
+class DummyConfig(object):
+    def __init__(self, registry, settings):
+        self.registry = registry
+        self.settings = settings
+
+class DummyRegistry(object):
+    def __init__(self, result=None):
+        self.result = result
+        self.registered = {}
+
+    def getUtility(self, iface):
+        return self.result
+
+    def registerUtility(self, impl, iface):
+        self.registered[iface] = impl
+
+    
+
+