48525 backend [ backend ] Upgrade Python version to 3.8. - #337
Merged
andrey-usov merged 7 commits intoAug 21, 2026
Conversation
- Bump backend base image to python:3.8.20-bullseye - Set 3.8.20 in .python-version and python3.8 as pre-commit language version - Update local setup instructions in backend/README.md Co-authored-by: Cursor <cursoragent@cursor.com>
- Raise python constraint to ~3.8.20 and Django to 2.2.28, the final 2.2 LTS patch, which adds Python 3.8 support and 28 releases of security fixes - Drop 7 direct dependencies unused in backend/src: recurly, ibm-cos-sdk, xmltodict, defusedxml, dj-database-url, python-slugify, iso8601 - Drop the direct importlib-metadata pin, redundant since importlib.metadata is part of the standard library in Python 3.8 - Relock with --no-update to keep the diff minimal: 16 packages removed, including the Python 2 backports futures, backports-functools-lru-cache and backports-ssl-match-hostname Co-authored-by: Cursor <cursoragent@cursor.com>
- Align the linter target with the new runtime in backend/ruff.toml - Import OrderedDict from typing instead of typing_extensions, the only UP035 finding the py38 target exposes; typing.OrderedDict exists since 3.7.2 Co-authored-by: Cursor <cursoragent@cursor.com>
- Point Windows setup instructions to Python 3.8.10, the last 3.8 release shipping a Windows binary installer: 3.8.11+ are source-only, so the previous 3.8.20 link left Windows developers unable to install the runtime - Relax the poetry python constraint from ~3.8.20 to ~3.8 so that poetry install works with the Windows installer version; the production runtime stays pinned to 3.8.20 by backend/Dockerfile Co-authored-by: Cursor <cursoragent@cursor.com>
- Django 2.2.18 dropped ";" from the QueryDict separator regex as part of the CVE-2021-23336 fix: FIELDS_MATCH changed from "[&;]" to "&" - 4 workflow list tests combined the fields and template_id filters with ";", so on Django 2.2.28 both landed in a single "fields" value, the template_id filter was ignored and no field matched Co-authored-by: Cursor <cursoragent@cursor.com>
Bugbot is paused — on-demand spend limit reachedBugbot uses usage-based billing for this team and has hit its on-demand spend limit. A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue. |
pneumojoseph
approved these changes
Aug 18, 2026
- Merge 12 commits from master into the Python 3.8 upgrade branch - No conflicts; incoming changes are frontend-only plus start.sh, scripts/create_backup.sh and docker-compose.src.yml Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
timofey-zeph
approved these changes
Aug 21, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The backend runs on a Python release that stopped receiving security updates in June 2023, and on a Django release that predates 28 patch releases of security fixes. Any vulnerability found in the runtime or the framework stays unpatched, and the outdated runtime blocks upgrades of the libraries the product depends on, which slows down delivery of everything else. This is the first step of moving the platform onto a supported runtime.
Fix / Solution
Moved the backend runtime from Python 3.7.5 to 3.8.20, the final release of the 3.8 series, and Django from 2.2 to 2.2.28, the final 2.2 LTS patch, which is the first Django release with official Python 3.8 support.
backend/Dockerfilebase imagepython:3.7.11-bullseye→python:3.8.20-bullseye;.python-versionand the pre-commit language version follow.django = "2.2"→django = "2.2.28",python = "^3.7.5"→python = "~3.8.20".backend/src:recurly,ibm-cos-sdk,xmltodict,defusedxml,dj-database-url,python-slugify,iso8601. Also dropped the directimportlib-metadatapin, redundant becauseimportlib.metadatais part of the standard library from 3.8 on.poetry lock --no-updateto keep the diff reviewable: 16 packages leftpoetry.lockand only Django changed version. Among the removed are the Python 2 backportsfutures,backports-functools-lru-cacheandbackports-ssl-match-hostname, which the removed SDKs were pulling in.backend/ruff.tomltarget-versionpy37→py38. The only new finding it exposes is oneUP035in a test importingOrderedDictfromtyping_extensions; it now comes fromtyping.ruff checkandpycodestyleare clean.No application code was touched apart from that single import, and no cosmetic changes are included: the whole diff is 6 config files plus
poetry.lock.Verified on the built image: Python 3.8.20, Django 2.2.28, all C-extensions install without compiling from source on amd64 (
psycopg2-binary,uvloop,httptools,websockets,twisted),importlib.metadataresolves from the standard library, and pytest collects 6161 tests with no collection errors.Release notes
No user-facing changes. The service moves to a supported Python runtime and to the latest security patch of the current Django release, closing known vulnerabilities in the framework, including the admin privilege escalation CVE-2019-19118 and the SQL injections CVE-2021-35042, CVE-2022-28346 and CVE-2022-28347.
One API behaviour change comes with those patches. As part of the fix for CVE-2021-23336 (web cache poisoning), Django 2.2.18 stopped treating a semicolon as a query string parameter separator:
QueryDictnow splits on&only. A request such asGET /workflows?fields=api-name-1;template_id=5used to be read as two filters and is now read as a singlefieldsvalue ofapi-name-1;template_id=5. Standard&-separated requests are unaffected, and neither the web client nor any other project code builds semicolon-separated query strings.Changes
API
No endpoints, serializers or business logic changed. Configuration only:
backend/Dockerfile— base imagebackend/pyproject.toml,backend/poetry.lock— Python constraint, Django version, removed dependenciesbackend/ruff.toml— linter target versionbackend/README.md— local setup instructions.python-version,.pre-commit-config.yaml— local and hook runtimebackend/src/processes/tests/test_webhooks/test_webhooks.py—OrderedDictimported fromtypingbackend/src/processes/tests/test_views/test_workflow/test_list.py— 4 tests combined thefieldsandtemplate_idfilters with;, which Django 2.2.28 no longer accepts as a separator; switched to&Web-client
No changes.
Local verification
The stack was brought up from source on the new runtime (
docker compose -f docker-compose.src.yml up -d --build backend celery celery-beat), which also exercisesmigrate,collectstatic,init_periodic_tasksandcompilemessageson startup.python 3.8.20,django 2.2.28Application startup complete/admin/login//api/schema(drf-spectacular)openapi: 3.0.3/workflows,/accounts/users,/templates,/v3/taskswith a token/ws/events101 Switching Protocolspong, 110 tasks processed, queue drained, no tracebackslinux/arm64imageaarch64The full suite was run module by module rather than in a single invocation, so a failure points at a module immediately. The per-module totals add up to 6161, which matches
pytest --collect-only, so the split covers every test. Longest modules:processes/test_services1063 tests in 27 min,processes/test_views/test_tasks330 in 24 min,processes/test_views/test_workflow536 in 17 min.The
linux/arm64image was built locally under qemu emulation to get a pre-merge signal, since CI only builds multi-arch on push tomaster. It succeeds without touching the Dockerfile:psycopg2-binary 2.8.5,uvloop 0.14.0,httptools 0.1.1,websockets 8.1andtwisted 20.3.0have no aarch64 wheels for cp38 and are compiled from sdist, which the base image already supports —python:3.8.20-bullseyeis the full variant and ships gcc 10.2.1 andlibpq-fe.h. The same packages have no aarch64 wheels for cp37 either, so CI has been compiling them from source all along; this is not a change introduced by the upgrade.Two things reviewers and QA should know about, neither introduced by this PR:
RuntimeWarning: line buffering (buffering=1) isn't supported in binary mode, the default buffer size will be usedfromos.py.gunicorn19.9.0 opens a file descriptor in binary mode with line buffering, and Python 3.8 started warning about it; 3.7 did not. No functional impact, buffering just falls back to the default. It goes away only withgunicorn20+, which is out of scope here. Please do not read it as a regression.makemigrations --check --dry-runreports pending migrations inaccounts(Meta onapikey,max_usersonaccount),logs(event_typeonaccountevent),permissions(indexes and a constraint onuserobjectpermission/groupobjectpermission) andprocesses(choicesonworkflowevent.type). The output is byte-for-byte identical onmaster, so the drift predates this branch. All of it is field metadata, no DDL. Left untouched here to keep the upgrade free of unrelated migrations.Test cases
Django 2.2.28 changes admin inline permissions (CVE-2019-19118): an inline becomes read-only when the user only holds
viewon the parent model. That is the one behaviour change a user can notice, so admin scenarios come first.API
viewonly on the parent modelchangeon the parent modelGET /workflows?fields=<api_name>&template_id=<id>migrateapplies cleanly,migrate --checkreports no pending migrationsWeb-client
Detailed integration scenarios for QA, covering all 15 external integrations, are in the upgrade plan attached to the Pneumatic task, section 4.8.
Release note for the releaser: the backend, celery and celery-beat images have to be rebuilt, since the base image changed. Rollback is the previous image tag together with a revert of this branch.
Made with Cursor
Note
Upgrade backend Python version from 3.7 to 3.8
pyproject.toml,ruff.toml, and pre-commit hooks to use Python 3.8.2.2to2.2.28and removes several unused dependencies (recurly,dj-database-url,ibm-cos-sdk,xmltodict,python-slugify,defusedxml,iso8601,importlib-metadata).&instead of;as parameter separator, and switchesOrderedDictimport fromtyping_extensionstotyping, both required for Python 3.8 compatibility.&separator behavior.Macroscope summarized 2bfa14d.