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: |
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: |

View File

@@ -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
-----

View File

@@ -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

View File

@@ -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"

View File

@@ -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
View File

@@ -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