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 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): 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") 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") 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, diff --git a/pyproject.toml b/pyproject.toml index 62af31e0de..038a15cc02 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 = [ @@ -21,11 +21,11 @@ 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", - "RestrictedPython~=8.0", + "RestrictedPython~=8.1", "WeasyPrint==66.0", "pydyf==0.11.0", "Werkzeug==3.1.3", @@ -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", @@ -47,15 +47,15 @@ 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", "premailer~=3.10.0", "psutil~=7.0.0", "psycopg2-binary~=2.9.1", - "pyOpenSSL~=25.1.0", - "pydantic~=2.11.7", + "pyOpenSSL~=25.3.0", + "pydantic~=2.12.0", "pyotp~=2.9.0", "python-dateutil~=2.9.0", "pytz==2025.2",