mirror of
https://github.com/pacnpal/django-anymail.git
synced 2025-12-20 03:41:05 -05:00
Drop support for Python 3.6 and old urllib3
This commit is contained in:
3
.github/workflows/test.yml
vendored
3
.github/workflows/test.yml
vendored
@@ -57,8 +57,7 @@ jobs:
|
||||
run: |
|
||||
set -x
|
||||
python -VV
|
||||
# Must pin virtualenv for tox py36 testenv:
|
||||
python -m pip install 'tox<4' 'virtualenv<20.22.0'
|
||||
python -m pip install 'tox<4'
|
||||
python -m tox --version
|
||||
- name: Test ${{ matrix.tox.name }}
|
||||
run: |
|
||||
|
||||
@@ -25,6 +25,18 @@ Release history
|
||||
^^^^^^^^^^^^^^^
|
||||
.. This extra heading level keeps the ToC from becoming unmanageably long
|
||||
|
||||
vNext
|
||||
-----
|
||||
|
||||
*unreleased changes*
|
||||
|
||||
Breaking changes
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
* Require Python 3.7 or later.
|
||||
* Require urllib3 1.25 or later (released 2019-04-29).
|
||||
|
||||
|
||||
v9.2
|
||||
-----
|
||||
|
||||
|
||||
@@ -1,33 +1,12 @@
|
||||
from datetime import datetime
|
||||
from email.utils import encode_rfc2231
|
||||
from urllib.parse import quote
|
||||
|
||||
from requests import Request
|
||||
|
||||
from ..exceptions import AnymailError, AnymailRequestsAPIError
|
||||
from ..message import AnymailRecipientStatus
|
||||
from ..utils import get_anymail_setting, rfc2822date
|
||||
from .base_requests import AnymailRequestsBackend, RequestsPayload
|
||||
|
||||
|
||||
# Feature-detect whether requests (urllib3) correctly uses RFC 7578 encoding for non-
|
||||
# ASCII filenames in Content-Disposition headers. (This was fixed in urllib3 v1.25.)
|
||||
# See MailgunPayload.get_request_params for info (and a workaround on older versions).
|
||||
# (Note: when this workaround is removed, please also remove "old_urllib3" tox envs.)
|
||||
def is_requests_rfc_5758_compliant():
|
||||
request = Request(
|
||||
method="POST",
|
||||
url="https://www.example.com",
|
||||
files=[("attachment", ("\N{NOT SIGN}.txt", "test", "text/plain"))],
|
||||
)
|
||||
prepared = request.prepare()
|
||||
form_data = prepared.body # bytes
|
||||
return b"filename*=" not in form_data
|
||||
|
||||
|
||||
REQUESTS_IS_RFC_7578_COMPLIANT = is_requests_rfc_5758_compliant()
|
||||
|
||||
|
||||
class EmailBackend(AnymailRequestsBackend):
|
||||
"""
|
||||
Mailgun API Email Backend
|
||||
@@ -163,37 +142,6 @@ class MailgunPayload(RequestsPayload):
|
||||
)
|
||||
return "%s/messages" % quote(self.sender_domain, safe="")
|
||||
|
||||
def get_request_params(self, api_url):
|
||||
params = super().get_request_params(api_url)
|
||||
non_ascii_filenames = [
|
||||
filename
|
||||
for (field, (filename, content, mimetype)) in params["files"]
|
||||
if filename is not None and not isascii(filename)
|
||||
]
|
||||
if non_ascii_filenames and not REQUESTS_IS_RFC_7578_COMPLIANT:
|
||||
# Workaround https://github.com/requests/requests/issues/4652:
|
||||
# Mailgun expects RFC 7578 compliant multipart/form-data, and is confused
|
||||
# by Requests/urllib3's improper use of RFC 2231 encoded filename parameters
|
||||
# ("filename*=utf-8''...") in Content-Disposition headers.
|
||||
# The workaround is to pre-generate the (non-compliant) form-data body, and
|
||||
# replace 'filename*={RFC 2231 encoded}' with 'filename="{UTF-8 bytes}"'.
|
||||
# Replace _only_ filenames that will be problems (not all "filename*=...")
|
||||
# to minimize potential side effects--e.g., in attached messages that might
|
||||
# have their own attachments with (correctly) RFC 2231 encoded filenames.
|
||||
prepared = Request(**params).prepare()
|
||||
form_data = prepared.body # bytes
|
||||
for filename in non_ascii_filenames: # text
|
||||
rfc2231_filename = encode_rfc2231(filename, charset="utf-8")
|
||||
form_data = form_data.replace(
|
||||
b"filename*=" + rfc2231_filename.encode("utf-8"),
|
||||
b'filename="' + filename.encode("utf-8") + b'"',
|
||||
)
|
||||
params["data"] = form_data
|
||||
# Content-Type: multipart/form-data; boundary=...
|
||||
params["headers"]["Content-Type"] = prepared.headers["Content-Type"]
|
||||
params["files"] = None # these are now in the form_data body
|
||||
return params
|
||||
|
||||
def serialize_data(self):
|
||||
self.populate_recipient_variables()
|
||||
return self.data
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[tool.black]
|
||||
force-exclude = '^/tests/test_settings/settings_.*\.py'
|
||||
max-line-length = 88
|
||||
target-version = ["py36"]
|
||||
target-version = ["py37"]
|
||||
|
||||
[tool.doc8]
|
||||
# ignore very long lines in ESP support table:
|
||||
@@ -16,4 +16,4 @@ max-line-length = 120
|
||||
combine_as_imports = true
|
||||
known_first_party = "anymail"
|
||||
profile = "black"
|
||||
py_version = "36"
|
||||
py_version = "37"
|
||||
|
||||
10
setup.py
10
setup.py
@@ -45,7 +45,6 @@ requirements_dev = [
|
||||
"sphinx-rtd-theme",
|
||||
"tox",
|
||||
"twine",
|
||||
"virtualenv<20.22.0", # tox dependency, pinned for Python 3.6 tox testenv
|
||||
"wheel",
|
||||
]
|
||||
|
||||
@@ -71,8 +70,12 @@ setup(
|
||||
license="BSD License",
|
||||
packages=["anymail"],
|
||||
zip_safe=False,
|
||||
python_requires=">=3.6",
|
||||
install_requires=["django>=2.0", "requests>=2.4.3"],
|
||||
python_requires=">=3.7",
|
||||
install_requires=[
|
||||
"django>=2.0",
|
||||
"requests>=2.4.3",
|
||||
"urllib3>=1.25.0", # requests dependency: fixes RFC 7578 header encoding
|
||||
],
|
||||
extras_require={
|
||||
# This can be used if particular backends have unique dependencies.
|
||||
# For simplicity, requests is included in the base requirements.
|
||||
@@ -100,7 +103,6 @@ setup(
|
||||
"Programming Language :: Python :: Implementation :: PyPy",
|
||||
"Programming Language :: Python :: Implementation :: CPython",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.6",
|
||||
"Programming Language :: Python :: 3.7",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
|
||||
17
tox.ini
17
tox.ini
@@ -6,7 +6,7 @@ envlist =
|
||||
# Test lint, docs, earliest/latest Django first, to catch most errors early...
|
||||
lint
|
||||
django42-py311-all
|
||||
django30-py36-all
|
||||
django30-py37-all
|
||||
docs
|
||||
# ... then test all the other supported combinations:
|
||||
# Django 4.2: Python 3.8, 3.9, 3.10, 3.11
|
||||
@@ -15,12 +15,12 @@ envlist =
|
||||
django41-py{38,39,310,py38,py39}-all
|
||||
# Django 4.0: Python 3.8, 3.9, 3.10
|
||||
django40-py{38,39,310,py38,py39}-all
|
||||
# Django 3.2: Python 3.6, 3.7, 3.8, 3.9
|
||||
django32-py{36,37,38,39,py38,py39}-all
|
||||
# Django 3.1: Python 3.6, 3.7, 3.8, 3.9 (added in 3.1.3)
|
||||
django31-py{36,37,38,39,py38,py39}-all
|
||||
# Django 3.0: Python 3.6, 3.7, 3.8, 3.9 (added in 3.0.11)
|
||||
django30-py{37,38,39,py38,py39}-all
|
||||
# Django 3.2: Python 3.6 (eol 2021-12-23), 3.7, 3.8, 3.9
|
||||
django32-py{37,38,39,py38,py39}-all
|
||||
# Django 3.1: Python 3.6 (eol 2021-12-23), 3.7, 3.8, 3.9 (added in 3.1.3)
|
||||
django31-py{37,38,39,py38,py39}-all
|
||||
# Django 3.0: Python 3.6 (eol 2021-12-23), 3.7, 3.8, 3.9 (added in 3.0.11)
|
||||
django30-py{38,39,py38,py39}-all
|
||||
# ... then prereleases (if available) and current development:
|
||||
# Django 5.0 alpha: Python 3.10+
|
||||
# [not yet in alpha] django50-py{310,311,py310,py311}-all
|
||||
@@ -28,8 +28,6 @@ envlist =
|
||||
djangoDev-py{310,311}-all
|
||||
# ... then partial installation (limit extras):
|
||||
django42-py311-{none,amazon_ses,postal}
|
||||
# ... then older versions of some dependencies:
|
||||
django32-py37-all-old_urllib3
|
||||
|
||||
[testenv]
|
||||
deps =
|
||||
@@ -41,7 +39,6 @@ deps =
|
||||
django42: django~=4.2.0
|
||||
django50: django~=5.0.0a0
|
||||
djangoDev: https://github.com/django/django/tarball/main
|
||||
old_urllib3: urllib3<1.25
|
||||
extras =
|
||||
# install [test] extras, unconditionally
|
||||
test
|
||||
|
||||
Reference in New Issue
Block a user