mirror of
https://github.com/pacnpal/django-anymail.git
synced 2025-12-20 03:41:05 -05:00
146 lines
5.0 KiB
Python
146 lines
5.0 KiB
Python
import json
|
|
|
|
# noinspection PyUnresolvedReferences
|
|
from six.moves.urllib.parse import urljoin
|
|
|
|
from .base import AnymailBaseBackend, BasePayload
|
|
from ..exceptions import AnymailImproperlyInstalled, AnymailRequestsAPIError, AnymailSerializationError
|
|
from .._version import __version__
|
|
|
|
try:
|
|
# noinspection PyUnresolvedReferences
|
|
import requests
|
|
except ImportError:
|
|
raise AnymailImproperlyInstalled('requests')
|
|
|
|
|
|
class AnymailRequestsBackend(AnymailBaseBackend):
|
|
"""
|
|
Base Anymail email backend for ESPs that use an HTTP API via requests
|
|
"""
|
|
|
|
def __init__(self, api_url, **kwargs):
|
|
"""Init options from Django settings"""
|
|
self.api_url = api_url
|
|
super(AnymailRequestsBackend, self).__init__(**kwargs)
|
|
self.session = None
|
|
|
|
def open(self):
|
|
if self.session:
|
|
return False # already exists
|
|
|
|
try:
|
|
self.session = requests.Session()
|
|
except requests.RequestException:
|
|
if not self.fail_silently:
|
|
raise
|
|
else:
|
|
self.session.headers["User-Agent"] = "Anymail/%s %s" % (
|
|
__version__, self.session.headers.get("User-Agent", ""))
|
|
return True
|
|
|
|
def close(self):
|
|
if self.session is None:
|
|
return
|
|
try:
|
|
self.session.close()
|
|
except requests.RequestException:
|
|
if not self.fail_silently:
|
|
raise
|
|
finally:
|
|
self.session = None
|
|
|
|
def _send(self, message):
|
|
if self.session is None:
|
|
class_name = self.__class__.__name__
|
|
raise RuntimeError(
|
|
"Session has not been opened in {class_name}._send. "
|
|
"(This is either an implementation error in {class_name}, "
|
|
"or you are incorrectly calling _send directly.)".format(class_name=class_name))
|
|
return super(AnymailRequestsBackend, self)._send(message)
|
|
|
|
def post_to_esp(self, payload, message):
|
|
"""Post payload to ESP send API endpoint, and return the raw response.
|
|
|
|
payload is the result of build_message_payload
|
|
message is the original EmailMessage
|
|
return should be a requests.Response
|
|
|
|
Can raise AnymailRequestsAPIError for HTTP errors in the post
|
|
"""
|
|
params = payload.get_request_params(self.api_url)
|
|
response = self.session.request(**params)
|
|
if response.status_code != 200:
|
|
raise AnymailRequestsAPIError(email_message=message, payload=payload, response=response)
|
|
return response
|
|
|
|
def deserialize_json_response(self, response, payload, message):
|
|
"""Deserialize an ESP API response that's in json.
|
|
|
|
Useful for implementing deserialize_response
|
|
"""
|
|
try:
|
|
return response.json()
|
|
except ValueError:
|
|
raise AnymailRequestsAPIError("Invalid JSON in %s API response" % self.esp_name,
|
|
email_message=message, payload=payload, response=response)
|
|
|
|
|
|
class RequestsPayload(BasePayload):
|
|
"""Abstract Payload for AnymailRequestsBackend"""
|
|
|
|
def __init__(self, message, defaults, backend,
|
|
method="POST", params=None, data=None,
|
|
headers=None, files=None, auth=None):
|
|
self.method = method
|
|
self.params = params
|
|
self.data = data
|
|
self.headers = headers
|
|
self.files = files
|
|
self.auth = auth
|
|
super(RequestsPayload, self).__init__(message, defaults, backend)
|
|
|
|
def get_request_params(self, api_url):
|
|
"""Returns a dict of requests.request params that will send payload to the ESP.
|
|
|
|
:param api_url: the base api_url for the backend
|
|
:return: dict
|
|
"""
|
|
api_endpoint = self.get_api_endpoint()
|
|
if api_endpoint is not None:
|
|
url = urljoin(api_url, api_endpoint)
|
|
else:
|
|
url = api_url
|
|
|
|
return dict(
|
|
method=self.method,
|
|
url=url,
|
|
params=self.params,
|
|
data=self.serialize_data(),
|
|
headers=self.headers,
|
|
files=self.files,
|
|
auth=self.auth,
|
|
# json= is not here, because we prefer to do our own serialization
|
|
# to provide extra context in error messages
|
|
)
|
|
|
|
def get_api_endpoint(self):
|
|
"""Returns a str that should be joined to the backend's api_url for sending this payload."""
|
|
return None
|
|
|
|
def serialize_data(self):
|
|
"""Performs any necessary serialization on self.data, and returns the result."""
|
|
return self.data
|
|
|
|
def serialize_json(self, data):
|
|
"""Returns data serialized to json, raising appropriate errors.
|
|
|
|
Useful for implementing serialize_data in a subclass,
|
|
"""
|
|
try:
|
|
return json.dumps(data)
|
|
except TypeError as err:
|
|
# Add some context to the "not JSON serializable" message
|
|
raise AnymailSerializationError(orig_err=err, email_message=self.message,
|
|
backend=self.backend, payload=self)
|