Permalink
Browse files

Merge develop branch for 4.0.2 release.

  • Loading branch information...
2 parents 571e440 + aa415c3 commit 0193aab1f30065eb7fa50db764a7a534bb2211ed @amcgregor amcgregor committed Nov 24, 2016
View
No changes.
View
@@ -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:
@@ -28,4 +33,4 @@ notifications:
on_failure: always
template:
- "%{repository_slug}:%{branch}@%{commit} %{message}"
- - "Duration: %{duration} - Details: %{build_url}"
+ - "Duration: %{duration} - Details: %{build_url}"
View
@@ -1,11 +0,0 @@
-#!/bin/bash
-set -e
-set -x
-
-git config --global user.email "alice+travis@gothcandy.com"
-git config --global user.name "Travis: Marrow"
-
-pip install --upgrade setuptools 'pip<8.0.0' pytest
-pip install codecov
-
-pip install -e .[develop]
View
@@ -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
@@ -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.
@@ -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
View
@@ -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
@@ -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']
@@ -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
@@ -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)
"""
@@ -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)
View
@@ -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')
@@ -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
@@ -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()
@@ -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):
@@ -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],
@@ -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):
@@ -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
@@ -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)
View
@@ -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
+
Oops, something went wrong.

0 comments on commit 0193aab

Please sign in to comment.