Skip to content

Commit

Permalink
Add vendored version of django mail
Browse files Browse the repository at this point in the history
  • Loading branch information
Indico Team authored and ThiefMaster committed Oct 17, 2019
1 parent c656796 commit 741c478
Show file tree
Hide file tree
Showing 12 changed files with 989 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
newdle/vendor/* linguist-vendored
Empty file added newdle/vendor/__init__.py
Empty file.
33 changes: 33 additions & 0 deletions newdle/vendor/django_mail/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# TODO: Move this whole package into a standalone pypi package, since it's
# useful in general for anyoen who wants to send emails (without using django)

# The code in here is taken almost verbatim from `django.core.mail`,
# which is licensed under the three-clause BSD license and is originally
# available on the following URL:
# https://github.com/django/django/blob/stable/2.2.x/django/core/mail/__init__.py
# Credits of the original code go to the Django Software Foundation
# and their contributors.

"""
Tools for sending email.
"""
from flask import current_app

from .backends.base import BaseEmailBackend
from .mail_utils import DNS_NAME, CachedDnsName
from .module_loading_utils import import_string


__all__ = ['get_connection']


def get_connection(backend=None, fail_silently=False, **kwds) -> BaseEmailBackend:
"""Load an email backend and return an instance of it.
If backend is None (default), use ``EMAIL_BACKEND`` from config.
Both fail_silently and other keyword arguments are used in the
constructor of the backend.
"""
klass = import_string(backend or current_app.config['EMAIL_BACKEND'])
return klass(fail_silently=fail_silently, **kwds)
Empty file.
70 changes: 70 additions & 0 deletions newdle/vendor/django_mail/backends/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# The code in here is taken almost verbatim from `django.core.mail.backends.base`,
# which is licensed under the three-clause BSD license and is originally
# available on the following URL:
# https://github.com/django/django/blob/stable/2.2.x/django/core/mail/backends/base.py
# Credits of the original code go to the Django Software Foundation
# and their contributors.


"""Base email backend class."""


class BaseEmailBackend:
"""
Base class for email backend implementations.
Subclasses must at least overwrite send_messages().
open() and close() can be called indirectly by using a backend object as a
context manager:
with backend as connection:
# do something with connection
pass
"""

def __init__(self, fail_silently=False, **kwargs):
self.fail_silently = fail_silently

def open(self):
"""
Open a network connection.
This method can be overwritten by backend implementations to
open a network connection.
It's up to the backend implementation to track the status of
a network connection if it's needed by the backend.
This method can be called by applications to force a single
network connection to be used when sending mails. See the
send_messages() method of the SMTP backend for a reference
implementation.
The default implementation does nothing.
"""
pass

def close(self):
"""Close a network connection."""
pass

def __enter__(self):
try:
self.open()
except Exception:
self.close()
raise
return self

def __exit__(self, exc_type, exc_value, traceback):
self.close()

def send_messages(self, email_messages):
"""
Send one or more EmailMessage objects and return the number of email
messages sent.
"""
raise NotImplementedError(
'subclasses of BaseEmailBackend must override send_messages() method'
)
51 changes: 51 additions & 0 deletions newdle/vendor/django_mail/backends/console.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# The code in here is taken almost verbatim from `django.core.mail.backends.console`,
# which is licensed under the three-clause BSD license and is originally
# available on the following URL:
# https://github.com/django/django/blob/stable/2.2.x/django/core/mail/backends/console.py
# Credits of the original code go to the Django Software Foundation
# and their contributors.

"""
Email backend that writes messages to console instead of sending them.
"""
import sys
import threading

from .base import BaseEmailBackend


class EmailBackend(BaseEmailBackend):
def __init__(self, *args, **kwargs):
self.stream = kwargs.pop('stream', sys.stdout)
self._lock = threading.RLock()
super().__init__(*args, **kwargs)

def write_message(self, message):
msg = message.message()
msg_data = msg.as_bytes()
charset = (
msg.get_charset().get_output_charset() if msg.get_charset() else 'utf-8'
)
msg_data = msg_data.decode(charset)
self.stream.write('%s\n' % msg_data)
self.stream.write('-' * 79)
self.stream.write('\n')

def send_messages(self, email_messages):
"""Write all messages to the stream in a thread-safe way."""
if not email_messages:
return
msg_count = 0
with self._lock:
try:
stream_created = self.open()
for message in email_messages:
self.write_message(message)
self.stream.flush() # flush after each message
msg_count += 1
if stream_created:
self.close()
except Exception:
if not self.fail_silently:
raise
return msg_count
39 changes: 39 additions & 0 deletions newdle/vendor/django_mail/backends/locmem.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# The code in here is taken almost verbatim from `django.core.mail.backends.locmem`,
# which is licensed under the three-clause BSD license and is originally
# available on the following URL:
# https://github.com/django/django/blob/stable/2.2.x/django/core/mail/backends/locmem.py
# Credits of the original code go to the Django Software Foundation
# and their contributors.

"""
Backend for test environment.
"""

from newdle.vendor import django_mail

from .base import BaseEmailBackend


class EmailBackend(BaseEmailBackend):
"""
An email backend for use during test sessions.
The test connection stores email messages in a dummy outbox,
rather than sending them out on the wire.
The dummy outbox is accessible through the outbox instance attribute.
"""

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if not hasattr(django_mail, 'outbox'):
django_mail.outbox = []

def send_messages(self, messages):
"""Redirect messages to the dummy outbox"""
msg_count = 0
for message in messages: # .message() triggers header validation
message.message()
django_mail.outbox.append(message)
msg_count += 1
return msg_count
168 changes: 168 additions & 0 deletions newdle/vendor/django_mail/backends/smtp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
# The code in here is taken almost verbatim from `django.core.mail.backends.smtp`,
# which is licensed under the three-clause BSD license and is originally
# available on the following URL:
# https://github.com/django/django/blob/stable/2.2.x/django/core/mail/backends/smtp.py
# Credits of the original code go to the Django Software Foundation
# and their contributors.

"""SMTP email backend class."""
import smtplib
import socket
import ssl
import threading

from flask import current_app

from ..mail_utils import DEFAULT_CHARSET, DNS_NAME
from ..message import sanitize_address
from .base import BaseEmailBackend


class EmailBackend(BaseEmailBackend):
"""
A wrapper that manages the SMTP network connection.
"""

def __init__(
self,
host=None,
port=None,
username=None,
password=None,
use_tls=None,
fail_silently=False,
use_ssl=None,
timeout=None,
ssl_keyfile=None,
ssl_certfile=None,
**kwargs,
):
super().__init__(fail_silently=fail_silently)
self.host = host or current_app.config['EMAIL_HOST']
self.port = port or current_app.config['EMAIL_PORT']
self.username = (
current_app.config['EMAIL_HOST_USER'] if username is None else username
)
self.password = (
current_app.config['EMAIL_HOST_PASSWORD'] if password is None else password
)
self.use_tls = (
current_app.config['EMAIL_USE_TLS'] if use_tls is None else use_tls
)
self.use_ssl = (
current_app.config['EMAIL_USE_SSL'] if use_ssl is None else use_ssl
)
self.timeout = (
current_app.config['EMAIL_TIMEOUT'] if timeout is None else timeout
)
self.ssl_keyfile = ssl_keyfile
self.ssl_certfile = ssl_certfile
if self.use_ssl and self.use_tls:
raise ValueError(
"EMAIL_USE_TLS/EMAIL_USE_SSL are mutually exclusive, so only set "
"one of those settings to True."
)
self.connection = None
self._lock = threading.RLock()

@property
def connection_class(self):
return smtplib.SMTP_SSL if self.use_ssl else smtplib.SMTP

def open(self):
"""
Ensure an open connection to the email server. Return whether or not a
new connection was required (True or False) or None if an exception
passed silently.
"""
if self.connection:
# Nothing to do if the connection is already open.
return False

# If local_hostname is not specified, socket.getfqdn() gets used.
# For performance, we use the cached FQDN for local_hostname.
connection_params = {'local_hostname': DNS_NAME.get_fqdn()}
if self.timeout is not None:
connection_params['timeout'] = self.timeout
if self.use_ssl:
connection_params.update(
{'keyfile': self.ssl_keyfile, 'certfile': self.ssl_certfile}
)
try:
self.connection = self.connection_class(
self.host, self.port, **connection_params
)

# TLS/SSL are mutually exclusive, so only attempt TLS over
# non-secure connections.
if not self.use_ssl and self.use_tls:
self.connection.starttls(
keyfile=self.ssl_keyfile, certfile=self.ssl_certfile
)
if self.username and self.password:
self.connection.login(self.username, self.password)
return True
except (smtplib.SMTPException, socket.error):
if not self.fail_silently:
raise

def close(self):
"""Close the connection to the email server."""
if self.connection is None:
return
try:
try:
self.connection.quit()
except (ssl.SSLError, smtplib.SMTPServerDisconnected):
# This happens when calling quit() on a TLS connection
# sometimes, or when the connection was already disconnected
# by the server.
self.connection.close()
except smtplib.SMTPException:
if self.fail_silently:
return
raise
finally:
self.connection = None

def send_messages(self, email_messages):
"""
Send one or more EmailMessage objects and return the number of email
messages sent.
"""
if not email_messages:
return 0
with self._lock:
new_conn_created = self.open()
if not self.connection or new_conn_created is None:
# We failed silently on open().
# Trying to send would be pointless.
return 0
num_sent = 0
for message in email_messages:
sent = self._send(message)
if sent:
num_sent += 1
if new_conn_created:
self.close()
return num_sent

def _send(self, email_message):
"""A helper method that does the actual sending."""
if not email_message.recipients():
return False
encoding = email_message.encoding or DEFAULT_CHARSET
from_email = sanitize_address(email_message.from_email, encoding)
recipients = [
sanitize_address(addr, encoding) for addr in email_message.recipients()
]
message = email_message.message()
try:
self.connection.sendmail(
from_email, recipients, message.as_bytes(linesep='\r\n')
)
except smtplib.SMTPException:
if not self.fail_silently:
raise
return False
return True
Loading

0 comments on commit 741c478

Please sign in to comment.