Opened 7 months ago
Last modified 6 days ago
#36664 assigned New feature
Python 3.15 compatibility.
| Reported by: | Mariusz Felisiak | Owned by: | Mariusz Felisiak |
|---|---|---|---|
| Component: | Core (Other) | Version: | dev |
| Severity: | Normal | Keywords: | |
| Cc: | Mike Edmunds | Triage Stage: | Someday/Maybe |
| Has patch: | no | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | no |
| Easy pickings: | no | UI/UX: | no |
Description
Python 3.15 final is scheduled for October 2026 (see PEP 790). This is a tracking ticket for compatibility fixes for Django submitted in the meantime.
Django 6.1 will be the first version to support Python 3.15, because Django 6.0 will end the mainstream support in August 2026.
Change History (11)
comment:1 by , 7 months ago
| Triage Stage: | Unreviewed → Accepted |
|---|
comment:2 by , 7 months ago
| Triage Stage: | Accepted → Someday/Maybe |
|---|
comment:3 by , 7 months ago
follow-up: 7 comment:5 by , 8 days ago
| Cc: | added |
|---|
Hi Mike,
Thanks to you, the future referenced in EmailBackend.prep_address() has arrived:
if not force_ascii: # Non-ASCII local-part is valid with SMTPUTF8. Remove once # https://github.com/python/cpython/issues/81074 is fixed. defects.discard("local-part contains non-ASCII characters)")
We have on the 3.15 builds:
====================================================================== ERROR: test_rejects_non_ascii_local_part (mail.test_backends.SMTPBackendTests.test_rejects_non_ascii_local_part) The SMTP EmailBackend does not currently support non-ASCII local-parts. ---------------------------------------------------------------------- Traceback (most recent call last): File "/Users/jwalls/django/tests/mail/test_backends.py", line 832, in test_rejects_non_ascii_local_part backend.send_messages([email]) ~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^ File "/Users/jwalls/django/django/core/mail/backends/smtp.py", line 138, in send_messages sent = self._send(message) File "/Users/jwalls/django/django/core/mail/backends/smtp.py", line 155, in _send self.connection.sendmail(from_email, recipients, message.as_bytes()) ^^^^^^^^^^^^^^^^^^^^^^^^ File "/Library/Frameworks/Python.framework/Versions/3.15/lib/python3.15/unittest/mock.py", line 697, in __getattr__ raise AttributeError("Mock object has no attribute %r" % name) AttributeError: Mock object has no attribute 'sendmail'
It fails more informatively when I remove spec=object():
====================================================================== ERROR: test_rejects_non_ascii_local_part (mail.test_backends.SMTPBackendTests.test_rejects_non_ascii_local_part) The SMTP EmailBackend does not currently support non-ASCII local-parts. ---------------------------------------------------------------------- Traceback (most recent call last): File "/Library/Frameworks/Python.framework/Versions/3.15/lib/python3.15/email/_header_value_parser.py", line 2843, in _refold_without_ew tstr.encode(encoding) ~~~~~~~~~~~^^^^^^^^^^ UnicodeEncodeError: 'ascii' codec can't encode character '\xf8' in position 1: ordinal not in range(128) During handling of the above exception, another exception occurred: Traceback (most recent call last): File "/Users/jwalls/django/tests/mail/test_backends.py", line 832, in test_rejects_non_ascii_local_part backend.send_messages([email]) ~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^ File "/Users/jwalls/django/django/core/mail/backends/smtp.py", line 138, in send_messages sent = self._send(message) File "/Users/jwalls/django/django/core/mail/backends/smtp.py", line 155, in _send self.connection.sendmail(from_email, recipients, message.as_bytes()) ~~~~~~~~~~~~~~~~^^ File "/Library/Frameworks/Python.framework/Versions/3.15/lib/python3.15/email/message.py", line 214, in as_bytes g.flatten(self, unixfrom=unixfrom) ~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ File "/Library/Frameworks/Python.framework/Versions/3.15/lib/python3.15/email/generator.py", line 118, in flatten self._write(msg) ~~~~~~~~~~~^^^^^ File "/Library/Frameworks/Python.framework/Versions/3.15/lib/python3.15/email/generator.py", line 201, in _write self._write_headers(msg) ~~~~~~~~~~~~~~~~~~~^^^^^ File "/Library/Frameworks/Python.framework/Versions/3.15/lib/python3.15/email/generator.py", line 433, in _write_headers folded = self.policy.fold_binary(h, v) File "/Library/Frameworks/Python.framework/Versions/3.15/lib/python3.15/email/policy.py", line 207, in fold_binary folded = self._fold(name, value, refold_binary=self.cte_type=='7bit') File "/Library/Frameworks/Python.framework/Versions/3.15/lib/python3.15/email/policy.py", line 213, in _fold return value.fold(policy=self) ~~~~~~~~~~^^^^^^^^^^^^^ File "/Library/Frameworks/Python.framework/Versions/3.15/lib/python3.15/email/headerregistry.py", line 251, in fold return header.fold(policy=policy) ~~~~~~~~~~~^^^^^^^^^^^^^^^ File "/Library/Frameworks/Python.framework/Versions/3.15/lib/python3.15/email/_header_value_parser.py", line 170, in fold return _refold_parse_tree(self, policy=policy) File "/Library/Frameworks/Python.framework/Versions/3.15/lib/python3.15/email/_header_value_parser.py", line 2832, in _refold_parse_tree _refold_with_ew(parse_tree, lines, maxlen, encoding, policy=policy) ~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/Library/Frameworks/Python.framework/Versions/3.15/lib/python3.15/email/_header_value_parser.py", line 2936, in _refold_with_ew encoded_part = part.fold(policy=policy)[:-len(policy.linesep)] ~~~~~~~~~^^^^^^^^^^^^^^^ File "/Library/Frameworks/Python.framework/Versions/3.15/lib/python3.15/email/_header_value_parser.py", line 170, in fold return _refold_parse_tree(self, policy=policy) File "/Library/Frameworks/Python.framework/Versions/3.15/lib/python3.15/email/_header_value_parser.py", line 2832, in _refold_parse_tree _refold_with_ew(parse_tree, lines, maxlen, encoding, policy=policy) ~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/Library/Frameworks/Python.framework/Versions/3.15/lib/python3.15/email/_header_value_parser.py", line 2936, in _refold_with_ew encoded_part = part.fold(policy=policy)[:-len(policy.linesep)] ~~~~~~~~~^^^^^^^^^^^^^^^ File "/Library/Frameworks/Python.framework/Versions/3.15/lib/python3.15/email/_header_value_parser.py", line 170, in fold return _refold_parse_tree(self, policy=policy) File "/Library/Frameworks/Python.framework/Versions/3.15/lib/python3.15/email/_header_value_parser.py", line 2834, in _refold_parse_tree _refold_without_ew(parse_tree, lines, maxlen, encoding, policy=policy) ~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/Library/Frameworks/Python.framework/Versions/3.15/lib/python3.15/email/_header_value_parser.py", line 2854, in _refold_without_ew raise errors.HeaderWriteError( ...<2 lines>... ) email.errors.HeaderWriteError: Non-ASCII local-part 'nø' is invalid under current policy setting (utf8=False) ----------------------------------------------------------------------
Should we be catching email.errors.HeaderWriteError and re-raising in the code, or should we be adjusting the test for 3.15? And should we do anything about the if not force_ascii: branch for now? Appreciate your advice.
comment:6 by , 8 days ago
On another topic, we have a failure in test_max_diff also due to the new pprint defaults. It's only a matter of doing this, because the output of assertDictEqual() is now more compact...
-
tests/test_utils/tests.py
diff --git a/tests/test_utils/tests.py b/tests/test_utils/tests.py index e54b9ef624..18a35c018a 100644
a b class AssertQuerySetEqualTests(TestCase): 353 353 ) 354 354 355 355 def test_maxdiff(self): 356 names = ["Joe Smith %s" % i for i in range(2 0)]356 names = ["Joe Smith %s" % i for i in range(25)] 357 357 Person.objects.bulk_create([Person(name=name) for name in names]) 358 358 names.append("Extra Person")
but they change might be reverted, so let's wait.
comment:7 by , 7 days ago
Replying to Jacob Walls:
We have on the 3.15 builds:
ERROR: test_rejects_non_ascii_local_part (mail.test_backends.SMTPBackendTests.test_rejects_non_ascii_local_part) The SMTP EmailBackend does not currently support non-ASCII local-parts.[...]
Should we be catching
email.errors.HeaderWriteErrorand re-raising in the code, or should we be adjusting the test for 3.15? And should we do anything about theif not force_ascii:branch for now? Appreciate your advice.
I'll open a PR to update the test. I think the right approach to catching unsupported non-ASCII email usernames is:
- Let stdlib email report the problem when it's capable (i.e., 3.15+), as a
HeaderWriteError: Non-ASCII local-part … is invalid under current policy. I know we try to avoid testing stdlib behavior, but given the upstream flux in this area we might want to ensure something useful gets reported to the user (without pinning our tests to the precise stdlib error). - Continue to raise (and test) our own
ValueErrorinprep_address()if stdlib email would incorrectly encode the email as something undeliverable (i.e., before 3.15; there are currently no plans to backport the bugfix from 3.15 due to compatibility concerns).
It fails more informatively when I remove
spec=object():
There's actually no need for that test to mock the smtplib connection in the first place; I'll remove it.
comment:8 by , 7 days ago
Just a heads up that the markers in tests/requirements/py3.txt should probably be sys_platform rather than sys.platform. Also, there's no numpy wheel available for 3.150b1 on macOS, and the build requirements seem complex, so I just disabled it like was already done for win32.
-
tests/requirements/py3.txt
diff --git a/tests/requirements/py3.txt b/tests/requirements/py3.txt
a b 6 6 docutils >= 0.22 7 7 geoip2 >= 4.8.0 8 8 jinja2 >= 2.11.0 9 numpy >= 1.26.0; sys .platform != 'win32' or python_version < '3.15'10 Pillow >= 10.1.0; sys .platform != 'win32' or python_version < '3.15'9 numpy >= 1.26.0; sys_platform != 'win32' and sys_platform != 'darwin' or python_version < '3.15' 10 Pillow >= 10.1.0; sys_platform != 'win32' or python_version < '3.15' 11 11 # pylibmc/libmemcached can't be built on Windows. 12 12 pylibmc; sys_platform != 'win32' 13 13 pymemcache >= 3.4.0
comment:9 by , 7 days ago
| Has patch: | set |
|---|
Email test updates: https://github.com/django/django/pull/21284.
(Don't know whether I'm supposed to mark "has patch" for refs PRs, but figured it wouldn't hurt to put it in the review queue.)
comment:11 by , 6 days ago
| Has patch: | unset |
|---|
In 5e2bbebe: