Skip to content

Commit

Permalink
- pyramid_mailer.includeme function added for
Browse files Browse the repository at this point in the history
  ``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``.
  • Loading branch information
mcdonc committed Mar 3, 2011
1 parent 0508cac commit 60e3086
Show file tree
Hide file tree
Showing 8 changed files with 264 additions and 68 deletions.
2 changes: 1 addition & 1 deletion .hgignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ syntax: glob
*.orig
*.cfg
*.tox

env26/

*~

Expand Down
16 changes: 16 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
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``.
199 changes: 136 additions & 63 deletions docs/index.rst
Original file line number Diff line number Diff line change
@@ -1,66 +1,70 @@
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::

from pyramid_mailer.mailer import Mailer

mailer = Mailer()
config.include('pyramid_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::
Thereafter in view code, use the ``pyramid_mailer.get_mailer`` API to obtain
the configured mailer::

config = Configurator(settings=settings)
config.registry['mailer'] = Mailer.from_settings(settings)
from pyramid_mailer import get_mailer
mailer = get_mailer(request)

or alternatively::

from pyramid_mailer import mailer_factory_from_settings
config.registry['mailer'] = mailer_factory_from_settings(settings)

You can then access your mailer in a view::

def my_view(request):
mailer = request.registry['mailer']

To send a message, you must first create a :class:`pyramid_mailer.message.Message` instance::
To send a message, you must first create a
:class:`pyramid_mailer.message.Message` instance::

from pyramid_mailer.message import Message

Expand All @@ -69,30 +73,78 @@ To send a message, you must first create a :class:`pyramid_mailer.message.Messag
recipients=["[email protected]"],
body="hello, arthur")

The ``Message`` is then passed to the ``Mailer`` instance. You can either send the message right away::
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.
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**::
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.
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::

config = Configurator(settings=settings)
config.registry['mailer'] = Mailer.from_settings(settings)

or alternatively::

from pyramid_mailer import mailer_factory_from_settings
config.registry['mailer'] = mailer_factory_from_settings(settings)

You can then access your mailer in a view::

def my_view(request):
mailer = request.registry['mailer']

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
Expand All @@ -110,12 +162,18 @@ 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::

Expand Down Expand Up @@ -147,7 +205,8 @@ committed, and mail will be sent.
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
Expand All @@ -159,7 +218,8 @@ Attachments are added using the :class:`pyramid_mailer.message.Attachment` class

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
Expand All @@ -176,28 +236,38 @@ You can pass the data either as a string or file object, so the above code could
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")
Expand All @@ -210,12 +280,13 @@ Queue

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.::
Expand All @@ -235,6 +306,8 @@ API

.. autofunction:: mailer_factory_from_settings

.. autofunction:: get_mailer

.. module:: pyramid_mailer.mailer

.. autoclass:: Mailer
Expand Down
19 changes: 19 additions & 0 deletions pyramid_mailer/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from pyramid_mailer.mailer import Mailer
from pyramid_mailer.interfaces import IMailer

def mailer_factory_from_settings(settings, prefix='mail.'):
"""
Expand All @@ -8,3 +9,21 @@ 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)
5 changes: 5 additions & 0 deletions pyramid_mailer/interfaces.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from zope.interface import Interface

class IMailer(Interface):
pass

6 changes: 3 additions & 3 deletions pyramid_mailer/mailer.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ class SMTP_SSLMailer(SMTPMailer):
# 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

Expand All @@ -69,7 +69,7 @@ def __init__(self, *args, **kwargs):

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),
Expand Down Expand Up @@ -158,7 +158,7 @@ def from_settings(cls, settings, prefix='mail.'):
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)

Expand Down
Loading

0 comments on commit 60e3086

Please sign in to comment.