Drop support for Python 3.6 and old urllib3

This commit is contained in:
Mike Edmunds
2023-05-02 13:59:01 -07:00
parent 485766182e
commit 9fba58237d
6 changed files with 28 additions and 70 deletions

View File

@@ -57,8 +57,7 @@ jobs:
run: | run: |
set -x set -x
python -VV python -VV
# Must pin virtualenv for tox py36 testenv: python -m pip install 'tox<4'
python -m pip install 'tox<4' 'virtualenv<20.22.0'
python -m tox --version python -m tox --version
- name: Test ${{ matrix.tox.name }} - name: Test ${{ matrix.tox.name }}
run: | run: |

View File

@@ -25,6 +25,18 @@ Release history
^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^
.. This extra heading level keeps the ToC from becoming unmanageably long .. 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 v9.2
----- -----

View File

@@ -1,33 +1,12 @@
from datetime import datetime from datetime import datetime
from email.utils import encode_rfc2231
from urllib.parse import quote from urllib.parse import quote
from requests import Request
from ..exceptions import AnymailError, AnymailRequestsAPIError from ..exceptions import AnymailError, AnymailRequestsAPIError
from ..message import AnymailRecipientStatus from ..message import AnymailRecipientStatus
from ..utils import get_anymail_setting, rfc2822date from ..utils import get_anymail_setting, rfc2822date
from .base_requests import AnymailRequestsBackend, RequestsPayload 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): class EmailBackend(AnymailRequestsBackend):
""" """
Mailgun API Email Backend Mailgun API Email Backend
@@ -163,37 +142,6 @@ class MailgunPayload(RequestsPayload):
) )
return "%s/messages" % quote(self.sender_domain, safe="") 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): def serialize_data(self):
self.populate_recipient_variables() self.populate_recipient_variables()
return self.data return self.data

View File

@@ -1,7 +1,7 @@
[tool.black] [tool.black]
force-exclude = '^/tests/test_settings/settings_.*\.py' force-exclude = '^/tests/test_settings/settings_.*\.py'
max-line-length = 88 max-line-length = 88
target-version = ["py36"] target-version = ["py37"]
[tool.doc8] [tool.doc8]
# ignore very long lines in ESP support table: # ignore very long lines in ESP support table:
@@ -16,4 +16,4 @@ max-line-length = 120
combine_as_imports = true combine_as_imports = true
known_first_party = "anymail" known_first_party = "anymail"
profile = "black" profile = "black"
py_version = "36" py_version = "37"

View File

@@ -45,7 +45,6 @@ requirements_dev = [
"sphinx-rtd-theme", "sphinx-rtd-theme",
"tox", "tox",
"twine", "twine",
"virtualenv<20.22.0", # tox dependency, pinned for Python 3.6 tox testenv
"wheel", "wheel",
] ]
@@ -71,8 +70,12 @@ setup(
license="BSD License", license="BSD License",
packages=["anymail"], packages=["anymail"],
zip_safe=False, zip_safe=False,
python_requires=">=3.6", python_requires=">=3.7",
install_requires=["django>=2.0", "requests>=2.4.3"], install_requires=[
"django>=2.0",
"requests>=2.4.3",
"urllib3>=1.25.0", # requests dependency: fixes RFC 7578 header encoding
],
extras_require={ extras_require={
# This can be used if particular backends have unique dependencies. # This can be used if particular backends have unique dependencies.
# For simplicity, requests is included in the base requirements. # For simplicity, requests is included in the base requirements.
@@ -100,7 +103,6 @@ setup(
"Programming Language :: Python :: Implementation :: PyPy", "Programming Language :: Python :: Implementation :: PyPy",
"Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.9",

17
tox.ini
View File

@@ -6,7 +6,7 @@ envlist =
# Test lint, docs, earliest/latest Django first, to catch most errors early... # Test lint, docs, earliest/latest Django first, to catch most errors early...
lint lint
django42-py311-all django42-py311-all
django30-py36-all django30-py37-all
docs docs
# ... then test all the other supported combinations: # ... then test all the other supported combinations:
# Django 4.2: Python 3.8, 3.9, 3.10, 3.11 # 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 django41-py{38,39,310,py38,py39}-all
# Django 4.0: Python 3.8, 3.9, 3.10 # Django 4.0: Python 3.8, 3.9, 3.10
django40-py{38,39,310,py38,py39}-all django40-py{38,39,310,py38,py39}-all
# Django 3.2: Python 3.6, 3.7, 3.8, 3.9 # Django 3.2: Python 3.6 (eol 2021-12-23), 3.7, 3.8, 3.9
django32-py{36,37,38,39,py38,py39}-all django32-py{37,38,39,py38,py39}-all
# Django 3.1: Python 3.6, 3.7, 3.8, 3.9 (added in 3.1.3) # Django 3.1: Python 3.6 (eol 2021-12-23), 3.7, 3.8, 3.9 (added in 3.1.3)
django31-py{36,37,38,39,py38,py39}-all django31-py{37,38,39,py38,py39}-all
# Django 3.0: Python 3.6, 3.7, 3.8, 3.9 (added in 3.0.11) # Django 3.0: Python 3.6 (eol 2021-12-23), 3.7, 3.8, 3.9 (added in 3.0.11)
django30-py{37,38,39,py38,py39}-all django30-py{38,39,py38,py39}-all
# ... then prereleases (if available) and current development: # ... then prereleases (if available) and current development:
# Django 5.0 alpha: Python 3.10+ # Django 5.0 alpha: Python 3.10+
# [not yet in alpha] django50-py{310,311,py310,py311}-all # [not yet in alpha] django50-py{310,311,py310,py311}-all
@@ -28,8 +28,6 @@ envlist =
djangoDev-py{310,311}-all djangoDev-py{310,311}-all
# ... then partial installation (limit extras): # ... then partial installation (limit extras):
django42-py311-{none,amazon_ses,postal} django42-py311-{none,amazon_ses,postal}
# ... then older versions of some dependencies:
django32-py37-all-old_urllib3
[testenv] [testenv]
deps = deps =
@@ -41,7 +39,6 @@ deps =
django42: django~=4.2.0 django42: django~=4.2.0
django50: django~=5.0.0a0 django50: django~=5.0.0a0
djangoDev: https://github.com/django/django/tarball/main djangoDev: https://github.com/django/django/tarball/main
old_urllib3: urllib3<1.25
extras = extras =
# install [test] extras, unconditionally # install [test] extras, unconditionally
test test