From da20643ad28a575663c408a1110a35d5cedb655f Mon Sep 17 00:00:00 2001 From: Richard Tibbles Date: Fri, 18 Oct 2024 17:04:51 -0700 Subject: [PATCH 1/2] Add support and testing for Python 3.13 --- .github/workflows/tox.yml | 4 ++-- setup.py | 3 ++- tox.ini | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/tox.yml b/.github/workflows/tox.yml index 66eee24..c0c0177 100644 --- a/.github/workflows/tox.yml +++ b/.github/workflows/tox.yml @@ -22,7 +22,7 @@ jobs: strategy: max-parallel: 5 matrix: - python-version: [ 3.6, 3.7, 3.8, 3.9, '3.10', '3.11', '3.12' ] + python-version: [ 3.6, 3.7, 3.8, 3.9, '3.10', '3.11', '3.12', '3.13' ] steps: - uses: actions/checkout@v2 @@ -54,7 +54,7 @@ jobs: strategy: max-parallel: 5 matrix: - python-version: [ 3.6, 3.7, 3.8, 3.9, '3.10', '3.11', '3.12' ] + python-version: [ 3.6, 3.7, 3.8, 3.9, '3.10', '3.11', '3.12', '3.13' ] steps: - uses: actions/checkout@v2 diff --git a/setup.py b/setup.py index d867f6f..daedec9 100644 --- a/setup.py +++ b/setup.py @@ -46,6 +46,7 @@ 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', + 'Programming Language :: Python :: 3.13', ], - python_requires=">=3.6, <3.13", + python_requires=">=3.6, <3.14", ) diff --git a/tox.ini b/tox.ini index 5b64f89..46e0408 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] envlist = - py{3.6,3.7,3.8,3.9,3.10,3.11,3.12}-cryptography{40.0.2} + py{3.6,3.7,3.8,3.9,3.10,3.11,3.12,3.13}-cryptography{40.0.2} postgres windows @@ -20,6 +20,7 @@ basepython = py3.10: python3.10 py3.11: python3.11 py3.12: python3.12 + py3.13: python3.13 postgres: python3.9 windows: python3.8 From 1d306597deb9821e5ea86b52ba5530a12375809a Mon Sep 17 00:00:00 2001 From: Richard Tibbles Date: Fri, 18 Oct 2024 21:43:10 -0700 Subject: [PATCH 2/2] Cleanup no longer needed monkey patches. Forward port cgi module for use in Python 3.13. --- tests/testapp/testapp/__init__.py | 75 ++++--------------------------- tests/testapp/testapp/cgi.py | 47 +++++++++++++++++++ 2 files changed, 55 insertions(+), 67 deletions(-) create mode 100644 tests/testapp/testapp/cgi.py diff --git a/tests/testapp/testapp/__init__.py b/tests/testapp/testapp/__init__.py index 150cbd4..d06f1b5 100644 --- a/tests/testapp/testapp/__init__.py +++ b/tests/testapp/testapp/__init__.py @@ -1,76 +1,17 @@ import sys -def monkey_patch_collections(): +def forward_port_cgi_module(): """ - Monkey-patching for the collections module is required for Python 3.10 - and above. - Prior to 3.10, the collections module still contained all the entities defined in - collections.abc from Python 3.3 onwards. Here we patch those back into main - collections module. - This can be removed when we upgrade to a version of Django that is Python 3.10 compatible. - Copied from: - https://github.com/learningequality/kolibri/blob/589dd15aa79e8694aff8754bb34f12384315dbb6/kolibri/utils/compat.py#L90 + Forward ports the required parts of the removed cgi module. + This can be removed when we upgrade to a version of Django that is Python 3.13 compatible. """ - if sys.version_info < (3, 10): + if sys.version_info < (3, 13): return - import collections - from collections import abc + from importlib import import_module - for name in dir(abc): - if not hasattr(collections, name): - setattr(collections, name, getattr(abc, name)) + module = import_module("testapp.cgi") + sys.modules["cgi"] = module -monkey_patch_collections() - - -def monkey_patch_translation(): - """ - Monkey-patching for the gettext module is required for Python 3.11 - and above. - Prior to 3.11, the gettext module classes still had the deprecated set_output_charset - This can be removed when we upgrade to a version of Django that no longer relies - on this deprecated Python 2.7 only call. - Copied from: - https://github.com/learningequality/kolibri/blob/589dd15aa79e8694aff8754bb34f12384315dbb6/kolibri/utils/compat.py#L109 - """ - if sys.version_info < (3, 11): - return - - import gettext - - def set_output_charset(*args, **kwargs): - pass - - gettext.NullTranslations.set_output_charset = set_output_charset - - original_translation = gettext.translation - - def translation( - domain, - localedir=None, - languages=None, - class_=None, - fallback=False, - codeset=None, - ): - return original_translation( - domain, - localedir=localedir, - languages=languages, - class_=class_, - fallback=fallback, - ) - - gettext.translation = translation - - original_install = gettext.install - - def install(domain, localedir=None, codeset=None, names=None): - return original_install(domain, localedir=localedir, names=names) - - gettext.install = install - - -monkey_patch_translation() +forward_port_cgi_module() diff --git a/tests/testapp/testapp/cgi.py b/tests/testapp/testapp/cgi.py new file mode 100644 index 0000000..a825ef5 --- /dev/null +++ b/tests/testapp/testapp/cgi.py @@ -0,0 +1,47 @@ +""" +A minimal port of the removed cgi module for use in Python 3.13. +Only imports the specific parts of the module that are used by Django. +Informed by the PR that removed its use in Django: +https://github.com/django/django/pull/15679 +""" +from django.utils.regex_helper import _lazy_re_compile + + +def _parseparam(s): + while s[:1] == ";": + s = s[1:] + end = s.find(";") + while end > 0 and (s.count('"', 0, end) - s.count('\\"', 0, end)) % 2: + end = s.find(";", end + 1) + if end < 0: + end = len(s) + f = s[:end] + yield f.strip() + s = s[end:] + + +def parse_header(line): + """ + Parse a Content-type like header. + Return the main content-type and a dictionary of options. + """ + parts = _parseparam(";" + line) + key = parts.__next__() + pdict = {} + for p in parts: + i = p.find("=") + if i >= 0: + name = p[:i].strip().lower() + value = p[i + 1 :].strip() + if len(value) >= 2 and value[0] == value[-1] == '"': + value = value[1:-1] + value = value.replace("\\\\", "\\").replace('\\"', '"') + pdict[name] = value + return key, pdict + + +boundary_re = _lazy_re_compile(rb"[ -~]{0,200}[!-~]") + + +def valid_boundary(boundary): + return boundary_re.fullmatch(boundary) is not None