Skip to content

Commit

Permalink
Merge develop branch for 4.0.2 release.
Browse files Browse the repository at this point in the history
  • Loading branch information
amcgregor committed Nov 24, 2016
2 parents 571e440 + aa415c3 commit 0193aab
Show file tree
Hide file tree
Showing 14 changed files with 199 additions and 73 deletions.
Empty file added .packaging/.keep
Empty file.
17 changes: 11 additions & 6 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,22 +1,27 @@
language: python
sudo: false
cache: pip

branches:
except:
- /^feature/.*$/
- /^[^/]+/.*$/

python:
- pypy
- "2.7"
- "pypy"
- "3.3"
- "3.4"
- "3.5"

install: travis_retry .travis/install.sh
install:
- travis_retry pip install --upgrade setuptools pip codecov 'setuptools_scm>=1.9'
- ./setup.py develop

script:
- python setup.py test
- codecov --file coverage.xml
python setup.py test

after_script:
bash <(curl -s https://codecov.io/bash)

notifications:
irc:
Expand All @@ -28,4 +33,4 @@ notifications:
on_failure: always
template:
- "%{repository_slug}:%{branch}@%{commit} %{message}"
- "Duration: %{duration} - Details: %{build_url}"
- "Duration: %{duration} - Details: %{build_url}"
11 changes: 0 additions & 11 deletions .travis/install.sh

This file was deleted.

18 changes: 13 additions & 5 deletions README.textile
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ Any of these attributes can also be defined within your mailer configuration. W
'message.subject': "Test subject."
})
message = mail.new()
message.subject == "Test subject."
message.subject = "Test subject."
message.send()</code></pre>

h4. %4.2.2.% Read-Only Attributes
Expand Down Expand Up @@ -267,7 +267,7 @@ table(configuration).
| @pipeline@ | @None@ | If a non-zero positive integer, this represents the number of messages to pipeline across a single SMTP connection. Most servers allow up to 10 messages to be delivered. |


h4(#imap-transport). %5.2.2.% Internet Mail Access Protocol (IMAP)
h4(#imap-transport). %6.2.2.% Internet Mail Access Protocol (IMAP)

Marrow Mailer, via the @imap@ transport, allows you to dump messages directly into folders on remote servers.

Expand Down Expand Up @@ -341,17 +341,25 @@ table(configuration).
| @path@ | @"/usr/sbin/sendmail"@ | The path to the @sendmail@ executable. |


h4(#ses-transport). %6.3.1.% Amazon Simple E-Mail Service (SES)
h4(#amazon-transport). %6.3.1.% Amazon Simple E-Mail Service (SES)

Deliver your messages via the Amazon Simple E-Mail Service. While Amazon allow you to utilize SMTP for communication, using the correct API allows you to get much richer information back from delivery upon both success *and* failure. To utilize this transport you must have the @boto@ package installed.
Deliver your messages via the Amazon Simple E-Mail Service with the @amazon@ transport. While Amazon allows you to utilize SMTP for communication, using the correct API allows you to get much richer information back from delivery upon both success *and* failure. To utilize this transport you must have the @boto@ package installed.

table(configuration).
|_. Directive |_. Default |_. Description |
| @id@ | — | Your Amazon AWS access key identifier. |
| @key@ | — | Your Amazon AWS secret access key. |
| @host@ | @"email.us-east-1.amazonaws.com"@ | The API endpoint to utilize. |


h4(#sendgrid-transport). %6.3.1.% SendGrid

The @sendgrid@ transport uses the email service provider SendGrid to deliver your transactional and marketing messages. Use your SendGrid username and password (@user@ and @key@), or supply an API key (only @key@).

table(configuration).
|_. Directive |_. Default |_. Description |
| @user@ | — | Your SendGrid username. Don't include this if you're using an API key. |
| @key@ | — | Your SendGrid password, or a SendGrid account API key. |


h2(#extending). %7.% Extending Marrow Mailer

Expand Down
31 changes: 21 additions & 10 deletions marrow/mailer/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,18 @@

"""MIME-encoded electronic mail message class."""

from __future__ import unicode_literals

import imghdr
import os
import sys
import time
import base64

from datetime import datetime
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.nonmultipart import MIMENonMultipart
from email.header import Header
from email.utils import make_msgid, formatdate
from mimetypes import guess_type

Expand All @@ -20,14 +22,6 @@
from marrow.util.compat import basestring, unicode, native


#from marrow.schema import Container, DataAttribute, Attribute, CallbackAttribute, Attributes
#from marrow.schema.util import
#from marrow.schema.compat import py2, py3, native, unicode, str





__all__ = ['Message']


Expand Down Expand Up @@ -258,7 +252,8 @@ def mime(self):
return message

def attach(self, name, data=None, maintype=None, subtype=None,
inline=False, filename=None, encoding=None):
inline=False, filename=None, filename_charset='', filename_language='',
encoding=None):
"""Attach a file to this message.
:param name: Path to the file to attach if data is None, or the name
Expand All @@ -274,6 +269,9 @@ def attach(self, name, data=None, maintype=None, subtype=None,
"inline" (True) or "attachment" (False)
:param filename: The file name of the attached file as seen
by the user in his/her mail client.
:param filename_charset: Charset used for the filename paramenter. Allows for
attachment names with characters from UTF-8 or Latin 1. See RFC 2231.
:param filename_language: Used to specify what language the filename is in. See RFC 2231.
:param encoding: Value of the Content-Encoding MIME header (e.g. "gzip"
in case of .tar.gz, but usually empty)
"""
Expand Down Expand Up @@ -309,6 +307,19 @@ def attach(self, name, data=None, maintype=None, subtype=None,
if not filename:
filename = name
filename = os.path.basename(filename)

if filename_charset or filename_language:
if not filename_charset:
filename_charset = 'utf-8'
# See https://docs.python.org/2/library/email.message.html#email.message.Message.add_header
# for more information.
# add_header() in the email module expects its arguments to be ASCII strings. Go ahead and handle
# the case where these arguments come in as unicode strings, since encoding ASCII strings
# as UTF-8 can't hurt.
if sys.version_info < (3, 0):
filename=(filename_charset.encode('utf-8'), filename_language.encode('utf-8'), filename.encode('utf-8'))
else:
filename=(filename_charset, filename_language, filename)

if inline:
part.add_header('Content-Disposition', 'inline', filename=filename)
Expand Down
2 changes: 1 addition & 1 deletion marrow/mailer/release.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from collections import namedtuple


version_info = namedtuple('version_info', ('major', 'minor', 'micro', 'releaselevel', 'serial'))(4, 0, 1, 'final', 0)
version_info = namedtuple('version_info', ('major', 'minor', 'micro', 'releaselevel', 'serial'))(4, 0, 2, 'final', 0)
version = ".".join([str(i) for i in version_info[:3]]) + ((version_info.releaselevel[0] + str(version_info.serial)) if version_info.releaselevel != 'final' else '')

author = namedtuple('Author', ['name', 'email'])("Alice Bevan-McGregor", 'alice@gothcandy.com')
Expand Down
53 changes: 53 additions & 0 deletions marrow/mailer/transport/mailgun.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# encoding: utf-8

try:
import requests

except ImportError:
raise ImportError("You must install the requests package to deliver mail mailgun.")


__all__ = ['MailgunTransport']

log = __import__('logging').getLogger(__name__)



class MailgunTransport(object): # pragma: no cover
__slots__ = ('ephemeral', 'keys', 'session')

API_URL_TMPL = "https://api.mailgun.net/v3/{domain}/messages.mime"

def __init__(self, config):
if 'domain' in config and 'key' in config:
self.keys = {config['domain']: config['key']}
else:
self.keys = config.get('keys', {})

if not self.keys:
raise ValueError("Must either define a `domain` and `key` configuration, or `keys` mapping.")

self.session = None

def startup(self):
self.session = requests.Session()

def deliver(self, message):
domain = message.author.address.rpartition('@')[2]
if domain not in self.keys:
raise Exception("No API key registered for: " + domain)

uri = self.API_URL_TMPL.format(domain=domain)

result = self.session.post(uri, auth=('api', self.keys[domain]),
data = {'from': message.author, 'to': list(str(i) for i in message.recipients),
'subject': message.subject},
files = {"message": ('message.mime', message.mime.as_bytes())})

result.raise_for_status()

def shutdown(self):
if self.session:
self.session.close()

self.session = None
2 changes: 1 addition & 1 deletion marrow/mailer/transport/postmark.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def _batchsend(self):

try:
response = urllib2.urlopen(request)
except (urllib2.HTTPError, urllib2.URLError), e:
except (urllib2.HTTPError, urllib2.URLError) as e:
raise DeliveryFailedException(e, "Could not connect to Postmark.")
else:
respcode = response.getcode()
Expand Down
17 changes: 13 additions & 4 deletions marrow/mailer/transport/sendgrid.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,14 @@


class SendgridTransport(object):
__slots__ = ('ephemeral', 'user', 'key')
__slots__ = ('ephemeral', 'user', 'key', 'bearer')

def __init__(self, config):
self.user = config.get('user')
self.bearer = False
if not 'user' in config:
self.bearer = True
else:
self.user = config.get('user')
self.key = config.get('key')

def startup(self):
Expand All @@ -29,8 +33,6 @@ def deliver(self, message):
to.extend(message.cc)

args = dict({
'api_user': self.user,
'api_key': self.key,
'from': [fromaddr.address.encode(message.encoding) for fromaddr in message.author],
'fromname': [fromaddr.name.encode(message.encoding) for fromaddr in message.author],
'to': [toaddr.address.encode(message.encoding) for toaddr in to],
Expand Down Expand Up @@ -62,12 +64,19 @@ def deliver(self, message):
msg.attachments = attachments
"""
raise MailConfigurationException()

if not self.bearer:
args['api_user'] = self.user
args['api_key'] = self.key

request = urllib2.Request(
"https://sendgrid.com/api/mail.send.json",
urllib.urlencode(args, True)
)

if self.bearer:
request.add_header("Authorization", "Bearer %s" % self.key)

try:
response = urllib2.urlopen(request)
except (urllib2.HTTPError, urllib2.URLError):
Expand Down
1 change: 1 addition & 0 deletions marrow/mailer/transport/ses.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ def __init__(self, config):
config['aws_secret_access_key'] = config.pop('key')

self.region = config.pop('region', "us-east-1")
config.pop('use') #boto throws an error if we leave this in the next line
self.config = config # All other configuration directives are passed to connect_to_region.
self.connection = None

Expand Down
1 change: 0 additions & 1 deletion marrow/mailer/transport/smtp.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,6 @@ def send_with_smtp(self, message):
try:
sender = str(message.envelope)
recipients = message.recipients.string_addresses
recipients = [addr.decode('utf-8') for addr in message.recipients.string_addresses]
content = str(message)

self.connection.sendmail(sender, recipients, content)
Expand Down
35 changes: 32 additions & 3 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -1,9 +1,38 @@
[pytest]
addopts = --flakes --spec --cov-report term-missing --cov-report html --cov-report xml --no-cov-on-fail --cov marrow.mailer -l --durations=5 -r fEsxw --color=yes test/
[aliases]
test = pytest

[check]
metadata = 1
restructuredtext = 1

[clean]
build-base = .packaging/build
bdist-base = .packaging/dist

[build]
build-base = .packaging/build

[install]
optimize = 1

[bdist]
bdist-base = .packaging/dist
dist-dir = .packaging/release

[bdist_wheel]
bdist-dir = .packaging/dist
dist-dir = .packaging/release

[register]
strict = 1

[tool:pytest]
addopts = --flakes --cov-report term-missing --cov-report xml --no-cov-on-fail --cov marrow.mailer -l --durations=5 -r fEsxw --color=auto test

flakes-ignore =
test/*.py UnusedImport
test/*/*.py UnusedImport

[wheel]
universal=1
universal = 1

Loading

0 comments on commit 0193aab

Please sign in to comment.