From 336e1ea3a5aa9f9b006a1fad693cbae0f810caf5 Mon Sep 17 00:00:00 2001 From: Akhil Narang Date: Mon, 29 Sep 2025 17:59:52 +0530 Subject: [PATCH 01/11] build: update python version range Signed-off-by: Akhil Narang --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 62af31e0de..49b5037f49 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ authors = [ { name = "Frappe Technologies Pvt Ltd", email = "developers@frappe.io"} ] description = "Metadata driven, full-stack low code web framework" -requires-python = ">=3.10,<3.14" +requires-python = ">=3.10,<3.15" readme = "README.md" dynamic = ["version"] dependencies = [ From 9f2dba7bef3d8b352eb5dd36f5f2367d5025d90b Mon Sep 17 00:00:00 2001 From: Akhil Narang Date: Mon, 29 Sep 2025 18:02:21 +0530 Subject: [PATCH 02/11] chore(ci): bump python version Signed-off-by: Akhil Narang --- .github/workflows/_base-server-tests.yml | 2 +- .github/workflows/_base-type-check.yml | 2 +- .github/workflows/_base-ui-tests.yml | 2 +- .github/workflows/generate-pot-file.yml | 2 +- .github/workflows/linters.yml | 8 ++++---- .github/workflows/on_release.yml | 2 +- .github/workflows/publish-assets-develop.yml | 2 +- .github/workflows/run-indinvidual-tests.yml | 2 +- 8 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/_base-server-tests.yml b/.github/workflows/_base-server-tests.yml index d76a380c41..a0b9ef2391 100644 --- a/.github/workflows/_base-server-tests.yml +++ b/.github/workflows/_base-server-tests.yml @@ -9,7 +9,7 @@ on: python-version: required: false type: string - default: '3.13' + default: '3.14' node-version: required: false type: number diff --git a/.github/workflows/_base-type-check.yml b/.github/workflows/_base-type-check.yml index ed9caea616..9add0eae7f 100644 --- a/.github/workflows/_base-type-check.yml +++ b/.github/workflows/_base-type-check.yml @@ -5,7 +5,7 @@ on: python-version: required: false type: string - default: '3.13.0' + default: '3.14.0' jobs: typecheck: diff --git a/.github/workflows/_base-ui-tests.yml b/.github/workflows/_base-ui-tests.yml index 1d53350961..d63e2ec48e 100644 --- a/.github/workflows/_base-ui-tests.yml +++ b/.github/workflows/_base-ui-tests.yml @@ -9,7 +9,7 @@ on: python-version: required: false type: string - default: '3.13' + default: '3.14' node-version: required: false type: number diff --git a/.github/workflows/generate-pot-file.yml b/.github/workflows/generate-pot-file.yml index b598c5941c..b17fe1b740 100644 --- a/.github/workflows/generate-pot-file.yml +++ b/.github/workflows/generate-pot-file.yml @@ -25,7 +25,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v6 with: - python-version: "3.13" + python-version: "3.14" - name: Run script to update POT file run: | diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml index 241a778fa8..abc23ebd83 100644 --- a/.github/workflows/linters.yml +++ b/.github/workflows/linters.yml @@ -41,7 +41,7 @@ jobs: - name: 'Setup Environment' uses: actions/setup-python@v6 with: - python-version: '3.13' + python-version: '3.14' - uses: actions/checkout@v5 - name: Validate Docs @@ -60,7 +60,7 @@ jobs: - uses: actions/checkout@v5 - uses: actions/setup-python@v6 with: - python-version: '3.13' + python-version: '3.14' cache: pip - name: Download Semgrep rules @@ -78,7 +78,7 @@ jobs: steps: - uses: actions/setup-python@v6 with: - python-version: '3.13' + python-version: '3.14' - uses: actions/checkout@v5 @@ -106,6 +106,6 @@ jobs: - uses: actions/checkout@v5 - uses: actions/setup-python@v6 with: - python-version: '3.13' + python-version: '3.14' cache: pip - uses: pre-commit/action@v3.0.1 diff --git a/.github/workflows/on_release.yml b/.github/workflows/on_release.yml index 597f942830..4372beb9ff 100644 --- a/.github/workflows/on_release.yml +++ b/.github/workflows/on_release.yml @@ -26,7 +26,7 @@ jobs: - uses: actions/setup-python@v6 with: - python-version: '3.13' + python-version: '3.14' - name: Set up bench and build assets run: | npm install -g yarn diff --git a/.github/workflows/publish-assets-develop.yml b/.github/workflows/publish-assets-develop.yml index fe2ec1c3f9..33ae37f844 100644 --- a/.github/workflows/publish-assets-develop.yml +++ b/.github/workflows/publish-assets-develop.yml @@ -19,7 +19,7 @@ jobs: node-version: 22 - uses: actions/setup-python@v6 with: - python-version: '3.13' + python-version: '3.14' - name: Set up bench and build assets run: | npm install -g yarn diff --git a/.github/workflows/run-indinvidual-tests.yml b/.github/workflows/run-indinvidual-tests.yml index cb127b7fd7..9bc21feff1 100644 --- a/.github/workflows/run-indinvidual-tests.yml +++ b/.github/workflows/run-indinvidual-tests.yml @@ -74,7 +74,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v6 with: - python-version: '3.13' + python-version: '3.14' - name: Setup Node uses: actions/setup-node@v6 From 4ff5297d9ed139bd392c71f32116708e7daca030 Mon Sep 17 00:00:00 2001 From: Akhil Narang Date: Mon, 29 Sep 2025 17:50:01 +0530 Subject: [PATCH 03/11] build(deps): bump pypika Signed-off-by: Akhil Narang --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 49b5037f49..8eb74a6aee 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ dependencies = [ # do NOT add loose requirements on PyMySQL versions. "PyMySQL==1.1.1", "pypdf~=6.1.3", - "PyPika @ git+https://github.com/frappe/pypika@093984977ce157d35e048c51d9ff55a1f0f44570", + "PyPika @ git+https://github.com/frappe/pypika@2c50e6142b2d61d2d243e466fdd5dc03b3d918f2", "mysqlclient==2.2.7", "PyQRCode~=1.2.1", "PyYAML~=6.0.2", From 9d45a931d1bc4fc9496b42245ef284595eb36961 Mon Sep 17 00:00:00 2001 From: Akhil Narang Date: Mon, 29 Sep 2025 17:59:46 +0530 Subject: [PATCH 04/11] build(deps): bump orjson Signed-off-by: Akhil Narang --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 8eb74a6aee..2ca20501a0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,7 +47,7 @@ dependencies = [ "num2words~=0.5.14", "oauthlib~=3.2.2", "openpyxl~=3.1.5", - "orjson~=3.10.18", + "orjson~=3.11.3", "passlib~=1.7.4", "pdfkit~=1.0.0", "phonenumbers~=9.0.7", From c00a06445f967d08a2e38922d3011c480d46c46f Mon Sep 17 00:00:00 2001 From: Akhil Narang Date: Mon, 29 Sep 2025 18:00:01 +0530 Subject: [PATCH 05/11] build(deps): bump pydantic version Signed-off-by: Akhil Narang --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 2ca20501a0..80f4cc7787 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,7 +55,7 @@ dependencies = [ "psutil~=7.0.0", "psycopg2-binary~=2.9.1", "pyOpenSSL~=25.1.0", - "pydantic~=2.11.7", + "pydantic~=2.12.0", "pyotp~=2.9.0", "python-dateutil~=2.9.0", "pytz==2025.2", From 0c3be512db218af73ce313ae9dd1cf119a7808dd Mon Sep 17 00:00:00 2001 From: Akhil Narang Date: Wed, 8 Oct 2025 13:11:03 +0530 Subject: [PATCH 06/11] build(deps): bump RestrictedPython version [change to non-alpha before merging] Signed-off-by: Akhil Narang --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 80f4cc7787..cbfbe53c4c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,7 +25,7 @@ dependencies = [ "mysqlclient==2.2.7", "PyQRCode~=1.2.1", "PyYAML~=6.0.2", - "RestrictedPython~=8.0", + "RestrictedPython~=8.1", "WeasyPrint==66.0", "pydyf==0.11.0", "Werkzeug==3.1.3", From 770024f048951a8e5fc302cdc2602debba09eae8 Mon Sep 17 00:00:00 2001 From: Akhil Narang Date: Wed, 8 Oct 2025 18:30:46 +0530 Subject: [PATCH 07/11] fix: adjust refcount test for python3.14 References: - https://docs.python.org/3.14/whatsnew/3.14.html#optimizations - https://docs.python.org/3.14/whatsnew/3.14.html#whatsnew314-refcount Signed-off-by: Akhil Narang --- frappe/tests/test_perf.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frappe/tests/test_perf.py b/frappe/tests/test_perf.py index 241ed853c6..1e42c0f095 100644 --- a/frappe/tests/test_perf.py +++ b/frappe/tests/test_perf.py @@ -193,14 +193,16 @@ class TestPerformance(IntegrationTestCase): """ query = "select * from tabUser" + expected_refcount = 1 if sys.version_info >= (3, 14) else 2 for kwargs in ({}, {"as_dict": True}, {"as_list": True}): result = frappe.db.sql(query, **kwargs) - self.assertEqual(sys.getrefcount(result), 2) # Note: This always returns +1 + self.assertEqual(sys.getrefcount(result), expected_refcount) # Note: This always returns +1 self.assertFalse(gc.get_referrers(result)) def test_no_cyclic_references(self): doc = frappe.get_doc("User", "Administrator") - self.assertEqual(sys.getrefcount(doc), 2) # Note: This always returns +1 + expected_refcount = 1 if sys.version_info >= (3, 14) else 2 + self.assertEqual(sys.getrefcount(doc), expected_refcount) # Note: This always returns +1 def test_get_doc_cache_calls(self): frappe.get_doc("User", "Administrator") From b83ae4d7938ebf5324646f2d66cdbdde47df8379 Mon Sep 17 00:00:00 2001 From: Akhil Narang Date: Wed, 8 Oct 2025 18:39:12 +0530 Subject: [PATCH 08/11] build(deps): bump cryptography and openssl This allows us to use 3.14 freethreaded Signed-off-by: Akhil Narang --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index cbfbe53c4c..038a15cc02 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,7 +35,7 @@ dependencies = [ "bleach[css]~=6.2.0", "chardet~=5.2.0", "croniter~=6.0.0", - "cryptography~=45.0.4", + "cryptography~=46.0.2", "cssutils~=2.11.1", "email-reply-parser~=0.5.12", "gunicorn @ git+https://github.com/frappe/gunicorn@bb554053bb87218120d76ab6676af7015680e8b6", @@ -54,7 +54,7 @@ dependencies = [ "premailer~=3.10.0", "psutil~=7.0.0", "psycopg2-binary~=2.9.1", - "pyOpenSSL~=25.1.0", + "pyOpenSSL~=25.3.0", "pydantic~=2.12.0", "pyotp~=2.9.0", "python-dateutil~=2.9.0", From 6e20cde86213a751350aad5ee32c272edc06767f Mon Sep 17 00:00:00 2001 From: Akhil Narang Date: Thu, 9 Oct 2025 12:21:28 +0530 Subject: [PATCH 09/11] chore: temporarily raise allow higher memory usage in basic rq job Signed-off-by: Akhil Narang --- frappe/core/doctype/rq_job/test_rq_job.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/frappe/core/doctype/rq_job/test_rq_job.py b/frappe/core/doctype/rq_job/test_rq_job.py index accc6712b8..cb9bfd6092 100644 --- a/frappe/core/doctype/rq_job/test_rq_job.py +++ b/frappe/core/doctype/rq_job/test_rq_job.py @@ -176,6 +176,13 @@ class TestRQJob(IntegrationTestCase): if frappe.conf.use_mysqlclient: # TEMP: Add extra allowance for running two connectors, this should be rolled back before v16 LAST_MEASURED_USAGE += 2 + + # Observed higher usage on 3.14. Temporarily raising the limit + from sys import version_info + + if version_info >= (3, 14): + LAST_MEASURED_USAGE += 5 + self.assertLessEqual(rss, LAST_MEASURED_USAGE * 1.05, msg) def test_clear_failed_jobs(self): From edc4b4bbd52536367e90eebf00d0176e43096652 Mon Sep 17 00:00:00 2001 From: Akhil Narang Date: Mon, 3 Nov 2025 13:04:40 +0530 Subject: [PATCH 10/11] fix: set multiprocessing start mode to `fork` instead of `forkserver` worker-pool with fork workers seem to not pick up jobs otherwise - the test `test_multi_queue_burst_consumption_worker_pool` just times out. https://docs.python.org/3/whatsnew/3.14.html#concurrent-futures Signed-off-by: Akhil Narang --- frappe/utils/background_jobs.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/frappe/utils/background_jobs.py b/frappe/utils/background_jobs.py index 34ac14d463..9c57ed5ddb 100644 --- a/frappe/utils/background_jobs.py +++ b/frappe/utils/background_jobs.py @@ -2,6 +2,7 @@ import os import random import signal import socket +import sys import time from collections import defaultdict from collections.abc import Callable @@ -373,6 +374,7 @@ class FrappeWorker(Worker): def start_frappe_scheduler(self): from frappe.utils.scheduler import start_scheduler + # TODO: switch to multiprocessing.Process() after further investigating of fork -> forkserver Thread(target=start_scheduler, daemon=True).start() @@ -418,7 +420,6 @@ def start_worker_pool( WARNING: This feature is considered "EXPERIMENTAL". """ - _start_sentry() # If gc.freeze is done then importing modules before forking allows us to share the memory @@ -447,9 +448,15 @@ def start_worker_pool( logging_level = "WARNING" # TODO: Make this true by default eventually. It's limited to RQ WorkerPool - no_fork = sbool(os.environ.get("FRAPPE_BACKGROUND_WORKERS_NOFORK", False)) + if sbool(os.environ.get("FRAPPE_BACKGROUND_WORKERS_NOFORK", False)): + worker_klass = FrappeWorkerNoFork + else: + if sys.version_info >= (3, 14): + import multiprocessing + + multiprocessing.set_start_method("fork", force=True) + worker_klass = FrappeWorker - worker_klass = FrappeWorkerNoFork if no_fork else FrappeWorker pool = WorkerPool( queues=queues, connection=redis_connection, From 60e714e2e13a06fcbae87644434f14a8fc2a699b Mon Sep 17 00:00:00 2001 From: Akhil Narang Date: Mon, 3 Nov 2025 23:15:11 +0530 Subject: [PATCH 11/11] fix: `commit` before trying to delete user Signed-off-by: Akhil Narang --- frappe/integrations/doctype/connected_app/test_connected_app.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frappe/integrations/doctype/connected_app/test_connected_app.py b/frappe/integrations/doctype/connected_app/test_connected_app.py index f063b2e920..e69ad7ce54 100644 --- a/frappe/integrations/doctype/connected_app/test_connected_app.py +++ b/frappe/integrations/doctype/connected_app/test_connected_app.py @@ -144,6 +144,8 @@ class TestConnectedApp(IntegrationTestCase): doc = frappe.get_doc("OAuth Authorization Code", code.name) doc.delete() + frappe.db.commit() + delete_if_exists("user") delete_if_exists("oauth_client")