From fea16239bd4c7c69c6b7c7739ee01e1da55a103d Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 28 Dec 2023 13:48:35 +0530 Subject: [PATCH 01/48] fix: force `[]` as default for child tables (#24000) (cherry picked from commit ad8ad088c50ced942336ae9e10ada3e46ea9fb78) --- frappe/public/js/frappe/model/create_new.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frappe/public/js/frappe/model/create_new.js b/frappe/public/js/frappe/model/create_new.js index 159e39e3a73..23bd43d663e 100644 --- a/frappe/public/js/frappe/model/create_new.js +++ b/frappe/public/js/frappe/model/create_new.js @@ -151,10 +151,6 @@ $.extend(frappe.model, { df.ignore_user_permissions != 1 && allowed_records.length; - if (frappe.model.table_fields.includes(df.fieldtype)) { - value = []; - } - // don't set defaults for "User" link field using User Permissions! if (df.fieldtype === "Link" && df.options !== "User") { // If user permission has Is Default enabled or single-user permission has found against respective doctype. @@ -232,6 +228,10 @@ $.extend(frappe.model, { value = frappe.datetime.now_time(); } + if (frappe.model.table_fields.includes(df.fieldtype)) { + value = []; + } + // set it here so we know it was set as a default df.__default_value = value; From 2045340abedaef0e89ec80162eafaf2d00508d83 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 20 Feb 2024 19:23:53 +0530 Subject: [PATCH 02/48] feat: socketio using authorization headers (backport #24858) (#24966) * feat: socketio using authorization headers Earlier socketio only worked in browser where browser would send cookie (cause same domain) and hence socketio server used it to auth connection. This however is limited and doesn't allow simply creating socket connection from apps. Authorization headers on other hand are simple to implement. (cherry picked from commit 11ea7e41795e8366d76fde8d9f85e2296a30bc57) # Conflicts: # realtime/utils.js * fix: ignore if no cookies are sent (cherry picked from commit a35e6b49757cbced23dda10588c18a16a0ee3720) * chore: conflicts --------- Co-authored-by: Ankush Menat --- realtime/handlers/frappe_handlers.js | 11 +++-------- realtime/middlewares/authenticate.js | 21 +++++++++++++-------- realtime/utils.js | 13 +++++++++++++ 3 files changed, 29 insertions(+), 16 deletions(-) diff --git a/realtime/handlers/frappe_handlers.js b/realtime/handlers/frappe_handlers.js index 923c0858b4f..d56090ffb27 100644 --- a/realtime/handlers/frappe_handlers.js +++ b/realtime/handlers/frappe_handlers.js @@ -1,5 +1,4 @@ -const request = require("superagent"); -const { get_url } = require("../utils"); +const { frappe_request } = require("../utils"); const log = console.log; const WEBSITE_ROOM = "website"; @@ -114,11 +113,9 @@ function notify_disconnected_documents(socket) { function can_subscribe_doctype(args) { if (!args) return; if (!args.doctype) return; - request - .get(get_url(args.socket, "/api/method/frappe.realtime.can_subscribe_doctype")) + frappe_request("/api/method/frappe.realtime.can_subscribe_doctype", args.socket) .type("form") .query({ - sid: args.socket.sid, doctype: args.doctype, }) .end(function (err, res) { @@ -166,11 +163,9 @@ function notify_subscribed_doc_users(args) { function can_subscribe_doc(args) { if (!args) return; if (!args.doctype || !args.docname) return; - request - .get(get_url(args.socket, "/api/method/frappe.realtime.can_subscribe_doc")) + frappe_request("/api/method/frappe.realtime.can_subscribe_doc", args.socket) .type("form") .query({ - sid: args.socket.sid, doctype: args.doctype, docname: args.docname, }) diff --git a/realtime/middlewares/authenticate.js b/realtime/middlewares/authenticate.js index 6d81cd2e15f..127939a26b5 100644 --- a/realtime/middlewares/authenticate.js +++ b/realtime/middlewares/authenticate.js @@ -23,23 +23,28 @@ function authenticate_with_frappe(socket, next) { return; } - let cookies = cookie.parse(socket.request.headers.cookie); + let cookies = cookie.parse(socket.request.headers.cookie || ""); + let authorization_header = socket.request.headers.authorization; - if (!cookies.sid) { - next(new Error("No sid transmitted.")); + if (!cookies.sid && !authorization_header) { + next(new Error("No authentication method used. Use cookie or authorization header.")); return; } - request - .get(get_url(socket, "/api/method/frappe.realtime.get_user_info")) + let auth_req = request.get(get_url(socket, "/api/method/frappe.realtime.get_user_info")); + if (cookies.sid) { + auth_req = auth_req.query({ sid: cookies.sid }); + } else { + auth_req = auth_req.set("Authorization", authorization_header); + } + + auth_req .type("form") - .query({ - sid: cookies.sid, - }) .then((res) => { socket.user = res.body.message.user; socket.user_type = res.body.message.user_type; socket.sid = cookies.sid; + socket.authorization_header = authorization_header; next(); }) .catch((e) => { diff --git a/realtime/utils.js b/realtime/utils.js index 0ff3be0a494..b821f68533f 100644 --- a/realtime/utils.js +++ b/realtime/utils.js @@ -1,3 +1,5 @@ +const request = require("superagent"); + function get_url(socket, path) { if (!path) { path = ""; @@ -5,6 +7,17 @@ function get_url(socket, path) { return socket.request.headers.origin + path; } +// Authenticates a partial request created using superagent +function frappe_request(path, socket) { + const partial_req = request.get(get_url(socket, path)); + if (socket.sid) { + return partial_req.query({ sid: socket.sid }); + } else if (socket.authorization_header) { + return partial_req.set("Authorization", socket.authorization_header); + } +} + module.exports = { get_url, + frappe_request, }; From 00d3dac6211e07c0be09c0ca137b7406d4f6aab4 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 20 Feb 2024 15:11:35 +0000 Subject: [PATCH 03/48] refactor!: Drop user based workflow action compatibility (backport #24956) (#24970) * test: run workflow actions in tests * test: fix workflow action tests * test: cleanup test cleanups * fix: restore form dict after printing * test: patch printing during workflow tests --------- Co-authored-by: Ankush Menat --- frappe/__init__.py | 4 ++++ .../workflow/doctype/workflow/test_workflow.py | 16 +++++++++------- .../doctype/workflow_action/workflow_action.py | 3 ++- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index 9caf432b312..70ccb4a7a11 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -10,6 +10,7 @@ Read the documentation: https://frappeframework.com/docs """ +import copy import functools import gc import importlib @@ -2076,6 +2077,8 @@ def get_print( from frappe.utils.pdf import get_pdf from frappe.website.serve import get_response_content + original_form_dict = copy.deepcopy(local.form_dict) + local.form_dict.doctype = doctype local.form_dict.name = name local.form_dict.format = print_format @@ -2089,6 +2092,7 @@ def get_print( pdf_options["password"] = password html = get_response_content("printview") + local.form_dict = original_form_dict return get_pdf(html, options=pdf_options, output=output) if as_pdf else html diff --git a/frappe/workflow/doctype/workflow/test_workflow.py b/frappe/workflow/doctype/workflow/test_workflow.py index 271f611d830..950b9a16ca0 100644 --- a/frappe/workflow/doctype/workflow/test_workflow.py +++ b/frappe/workflow/doctype/workflow/test_workflow.py @@ -1,5 +1,7 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: MIT. See LICENSE +from unittest.mock import patch + import frappe from frappe.model.workflow import ( WorkflowTransitionError, @@ -19,10 +21,14 @@ def setUpClass(cls): make_test_records("User") def setUp(self): + self.patcher = patch("frappe.attach_print", return_value={}) + self.patcher.start() + frappe.db.delete("Workflow Action") self.workflow = create_todo_workflow() - frappe.set_user("Administrator") def tearDown(self): + frappe.set_user("Administrator") + self.patcher.stop() frappe.delete_doc("Workflow", "Test ToDo") def test_default_condition(self): @@ -82,7 +88,6 @@ def test_get_common_transition_actions(self): self.assertListEqual(actions, ["Review"]) def test_if_workflow_actions_were_processed_using_role(self): - frappe.db.delete("Workflow Action") user = frappe.get_doc("User", "test2@example.com") user.add_roles("Test Approver", "System Manager") frappe.set_user("test2@example.com") @@ -94,14 +99,11 @@ def test_if_workflow_actions_were_processed_using_role(self): # test if status of workflow actions are updated on approval self.test_approve(doc) user.remove_roles("Test Approver", "System Manager") - workflow_actions = frappe.get_all("Workflow Action", fields=["status"]) + workflow_actions = frappe.get_all("Workflow Action", fields=["*"]) self.assertEqual(len(workflow_actions), 1) self.assertEqual(workflow_actions[0].status, "Completed") - frappe.set_user("Administrator") def test_if_workflow_actions_were_processed_using_user(self): - frappe.db.delete("Workflow Action") - user = frappe.get_doc("User", "test2@example.com") user.add_roles("Test Approver", "System Manager") frappe.set_user("test2@example.com") @@ -166,7 +168,7 @@ def create_todo_workflow(): workflow.document_type = "ToDo" workflow.workflow_state_field = "workflow_state" workflow.is_active = 1 - workflow.send_email_alert = 0 + workflow.send_email_alert = 1 workflow.append("states", dict(state="Pending", allow_edit="All")) workflow.append( "states", diff --git a/frappe/workflow/doctype/workflow_action/workflow_action.py b/frappe/workflow/doctype/workflow_action/workflow_action.py index c495029a3b9..283296761c7 100644 --- a/frappe/workflow/doctype/workflow_action/workflow_action.py +++ b/frappe/workflow/doctype/workflow_action/workflow_action.py @@ -117,6 +117,7 @@ def process_workflow_actions(doc, state): doc=doc, transitions=next_possible_transitions, enqueue_after_commit=True, + now=frappe.flags.in_test, ) @@ -379,7 +380,7 @@ def send_workflow_action_email(doc, transitions): users_data = get_users_next_action_data(transitions, doc) common_args = get_common_email_args(doc) message = common_args.pop("message", None) - for user, data in users_data.items(): # noqa: B007 + for data in users_data.values(): email_args = { "recipients": [data.get("email")], "args": {"actions": list(deduplicate_actions(data.get("possible_actions"))), "message": message}, From 718973e90a4b8bd65693a80d764311e66f6c9d7c Mon Sep 17 00:00:00 2001 From: Maharshi Patel Date: Wed, 21 Feb 2024 11:32:28 +0530 Subject: [PATCH 04/48] fix: added important to bold class. it is kind of unintuitive that we are adding semi-bold weight ( 600 ) to bold class :D it also make sense to have !important to the bold class. (cherry picked from commit 18749838b6616764f3321a13862930a4898bb1bf) --- frappe/public/scss/desk/global.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/scss/desk/global.scss b/frappe/public/scss/desk/global.scss index 98bc9e7110e..298f3724cfe 100644 --- a/frappe/public/scss/desk/global.scss +++ b/frappe/public/scss/desk/global.scss @@ -340,7 +340,7 @@ select.input-xs { } .bold { - font-weight: 600; + font-weight: var(--weight-semibold) !important; } .text-color { From e778fe9f044d4c2be9cd1674d0aadef71577b636 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 21 Feb 2024 19:04:36 +0530 Subject: [PATCH 05/48] fix: allow negative numbers in grid search (#24989) (#24991) (cherry picked from commit 2dffcc08abf9639588168bf933c92eb7786f54f9) Co-authored-by: Ankush Menat --- frappe/public/js/frappe/utils/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js index d3ff11d9856..c075cf3de53 100644 --- a/frappe/public/js/frappe/utils/utils.js +++ b/frappe/public/js/frappe/utils/utils.js @@ -1580,7 +1580,7 @@ Object.assign(frappe.utils, { only_allow_num_decimal(input) { input.on("input", (e) => { let self = $(e.target); - self.val(self.val().replace(/[^0-9.]/g, "")); + self.val(self.val().replace(/[^0-9.\-]/g, "")); if ( (e.which != 46 || self.val().indexOf(".") != -1) && (e.which < 48 || e.which > 57) From de3042bfa3c2065cabc04fb2aebd1e72934345e2 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 21 Feb 2024 13:46:06 +0000 Subject: [PATCH 06/48] fix: Cast to string to handle int PK (#24988) (#24993) (cherry picked from commit 99bb5d030390a3e347c01eef516fdcd20b5191c9) Co-authored-by: Ankush Menat --- frappe/permissions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/permissions.py b/frappe/permissions.py index a37ade05af0..944909ccc3a 100644 --- a/frappe/permissions.py +++ b/frappe/permissions.py @@ -350,7 +350,7 @@ def has_user_permission(doc, user=None, debug=False): # if allowed_docs is empty it states that there is no applicable permission under the current doctype # only check if allowed_docs is not empty - if allowed_docs and docname not in allowed_docs: + if allowed_docs and str(docname) not in allowed_docs: # no user permissions for this doc specified debug and _debug_log( "User doesn't have access to this document because of User Permissions, allowed documents: " From 3f956c1533eef769fe8c1ffebe4f05c2890dbf6d Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 21 Feb 2024 13:57:41 +0000 Subject: [PATCH 07/48] fix: Correct type hint (#24990) (#24995) closes https://github.com/frappe/frappe/issues/24987 (cherry picked from commit dd50cc0cf16343bd069197cb2d5aad70f425bae3) Co-authored-by: Ankush Menat --- frappe/types/DF.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/types/DF.py b/frappe/types/DF.py index 2050d081193..13a762a9e26 100644 --- a/frappe/types/DF.py +++ b/frappe/types/DF.py @@ -18,7 +18,7 @@ Datetime = str | datetime Duration = int DynamicLink = Data -Float = str +Float = float HTMLEditor = Text Int = int JSON = Text From 76a32f2a577ba1649a63b3650e8c2ad819eef873 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 21 Feb 2024 20:04:17 +0530 Subject: [PATCH 08/48] =?UTF-8?q?fix:=20disable=20internal=20columns=20lik?= =?UTF-8?q?e=20=5Fcomments=20from=20report=20column=20selec=E2=80=A6=20(#2?= =?UTF-8?q?4998)=20(#25000)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: disable internal columns like _comments from report column selection * fix: only exclude comments from reportview (cherry picked from commit ab485b2f4f08cd696e6b311d8c9f1a5c876e0d4c) Co-authored-by: Jannat Patel <31363128+pateljannat@users.noreply.github.com> --- frappe/public/js/frappe/views/reports/report_view.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/views/reports/report_view.js b/frappe/public/js/frappe/views/reports/report_view.js index f7bf92fe3b2..34e565ddd9b 100644 --- a/frappe/public/js/frappe/views/reports/report_view.js +++ b/frappe/public/js/frappe/views/reports/report_view.js @@ -897,7 +897,9 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView { .filter(standard_fields_filter); // filter out docstatus field from picker - let std_fields = frappe.model.std_fields.filter((df) => df.fieldname !== "docstatus"); + let std_fields = frappe.model.std_fields.filter( + (df) => !["docstatus", "_comments"].includes(df.fieldname) + ); // add status field derived from docstatus, if status is not a standard field let has_status_values = false; From 34bca99dc83275a3bc48889d76fe24b9ebbe68bf Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 21 Feb 2024 15:01:20 +0000 Subject: [PATCH 09/48] fix: add show_dashboard field on custom fields (#24984) (#24986) closes https://github.com/frappe/frappe/issues/23637 (cherry picked from commit dab06f33b166b7d133630eeade785fe73179c1d9) # Conflicts: # frappe/custom/doctype/custom_field/custom_field.json Co-authored-by: Ankush Menat --- frappe/custom/doctype/custom_field/custom_field.json | 11 +++++++++-- frappe/custom/doctype/custom_field/custom_field.py | 1 + 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/frappe/custom/doctype/custom_field/custom_field.json b/frappe/custom/doctype/custom_field/custom_field.json index 1650e64f60b..fc802b79850 100644 --- a/frappe/custom/doctype/custom_field/custom_field.json +++ b/frappe/custom/doctype/custom_field/custom_field.json @@ -1,7 +1,7 @@ { "actions": [], "allow_import": 1, - "creation": "2013-01-10 16:34:01", + "creation": "2024-02-21 18:14:42.281748", "description": "Adds a custom field to a DocType", "doctype": "DocType", "document_type": "Setup", @@ -56,6 +56,7 @@ "ignore_xss_filter", "translatable", "hide_border", + "show_dashboard", "description", "permlevel", "width", @@ -444,13 +445,19 @@ "fieldname": "sort_options", "fieldtype": "Check", "label": "Sort Options" + }, + { + "default": "0", + "fieldname": "show_dashboard", + "fieldtype": "Check", + "label": "Show Dashboard" } ], "icon": "fa fa-glass", "idx": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2023-12-08 15:52:37.525003", + "modified": "2024-02-21 18:15:19.384933", "modified_by": "Administrator", "module": "Custom", "name": "Custom Field", diff --git a/frappe/custom/doctype/custom_field/custom_field.py b/frappe/custom/doctype/custom_field/custom_field.py index 25c73920da7..d0f27ca66e5 100644 --- a/frappe/custom/doctype/custom_field/custom_field.py +++ b/frappe/custom/doctype/custom_field/custom_field.py @@ -109,6 +109,7 @@ class CustomField(Document): report_hide: DF.Check reqd: DF.Check search_index: DF.Check + show_dashboard: DF.Check sort_options: DF.Check translatable: DF.Check unique: DF.Check From 594c2cae8ac7332fe36e6aa6710035684f891f34 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 22 Feb 2024 10:46:02 +0530 Subject: [PATCH 10/48] ci: fix ocassional patch test failure (#25001) (#25006) Some files in /env are touched during deletion which causes test to fail. Hunch: running processes still not killed OR background jobs. (cherry picked from commit 4f18daba1768efab42c55aa67dff4e85f65ea125) Co-authored-by: Ankush Menat --- .github/workflows/patch-mariadb-tests.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/patch-mariadb-tests.yml b/.github/workflows/patch-mariadb-tests.yml index 4875e6f5df1..57ed22ebfd7 100644 --- a/.github/workflows/patch-mariadb-tests.yml +++ b/.github/workflows/patch-mariadb-tests.yml @@ -110,6 +110,7 @@ jobs: - name: Run Patch Tests run: | cd ~/frappe-bench/ + sed -i 's/^worker:/# worker:/g' Procfile wget https://frappeframework.com/files/v13-frappe.sql.gz bench --site test_site --force restore ~/frappe-bench/v13-frappe.sql.gz @@ -126,6 +127,7 @@ jobs: git checkout -q -f $branch_name pgrep honcho | xargs kill + sleep 3 rm -rf ~/frappe-bench/env bench -v setup env bench start &>> ~/frappe-bench/bench_start.log & @@ -137,6 +139,7 @@ jobs: echo "Updating to last commit" pgrep honcho | xargs kill + sleep 3 rm -rf ~/frappe-bench/env git checkout -q -f "$GITHUB_SHA" bench -v setup env From dfcee61125ff0adceecd64223012bfaf6b824434 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 22 Feb 2024 13:51:34 +0530 Subject: [PATCH 11/48] fix: Guess currency from report row if available (#25009) (#25010) * fix: Guess currency from report row if available In number card using report the curency can be defined on a row in report, but since we don't pass report data currency fields get formatted with default currency of global company. * perf: use reducing common_doc for checking Overtime this will be smaller than all rows and if everything is exhausted then no work left to do. (cherry picked from commit 14bfcb628d8c080f6c6171ebd6946f1b848c742f) Co-authored-by: Ankush Menat --- .../js/frappe/widgets/number_card_widget.js | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/frappe/public/js/frappe/widgets/number_card_widget.js b/frappe/public/js/frappe/widgets/number_card_widget.js index e4297aa9551..21a358b08d7 100644 --- a/frappe/public/js/frappe/widgets/number_card_widget.js +++ b/frappe/public/js/frappe/widgets/number_card_widget.js @@ -213,21 +213,36 @@ export default class NumberCardWidget extends Widget { }, []); const col = res.columns.find((col) => col.fieldname == field); this.number = frappe.report_utils.get_result_of_fn(this.card_doc.report_function, vals); - this.set_formatted_number(col); + this.set_formatted_number(col, this._generate_common_doc(res.result)); } - set_formatted_number(df) { + set_formatted_number(df, doc) { const default_country = frappe.sys_defaults.country; const shortened_number = frappe.utils.shorten_number(this.number, default_country, 5); let number_parts = shortened_number.split(" "); const symbol = number_parts[1] || ""; number_parts[0] = window.convert_old_to_new_number_format(number_parts[0]); - const formatted_number = $(frappe.format(number_parts[0], df)).text(); + const formatted_number = $(frappe.format(number_parts[0], df, null, doc)).text(); this.formatted_number = formatted_number + " " + __(symbol); } + _generate_common_doc(rows) { + if (!rows || !rows.length) return {}; + // init with first doc, for each other doc if values are common then keep else discard + // Whatever is left should be same in all objects + const common_doc = Object.assign({}, rows[0]); + rows.forEach((row) => { + for (const [key, value] of Object.entries(common_doc)) { + if (value !== row[key]) { + delete common_doc[key]; + } + } + }); + return common_doc; + } + render_number() { const style_attr = this.card_doc.color ? `style="color: ${this.card_doc.color};"` : ""; From 4882bfc9194545a3716c9a84fcc361caa90379f4 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 22 Feb 2024 14:05:53 +0530 Subject: [PATCH 12/48] fix: handle total rows in number card (#25011) (#25012) (cherry picked from commit 00977e0c5ed508972b4e8e94805bd209ae375c77) Co-authored-by: Ankush Menat --- frappe/public/js/frappe/widgets/number_card_widget.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frappe/public/js/frappe/widgets/number_card_widget.js b/frappe/public/js/frappe/widgets/number_card_widget.js index 21a358b08d7..2d67814e01f 100644 --- a/frappe/public/js/frappe/widgets/number_card_widget.js +++ b/frappe/public/js/frappe/widgets/number_card_widget.js @@ -234,6 +234,8 @@ export default class NumberCardWidget extends Widget { // Whatever is left should be same in all objects const common_doc = Object.assign({}, rows[0]); rows.forEach((row) => { + if (Array.isArray(row)) return; // totals row + for (const [key, value] of Object.entries(common_doc)) { if (value !== row[key]) { delete common_doc[key]; From 3211ccf1da2859cd2f198b21375b674da173a7fb Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 22 Feb 2024 15:23:16 +0530 Subject: [PATCH 13/48] fix: Private images in PDFs from background jobs (#24980) (#25015) * refactor: Private images rendering in PDFs Private images currently render fine if PDF is generated during a request as we pass the cookiejar to WKHTML. Background jobs however fail completeley because they can't retrieve private images without cookiejar. This PR converts all image types to base64 encoded sources in HTML itself, so wkhtmltopdf doesn't have to a fire a request. * test: private images in pdf (cherry picked from commit 718b5b8bee5f0b304d7b7956113ce19e1555886c) Co-authored-by: Ankush Menat --- frappe/core/doctype/file/test_file.py | 9 ++++++-- frappe/core/doctype/file/utils.py | 16 +++++++++++++ frappe/tests/test_pdf.py | 14 ++++++++++++ frappe/utils/pdf.py | 33 +++++++++++++++++++++++++++ frappe/utils/response.py | 18 +++------------ 5 files changed, 73 insertions(+), 17 deletions(-) diff --git a/frappe/core/doctype/file/test_file.py b/frappe/core/doctype/file/test_file.py index 682a09011e0..e0ff4813c9b 100644 --- a/frappe/core/doctype/file/test_file.py +++ b/frappe/core/doctype/file/test_file.py @@ -39,13 +39,18 @@ def make_test_doc(ignore_permissions=False): @contextmanager -def make_test_image_file(): +def make_test_image_file(private=False): file_path = frappe.get_app_path("frappe", "tests/data/sample_image_for_optimization.jpg") with open(file_path, "rb") as f: file_content = f.read() test_file = frappe.get_doc( - {"doctype": "File", "file_name": "sample_image_for_optimization.jpg", "content": file_content} + { + "doctype": "File", + "file_name": "sample_image_for_optimization.jpg", + "content": file_content, + "is_private": private, + } ).insert() # remove those flags _test_file: "File" = frappe.get_doc("File", test_file.name) diff --git a/frappe/core/doctype/file/utils.py b/frappe/core/doctype/file/utils.py index cca3f8586b4..ee9fd805f02 100644 --- a/frappe/core/doctype/file/utils.py +++ b/frappe/core/doctype/file/utils.py @@ -411,3 +411,19 @@ def decode_file_content(content: bytes) -> bytes: if b"," in content: content = content.split(b",")[1] return safe_b64decode(content) + + +def find_file_by_url(path: str, name: str = None) -> Optional["File"]: + filters = {"file_url": str(path)} + if name: + filters["name"] = str(name) + + files = frappe.get_all("File", filters=filters, fields="*") + + # this file might be attached to multiple documents + # if the file is accessible from any one of those documents + # then it should be downloadable + for file_data in files: + file: "File" = frappe.get_doc(doctype="File", **file_data) + if file.is_downloadable(): + return file diff --git a/frappe/tests/test_pdf.py b/frappe/tests/test_pdf.py index 84004dc1f13..fb68cdffe40 100644 --- a/frappe/tests/test_pdf.py +++ b/frappe/tests/test_pdf.py @@ -6,6 +6,7 @@ import frappe import frappe.utils.pdf as pdfgen +from frappe.core.doctype.file.test_file import make_test_image_file from frappe.tests.utils import FrappeTestCase @@ -50,3 +51,16 @@ def test_pdf_generation_as_a_user(self): frappe.set_user("Administrator") pdf = pdfgen.get_pdf(self.html) self.assertTrue(pdf) + + def test_private_images_in_pdf(self): + with make_test_image_file(private=True) as file: + html = f"""
+ + +
+ """ + + pdf = pdfgen.get_pdf(html) + + # If image was actually retrieved then size will be in few kbs, else bytes. + self.assertGreaterEqual(len(pdf), 10_000) diff --git a/frappe/utils/pdf.py b/frappe/utils/pdf.py index c6450de84a1..e09184dc1e1 100644 --- a/frappe/utils/pdf.py +++ b/frappe/utils/pdf.py @@ -1,10 +1,13 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: MIT. See LICENSE +import base64 import contextlib import io +import mimetypes import os import re import subprocess +from urllib.parse import parse_qs, urlparse import pdfkit from bs4 import BeautifulSoup @@ -13,6 +16,7 @@ import frappe from frappe import _ +from frappe.core.doctype.file.utils import find_file_by_url from frappe.utils import scrub_urls from frappe.utils.jinja_globals import bundled_asset, is_rtl @@ -157,6 +161,7 @@ def prepare_options(html, options): # cookies options.update(get_cookie_options()) + html = inline_private_images(html) # page size pdf_page_size = ( @@ -224,6 +229,34 @@ def read_options_from_html(html): return str(soup), options +def inline_private_images(html) -> str: + soup = BeautifulSoup(html, "html.parser") + for img in soup.find_all("img"): + if b64 := _get_base64_image(img["src"]): + img["src"] = b64 + return str(soup) + + +def _get_base64_image(src): + """Return base64 version of image if user has permission to view it""" + try: + parsed_url = urlparse(src) + path = parsed_url.path + query = parse_qs(parsed_url.query) + mime_type = mimetypes.guess_type(path)[0] + if not mime_type.startswith("image/"): + return + filename = query.get("fid") and query["fid"][0] or None + file = find_file_by_url(path, name=filename) + if not file or not file.is_private: + return + + b64_encoded_image = base64.b64encode(file.get_content()).decode() + return f"data:{mime_type};base64,{b64_encoded_image}" + except Exception: + frappe.logger("pdf").error("Failed to convert inline images to base64", exc_info=True) + + def prepare_header_footer(soup: BeautifulSoup): options = {} diff --git a/frappe/utils/response.py b/frappe/utils/response.py index 3b5463979c9..ffe3d2e5853 100644 --- a/frappe/utils/response.py +++ b/frappe/utils/response.py @@ -260,25 +260,13 @@ def download_backup(path): def download_private_file(path: str) -> Response: """Checks permissions and sends back private file""" + from frappe.core.doctype.file.utils import find_file_by_url if frappe.session.user == "Guest": raise Forbidden(_("You don't have permission to access this file")) - filters = {"file_url": path} - if frappe.form_dict.fid: - filters["name"] = str(frappe.form_dict.fid) - - files = frappe.get_all("File", filters=filters, fields="*") - - # this file might be attached to multiple documents - # if the file is accessible from any one of those documents - # then it should be downloadable - for file_data in files: - file: "File" = frappe.get_doc(doctype="File", **file_data) - if file.is_downloadable(): - break - - else: + file = find_file_by_url(path, name=frappe.form_dict.fid) + if not file: raise Forbidden(_("You don't have permission to access this file")) make_access_log(doctype="File", document=file.name, file_type=os.path.splitext(path)[-1][1:]) From c092efa607b2d71a901a8917cb6058920c8d998d Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 22 Feb 2024 10:00:51 +0000 Subject: [PATCH 14/48] fix: support running QB union queries (backport #24757) (#25016) * fix: support running QB union queries closes https://github.com/frappe/frappe/issues/15609 (cherry picked from commit f8743d1cfb0e018dad6a15c346c17be45d493bf7) * fix: Pin pypika We've multiple monkey patched changes, we can't rely on loose pinning. (cherry picked from commit 7504ccaca8a683a95253510d0576ddfb947ef9ea) --------- Co-authored-by: Ankush Menat --- frappe/query_builder/utils.py | 6 +++++- frappe/tests/test_query_builder.py | 8 ++++++++ pyproject.toml | 2 +- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/frappe/query_builder/utils.py b/frappe/query_builder/utils.py index 67421e95f45..17f543f3f4c 100644 --- a/frappe/query_builder/utils.py +++ b/frappe/query_builder/utils.py @@ -3,7 +3,7 @@ from importlib import import_module from typing import Any, get_type_hints -from pypika.queries import Column, QueryBuilder +from pypika.queries import Column, QueryBuilder, _SetOperation from pypika.terms import PseudoColumn import frappe @@ -137,6 +137,10 @@ def prepare_query(query): builder_class.run = execute_query builder_class.walk = prepare_query + + # To support running union queries + _SetOperation.run = execute_query + _SetOperation.walk = prepare_query frappe._qb_patched[frappe.conf.db_type] = True diff --git a/frappe/tests/test_query_builder.py b/frappe/tests/test_query_builder.py index c9556c1eed3..c6784cd50dd 100644 --- a/frappe/tests/test_query_builder.py +++ b/frappe/tests/test_query_builder.py @@ -479,3 +479,11 @@ def test_util_table(self): DocType = Table("DocType") self.assertEqual(DocType.get_sql(), "DocType") + + def test_union(self): + user = frappe.qb.DocType("User") + role = frappe.qb.DocType("Role") + users = frappe.qb.from_(user).select(user.name) + roles = frappe.qb.from_(role).select(role.name) + + self.assertEqual(set(users.run() + roles.run()), set((users + roles).run())) diff --git a/pyproject.toml b/pyproject.toml index cc7a27ed057..5d041820b81 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ dependencies = [ # do NOT add loose requirements on PyMySQL versions. "PyMySQL==1.1.0", "pypdf~=3.17.0", - "PyPika~=0.48.9", + "PyPika==0.48.9", "PyQRCode~=1.2.1", "PyYAML~=6.0.1", "RestrictedPython~=6.2", From c517286d33e04553e086c6681c3e0d99e6cbfe17 Mon Sep 17 00:00:00 2001 From: Akhil Narang Date: Mon, 5 Feb 2024 18:32:12 +0530 Subject: [PATCH 15/48] ci: switch to ruff for linting Signed-off-by: Akhil Narang (cherry picked from commit fbb0e4479107e15639941acaafc2a5f2b984a01b) --- .flake8 | 75 ----------------------------------------- .pre-commit-config.yaml | 16 ++------- pyproject.toml | 42 +++++++++++++++++++---- 3 files changed, 37 insertions(+), 96 deletions(-) delete mode 100644 .flake8 diff --git a/.flake8 b/.flake8 deleted file mode 100644 index e783fbbeb39..00000000000 --- a/.flake8 +++ /dev/null @@ -1,75 +0,0 @@ -[flake8] -ignore = - B001, - B007, - B009, - B010, - B950, - E101, - E111, - E114, - E116, - E117, - E121, - E122, - E123, - E124, - E125, - E126, - E127, - E128, - E131, - E201, - E202, - E203, - E211, - E221, - E222, - E223, - E224, - E225, - E226, - E228, - E231, - E241, - E242, - E251, - E261, - E262, - E265, - E266, - E271, - E272, - E273, - E274, - E301, - E302, - E303, - E305, - E306, - E402, - E501, - E502, - E701, - E702, - E703, - E741, - F401, - F403, - F405, - W191, - W291, - W292, - W293, - W391, - W503, - W504, - E711, - E129, - F841, - E713, - E712, - B028, - -max-line-length = 200 -exclude=,test_*.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1e9e2adb900..bc20e86ebaa 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -20,18 +20,12 @@ repos: - id: check-yaml - id: debug-statements - - repo: https://github.com/asottile/pyupgrade - rev: v3.9.0 - hooks: - - id: pyupgrade - args: ['--py310-plus'] - - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.2.0 hooks: - id: ruff - name: "Sort Python imports" - args: ["--select", "I", "--fix"] + name: "Run ruff linter and apply fixes" + args: ["--fix"] - id: ruff-format name: "Format Python code" @@ -72,12 +66,6 @@ repos: frappe/public/js/lib/.* )$ - - repo: https://github.com/PyCQA/flake8 - rev: 6.0.0 - hooks: - - id: flake8 - additional_dependencies: ['flake8-bugbear',] - ci: autoupdate_schedule: weekly skip: [] diff --git a/pyproject.toml b/pyproject.toml index 5d041820b81..ab8bbbd26ac 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -89,13 +89,6 @@ dependencies = [ requires = ["flit_core >=3.4,<4"] build-backend = "flit_core.buildapi" -[tool.ruff] -line-length = 110 - -[tool.ruff.format] -indent-style = "tab" -docstring-code-format = true - [tool.bench.dev-dependencies] coverage = "~=6.5.0" Faker = "~=18.10.1" @@ -105,3 +98,38 @@ watchdog = "~=3.0.0" hypothesis = "~=6.77.0" responses = "==0.23.1" freezegun = "~=1.2.2" + +[tool.ruff] +line-length = 110 +target-version = "py310" + +[tool.ruff.lint] +select = [ + "F", + "E", + "W", + "I", + "UP", + "B", +] +ignore = [ + "B017", # assertRaises(Exception) - should be more specific + "B018", # useless expression, not assigned to anything + "B023", # function doesn't bind loop variable - will have last iteration's value + "B904", # raise inside except without from + "E101", # indentation contains mixed spaces and tabs + "E402", # module level import not at top of file + "E501", # line too long + "E741", # ambiguous variable name + "F401", # "unused" imports + "F403", # can't detect undefined names from * import + "F405", # can't detect undefined names from * import + "F722", # syntax error in forward type annotation + "F821", # undefined name + "W191", # indentation contains tabs +] + +[tool.ruff.format] +quote-style = "double" +indent-style = "tab" +docstring-code-format = true From c557ec6d98b62c586c38d43312045898f30b544d Mon Sep 17 00:00:00 2001 From: Akhil Narang Date: Mon, 5 Feb 2024 20:05:19 +0530 Subject: [PATCH 16/48] fix: ruff fixes (cherry picked from commit 26ae0f3460f29116e0c083d57eee9f33763237ea) Signed-off-by: Akhil Narang --- .github/helper/documentation.py | 15 +- .github/helper/roulette.py | 8 +- frappe/__init__.py | 24 +-- frappe/app.py | 2 +- frappe/auth.py | 6 +- .../assignment_rule/test_assignment_rule.py | 2 +- frappe/build.py | 3 - frappe/cache_manager.py | 5 +- frappe/commands/site.py | 6 +- frappe/commands/translate.py | 4 +- frappe/commands/utils.py | 4 +- frappe/config/__init__.py | 2 +- frappe/contacts/doctype/address/address.py | 9 +- frappe/contacts/doctype/contact/contact.py | 8 +- .../addresses_and_contacts.py | 3 +- .../core/doctype/activity_log/activity_log.py | 2 +- frappe/core/doctype/communication/email.py | 2 +- .../core/doctype/data_import/data_import.py | 4 +- frappe/core/doctype/data_import/exporter.py | 14 +- frappe/core/doctype/data_import/importer.py | 13 +- frappe/core/doctype/doctype/doctype.py | 18 +-- .../document_naming_settings.py | 4 +- .../domain_settings/domain_settings.py | 2 +- .../core/doctype/dynamic_link/dynamic_link.py | 2 +- .../core/doctype/error_log/test_error_log.py | 16 +- frappe/core/doctype/file/file.py | 4 +- frappe/core/doctype/file/utils.py | 6 +- frappe/core/doctype/module_def/module_def.py | 2 +- frappe/core/doctype/page/page.py | 11 +- frappe/core/doctype/report/report.py | 4 +- frappe/core/doctype/report/test_report.py | 102 +++++++++---- .../scheduled_job_type/scheduled_job_type.py | 4 +- .../doctype/server_script/server_script.py | 4 +- .../server_script/server_script_utils.py | 2 +- .../server_script/test_server_script.py | 4 +- .../system_settings/system_settings.py | 2 +- frappe/core/doctype/user/user.py | 13 +- .../permitted_documents_for_user.py | 6 +- frappe/coverage.py | 3 +- .../doctype/customize_form/customize_form.py | 6 +- .../audit_system_hooks/audit_system_hooks.py | 2 +- frappe/database/database.py | 22 +-- frappe/database/mariadb/database.py | 32 ++-- frappe/database/mariadb/setup_db.py | 5 +- frappe/database/postgres/database.py | 31 ++-- frappe/database/postgres/schema.py | 10 +- frappe/database/postgres/setup_db.py | 4 +- frappe/database/query.py | 24 +-- frappe/database/schema.py | 8 +- frappe/defaults.py | 10 +- frappe/deferred_insert.py | 2 +- frappe/desk/desktop.py | 12 +- frappe/desk/doctype/dashboard/dashboard.py | 4 +- .../dashboard_chart/dashboard_chart.py | 14 +- .../dashboard_settings/dashboard_settings.py | 2 +- .../desk/doctype/desktop_icon/desktop_icon.py | 2 +- frappe/desk/doctype/event/event.py | 4 +- .../desk/doctype/kanban_board/kanban_board.py | 4 +- .../list_view_settings/list_view_settings.py | 2 +- .../desk/doctype/number_card/number_card.py | 10 +- frappe/desk/doctype/tag/tag.py | 2 +- frappe/desk/form/linked_with.py | 20 +-- frappe/desk/form/load.py | 14 +- frappe/desk/form/utils.py | 4 +- frappe/desk/leaderboard.py | 4 +- frappe/desk/notifications.py | 2 +- frappe/desk/query_report.py | 14 +- frappe/desk/reportview.py | 24 +-- frappe/desk/search.py | 2 +- frappe/email/__init__.py | 2 +- .../auto_email_report/auto_email_report.py | 2 +- .../doctype/email_account/email_account.py | 88 ++++++++--- .../email_account/test_email_account.py | 12 +- .../email/doctype/email_group/email_group.py | 6 +- .../email/doctype/email_queue/email_queue.py | 2 +- frappe/email/receive.py | 2 +- frappe/frappeclient.py | 2 +- frappe/gettext/extractors/navbar.py | 51 +++++++ frappe/handler.py | 2 +- frappe/installer.py | 6 +- .../dropbox_settings/dropbox_settings.py | 2 +- .../doctype/ldap_settings/ldap_settings.py | 4 +- .../ldap_settings/test_ldap_settings.py | 15 +- .../social_login_key/test_social_login_key.py | 2 +- .../frappe_providers/frappecloud.py | 6 +- frappe/integrations/google_oauth.py | 4 +- frappe/integrations/offsite_backup_utils.py | 8 +- frappe/integrations/utils.py | 2 +- frappe/model/base_document.py | 10 +- frappe/model/db_query.py | 17 +-- frappe/model/delete_doc.py | 12 +- frappe/model/document.py | 8 +- frappe/model/dynamic_links.py | 2 +- frappe/model/meta.py | 12 +- frappe/model/naming.py | 10 +- frappe/model/rename_doc.py | 6 +- frappe/model/utils/__init__.py | 8 +- frappe/model/utils/rename_field.py | 10 +- frappe/model/workflow.py | 2 +- frappe/modules/import_file.py | 10 +- frappe/modules/utils.py | 4 +- frappe/parallel_test_runner.py | 2 +- .../delete_duplicate_user_permissions.py | 4 +- .../v11_0/update_list_user_settings.py | 4 +- .../patches/v12_0/delete_duplicate_indexes.py | 2 +- .../move_email_and_phone_to_child_table.py | 1 - .../v12_0/replace_null_values_in_tables.py | 2 +- .../update_date_filters_in_user_settings.py | 6 +- frappe/patches/v14_0/drop_unused_indexes.py | 2 +- frappe/permissions.py | 4 +- frappe/query_builder/builder.py | 5 +- frappe/rate_limiter.py | 2 +- frappe/realtime.py | 14 +- frappe/recorder.py | 2 +- frappe/search/website_search.py | 2 +- .../energy_point_log/energy_point_log.py | 4 +- .../energy_point_rule/energy_point_rule.py | 2 +- frappe/test_runner.py | 4 +- frappe/tests/test_api.py | 13 +- frappe/tests/test_api_v2.py | 3 +- frappe/tests/test_caching.py | 6 +- frappe/tests/test_commands.py | 21 +-- frappe/tests/test_db.py | 47 +++--- frappe/tests/test_db_query.py | 141 ++++++++++++++---- frappe/tests/test_form_load.py | 4 +- frappe/tests/test_naming.py | 2 +- frappe/tests/test_oauth20.py | 2 +- frappe/tests/test_patches.py | 4 +- frappe/tests/test_perf.py | 2 +- frappe/tests/test_query_report.py | 5 +- frappe/tests/test_recorder.py | 4 +- frappe/tests/test_rename_doc.py | 2 +- frappe/tests/test_safe_exec.py | 2 +- frappe/tests/test_translate.py | 11 +- frappe/tests/test_twofactor.py | 2 +- frappe/tests/test_utils.py | 12 +- frappe/tests/utils.py | 4 +- frappe/translate.py | 13 +- frappe/twofactor.py | 1 - frappe/utils/__init__.py | 12 +- frappe/utils/background_jobs.py | 14 +- frappe/utils/backups.py | 7 +- frappe/utils/boilerplate.py | 6 +- frappe/utils/caching.py | 4 +- frappe/utils/change_log.py | 4 +- frappe/utils/csvutils.py | 2 +- frappe/utils/dashboard.py | 3 +- frappe/utils/data.py | 25 ++-- frappe/utils/dateutils.py | 7 +- frappe/utils/formatters.py | 2 +- frappe/utils/global_search.py | 4 +- frappe/utils/image.py | 2 +- frappe/utils/jinja_globals.py | 2 +- frappe/utils/make_random.py | 14 +- frappe/utils/oauth.py | 2 +- frappe/utils/print_format.py | 2 +- frappe/utils/redis_wrapper.py | 2 +- frappe/utils/response.py | 4 +- frappe/utils/safe_exec.py | 20 ++- frappe/utils/typing_validations.py | 20 +-- frappe/utils/user.py | 2 +- frappe/website/doctype/blog_post/blog_post.py | 2 +- .../doctype/blog_post/test_blog_post.py | 4 +- frappe/website/doctype/web_page/web_page.py | 6 +- .../website_settings/website_settings.py | 2 +- .../website/page_renderers/template_page.py | 4 +- frappe/website/path_resolver.py | 5 +- .../website_analytics/website_analytics.py | 18 +-- frappe/website/router.py | 6 +- frappe/workflow/doctype/workflow/workflow.py | 12 +- .../workflow_action/workflow_action.py | 6 +- frappe/www/list.py | 6 +- frappe/www/printview.py | 2 +- frappe/www/qrcode.py | 2 +- frappe/www/search.py | 2 +- pyproject.toml | 3 + 176 files changed, 860 insertions(+), 713 deletions(-) create mode 100644 frappe/gettext/extractors/navbar.py diff --git a/.github/helper/documentation.py b/.github/helper/documentation.py index b541583fd6f..7eb209cbde2 100644 --- a/.github/helper/documentation.py +++ b/.github/helper/documentation.py @@ -1,7 +1,7 @@ import sys -import requests from urllib.parse import urlparse +import requests WEBSITE_REPOS = [ "erpnext_com", @@ -36,11 +36,7 @@ def is_documentation_link(word: str) -> bool: def contains_documentation_link(body: str) -> bool: - return any( - is_documentation_link(word) - for line in body.splitlines() - for word in line.split() - ) + return any(is_documentation_link(word) for line in body.splitlines() for word in line.split()) def check_pull_request(number: str) -> "tuple[int, str]": @@ -53,12 +49,7 @@ def check_pull_request(number: str) -> "tuple[int, str]": head_sha = (payload.get("head") or {}).get("sha") body = (payload.get("body") or "").lower() - if ( - not title.startswith("feat") - or not head_sha - or "no-docs" in body - or "backport" in body - ): + if not title.startswith("feat") or not head_sha or "no-docs" in body or "backport" in body: return 0, "Skipping documentation checks... 🏃" if contains_documentation_link(body): diff --git a/.github/helper/roulette.py b/.github/helper/roulette.py index e3b212fa89b..06fc534b61a 100644 --- a/.github/helper/roulette.py +++ b/.github/helper/roulette.py @@ -6,11 +6,11 @@ import sys import time import urllib.request -from functools import lru_cache +from functools import cache from urllib.error import HTTPError -@lru_cache(maxsize=None) +@cache def fetch_pr_data(pr_number, repo, endpoint=""): api_url = f"https://api.github.com/repos/{repo}/pulls/{pr_number}" @@ -82,9 +82,7 @@ def is_ci(file): def is_frontend_code(file): - return file.lower().endswith( - (".css", ".scss", ".less", ".sass", ".styl", ".js", ".ts", ".vue", ".html") - ) + return file.lower().endswith((".css", ".scss", ".less", ".sass", ".styl", ".js", ".ts", ".vue", ".html")) def is_docs(file): diff --git a/frappe/__init__.py b/frappe/__init__.py index 70ccb4a7a11..eb507334272 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -441,7 +441,7 @@ def errprint(msg: str) -> None: :param msg: Message.""" msg = as_unicode(msg) - if not request or (not "cmd" in local.form_dict) or conf.developer_mode: + if not request or ("cmd" not in local.form_dict) or conf.developer_mode: print(msg) error_log.append({"exc": msg}) @@ -470,7 +470,7 @@ def msgprint( as_list: bool = False, indicator: Literal["blue", "green", "orange", "red", "yellow"] | None = None, alert: bool = False, - primary_action: str = None, + primary_action: str | None = None, is_minimizable: bool = False, wide: bool = False, *, @@ -1212,7 +1212,7 @@ def get_cached_value(doctype: str, name: str, fieldname: str = "name", as_dict: values = [doc.get(f) for f in fieldname] if as_dict: - return _dict(zip(fieldname, values)) + return _dict(zip(fieldname, values, strict=False)) return values @@ -1556,7 +1556,7 @@ def _load_app_hooks(app_name: str | None = None): raise def _is_valid_hook(obj): - return not isinstance(obj, (types.ModuleType, types.FunctionType, type)) + return not isinstance(obj, types.ModuleType | types.FunctionType | type) for key, value in inspect.getmembers(app_hooks, predicate=_is_valid_hook): if not key.startswith("_"): @@ -1564,7 +1564,9 @@ def _is_valid_hook(obj): return hooks -def get_hooks(hook: str = None, default: Any | None = "_KEEP_DEFAULT_LIST", app_name: str = None) -> _dict: +def get_hooks( + hook: str | None = None, default: Any | None = "_KEEP_DEFAULT_LIST", app_name: str | None = None +) -> _dict: """Get hooks via `app/hooks.py` :param hook: Name of the hook. Will gather all hooks for this name and return as a list. @@ -1802,13 +1804,13 @@ def remove_no_copy_fields(d): newdoc = get_doc(copy.deepcopy(d)) newdoc.set("__islocal", 1) - for fieldname in fields_to_clear + ["amended_from", "amendment_date"]: + for fieldname in [*fields_to_clear, "amended_from", "amendment_date"]: newdoc.set(fieldname, None) if not ignore_no_copy: remove_no_copy_fields(newdoc) - for i, d in enumerate(newdoc.get_all_children()): + for _i, d in enumerate(newdoc.get_all_children()): d.set("__islocal", 1) for fieldname in fields_to_clear: @@ -1967,7 +1969,7 @@ def get_all(doctype, *args, **kwargs): frappe.get_all("ToDo", fields=["*"], filters = [["modified", ">", "2014-01-01"]]) """ kwargs["ignore_permissions"] = True - if not "limit_page_length" in kwargs: + if "limit_page_length" not in kwargs: kwargs["limit_page_length"] = 0 return get_list(doctype, *args, **kwargs) @@ -2397,7 +2399,7 @@ def mock(type, size=1, locale="en"): if type not in dir(fake): raise ValueError("Not a valid mock type.") else: - for i in range(size): + for _i in range(size): data = getattr(fake, type)() results.append(data) @@ -2411,7 +2413,7 @@ def validate_and_sanitize_search_inputs(fn): def wrapper(*args, **kwargs): from frappe.desk.search import sanitize_searchfield - kwargs.update(dict(zip(fn.__code__.co_varnames, args))) + kwargs.update(dict(zip(fn.__code__.co_varnames, args, strict=False))) sanitize_searchfield(kwargs["searchfield"]) kwargs["start"] = cint(kwargs["start"]) kwargs["page_len"] = cint(kwargs["page_len"]) @@ -2424,7 +2426,7 @@ def wrapper(*args, **kwargs): return wrapper -from frappe.utils.error import log_error # noqa: backward compatibility +from frappe.utils.error import log_error if _tune_gc: # generational GC gets triggered after certain allocs (g0) which is 700 by default. diff --git a/frappe/app.py b/frappe/app.py index 4ce356ecc8b..5406aa90169 100644 --- a/frappe/app.py +++ b/frappe/app.py @@ -22,7 +22,7 @@ import frappe.recorder import frappe.utils.response from frappe import _ -from frappe.auth import SAFE_HTTP_METHODS, UNSAFE_HTTP_METHODS, HTTPRequest, validate_auth # noqa +from frappe.auth import SAFE_HTTP_METHODS, UNSAFE_HTTP_METHODS, HTTPRequest, validate_auth from frappe.middlewares import StaticDataMiddleware from frappe.utils import CallbackManager, cint, get_site_name from frappe.utils.data import escape_html diff --git a/frappe/auth.py b/frappe/auth.py index f7093ac81c8..dfab9c7c21e 100644 --- a/frappe/auth.py +++ b/frappe/auth.py @@ -223,7 +223,7 @@ def clear_active_sessions(self): clear_sessions(frappe.session.user, keep_current=True) - def authenticate(self, user: str = None, pwd: str = None): + def authenticate(self, user: str | None = None, pwd: str | None = None): from frappe.core.doctype.user.user import User if not (user and pwd): @@ -378,7 +378,7 @@ def set_cookie( } def delete_cookie(self, to_delete): - if not isinstance(to_delete, (list, tuple)): + if not isinstance(to_delete, list | tuple): to_delete = [to_delete] self.to_delete.extend(to_delete) @@ -485,7 +485,7 @@ def __init__( max_consecutive_login_attempts: int = 3, lock_interval: int = 5 * 60, *, - user_name: str = None, + user_name: str | None = None, ): """Initialize the tracker. diff --git a/frappe/automation/doctype/assignment_rule/test_assignment_rule.py b/frappe/automation/doctype/assignment_rule/test_assignment_rule.py index ed5fca79029..da123840aa7 100644 --- a/frappe/automation/doctype/assignment_rule/test_assignment_rule.py +++ b/frappe/automation/doctype/assignment_rule/test_assignment_rule.py @@ -104,7 +104,7 @@ def test_load_balancing(self): frappe.db.delete("ToDo", {"name": d.name}) # add 5 more assignments - for i in range(5): + for _ in range(5): _make_test_record(public=1) # check if each user still has 10 assignments diff --git a/frappe/build.py b/frappe/build.py index 1d7796301bc..22fbe0195a5 100644 --- a/frappe/build.py +++ b/frappe/build.py @@ -178,9 +178,6 @@ def symlink(target, link_name, overwrite=False): if not overwrite: return os.symlink(target, link_name) - # os.replace() may fail if files are on different filesystems - link_dir = os.path.dirname(link_name) - # Create link to target with temporary filename while True: temp_link_name = f"tmp{frappe.generate_hash()}" diff --git a/frappe/cache_manager.py b/frappe/cache_manager.py index 960c72f0425..484916c12b8 100644 --- a/frappe/cache_manager.py +++ b/frappe/cache_manager.py @@ -41,7 +41,8 @@ def get_doctype_map_key(doctype): "information_schema:counts", "db_tables", "server_script_autocompletion_items", -) + doctype_map_keys + *doctype_map_keys, +) user_cache_keys = ( "bootinfo", @@ -110,7 +111,7 @@ def clear_global_cache(): def clear_defaults_cache(user=None): if user: - for p in [user] + common_default_keys: + for p in [user, *common_default_keys]: frappe.cache.hdel("defaults", p) elif frappe.flags.in_install != "frappe": frappe.cache.delete_key("defaults") diff --git a/frappe/commands/site.py b/frappe/commands/site.py index 39e560c9cc1..f3c2b526372 100644 --- a/frappe/commands/site.py +++ b/frappe/commands/site.py @@ -449,7 +449,7 @@ def install_app(context, apps, force=False): print(f"App {app} is Incompatible with Site {site}{err_msg}") exit_code = 1 except Exception as err: - err_msg = f": {str(err)}\n{frappe.get_traceback(with_context=True)}" + err_msg = f": {err!s}\n{frappe.get_traceback(with_context=True)}" print(f"An error occurred while installing {app}{err_msg}") exit_code = 1 @@ -1020,9 +1020,9 @@ def _drop_site( messages = [ "=" * 80, f"Error: The operation has stopped because backup of {site}'s database failed.", - f"Reason: {str(err)}\n", + f"Reason: {err!s}\n", "Fix the issue and try again.", - "Hint: Use 'bench drop-site {0} --force' to force the removal of {0}".format(site), + f"Hint: Use 'bench drop-site {site} --force' to force the removal of {site}", ] click.echo("\n".join(messages)) sys.exit(1) diff --git a/frappe/commands/translate.py b/frappe/commands/translate.py index d030656e773..96af8bc154c 100644 --- a/frappe/commands/translate.py +++ b/frappe/commands/translate.py @@ -37,9 +37,7 @@ def new_language(context, lang_code, app): frappe.connect(site=context["sites"][0]) frappe.translate.write_translations_file(app, lang_code) - print( - "File created at ./apps/{app}/{app}/translations/{lang_code}.csv".format(app=app, lang_code=lang_code) - ) + print(f"File created at ./apps/{app}/{app}/translations/{lang_code}.csv") print("You will need to add the language in frappe/geo/languages.json, if you haven't done it already.") diff --git a/frappe/commands/utils.py b/frappe/commands/utils.py index 22b7dde488a..4a5661adaf1 100644 --- a/frappe/commands/utils.py +++ b/frappe/commands/utils.py @@ -559,7 +559,7 @@ def jupyter(context): os.mkdir(jupyter_notebooks_path) bin_path = os.path.abspath("../env/bin") print( - """ + f""" Starting Jupyter notebook Run the following in your first cell to connect notebook to frappe ``` @@ -569,7 +569,7 @@ def jupyter(context): frappe.local.lang = frappe.db.get_default('lang') frappe.db.connect() ``` - """.format(site=site, sites_path=sites_path) + """ ) os.execv( f"{bin_path}/jupyter", diff --git a/frappe/config/__init__.py b/frappe/config/__init__.py index 6f97efbfdfd..74f717df157 100644 --- a/frappe/config/__init__.py +++ b/frappe/config/__init__.py @@ -2,7 +2,7 @@ from frappe import _ -def get_modules_from_all_apps_for_user(user: str = None) -> list[dict]: +def get_modules_from_all_apps_for_user(user: str | None = None) -> list[dict]: user = user or frappe.session.user all_modules = get_modules_from_all_apps() global_blocked_modules = frappe.get_doc("User", "Administrator").get_blocked_modules() diff --git a/frappe/contacts/doctype/address/address.py b/frappe/contacts/doctype/address/address.py index d089b9a75bc..4e57f50fa6d 100644 --- a/frappe/contacts/doctype/address/address.py +++ b/frappe/contacts/doctype/address/address.py @@ -125,11 +125,10 @@ def get_preferred_address(doctype, name, preferred_key="is_primary_address"): FROM `tabAddress` addr, `tabDynamic Link` dl WHERE - dl.parent = addr.name and dl.link_doctype = %s and - dl.link_name = %s and ifnull(addr.disabled, 0) = 0 and - %s = %s - """ - % ("%s", "%s", preferred_key, "%s"), + dl.parent = addr.name and dl.link_doctype = {} and + dl.link_name = {} and ifnull(addr.disabled, 0) = 0 and + {} = {} + """.format("%s", "%s", preferred_key, "%s"), (doctype, name, 1), as_dict=1, ) diff --git a/frappe/contacts/doctype/contact/contact.py b/frappe/contacts/doctype/contact/contact.py index 6f167b700ea..f5efd02c2f1 100644 --- a/frappe/contacts/doctype/contact/contact.py +++ b/frappe/contacts/doctype/contact/contact.py @@ -256,7 +256,7 @@ def contact_query(doctype, txt, searchfield, start, page_len, filters): link_name = filters.pop("link_name") return frappe.db.sql( - """select + f"""select `tabContact`.name, `tabContact`.full_name, `tabContact`.company_name from `tabContact`, `tabDynamic Link` @@ -265,12 +265,12 @@ def contact_query(doctype, txt, searchfield, start, page_len, filters): `tabDynamic Link`.parenttype = 'Contact' and `tabDynamic Link`.link_doctype = %(link_doctype)s and `tabDynamic Link`.link_name = %(link_name)s and - `tabContact`.`{key}` like %(txt)s - {mcond} + `tabContact`.`{searchfield}` like %(txt)s + {get_match_cond(doctype)} order by if(locate(%(_txt)s, `tabContact`.full_name), locate(%(_txt)s, `tabContact`.company_name), 99999), `tabContact`.idx desc, `tabContact`.full_name - limit %(start)s, %(page_len)s """.format(mcond=get_match_cond(doctype), key=searchfield), + limit %(start)s, %(page_len)s """, { "txt": "%" + txt + "%", "_txt": txt.replace("%", ""), diff --git a/frappe/contacts/report/addresses_and_contacts/addresses_and_contacts.py b/frappe/contacts/report/addresses_and_contacts/addresses_and_contacts.py index 9bb661d90c5..b1b76a1c124 100644 --- a/frappe/contacts/report/addresses_and_contacts/addresses_and_contacts.py +++ b/frappe/contacts/report/addresses_and_contacts/addresses_and_contacts.py @@ -52,7 +52,6 @@ def get_columns(filters): def get_data(filters): - data = [] reference_doctype = filters.get("reference_doctype") reference_name = filters.get("reference_name") @@ -108,7 +107,7 @@ def get_reference_details(reference_doctype, doctype, reference_list, reference_ ["Dynamic Link", "link_doctype", "=", reference_doctype], ["Dynamic Link", "link_name", "in", reference_list], ] - fields = ["`tabDynamic Link`.link_name"] + field_map.get(doctype, []) + fields = ["`tabDynamic Link`.link_name", *field_map.get(doctype, [])] records = frappe.get_list(doctype, filters=filters, fields=fields, as_list=True) temp_records = [d[1:] for d in records] diff --git a/frappe/core/doctype/activity_log/activity_log.py b/frappe/core/doctype/activity_log/activity_log.py index e7539cc12a1..39f11caa99d 100644 --- a/frappe/core/doctype/activity_log/activity_log.py +++ b/frappe/core/doctype/activity_log/activity_log.py @@ -53,7 +53,7 @@ def set_status(self): def set_ip_address(self): if self.operation in ("Login", "Logout"): - self.ip_address = getattr(frappe.local, "request_ip") + self.ip_address = frappe.local.request_ip @staticmethod def clear_old_logs(days=None): diff --git a/frappe/core/doctype/communication/email.py b/frappe/core/doctype/communication/email.py index a0c9d35f201..111d18e1471 100755 --- a/frappe/core/doctype/communication/email.py +++ b/frappe/core/doctype/communication/email.py @@ -262,7 +262,7 @@ def add_attachments(name: str, attachments: Iterable[str | dict]) -> None: @frappe.whitelist(allow_guest=True, methods=("GET",)) -def mark_email_as_seen(name: str = None): +def mark_email_as_seen(name: str | None = None): frappe.request.after_response.add(lambda: _mark_email_as_seen(name)) frappe.response.update(frappe.utils.get_imaginary_pixel_response()) diff --git a/frappe/core/doctype/data_import/data_import.py b/frappe/core/doctype/data_import/data_import.py index 8ecff22a6e1..0011b26acb0 100644 --- a/frappe/core/doctype/data_import/data_import.py +++ b/frappe/core/doctype/data_import/data_import.py @@ -268,10 +268,10 @@ def post_process(out): for key in del_keys: if key in doc: del doc[key] - for k, v in doc.items(): + for v in doc.values(): if isinstance(v, list): for child in v: - for key in del_keys + ("docstatus", "doctype", "modified", "name"): + for key in (*del_keys, "docstatus", "doctype", "modified", "name"): if key in child: del child[key] diff --git a/frappe/core/doctype/data_import/exporter.py b/frappe/core/doctype/data_import/exporter.py index b1f1223b35d..a7dcd1acd71 100644 --- a/frappe/core/doctype/data_import/exporter.py +++ b/frappe/core/doctype/data_import/exporter.py @@ -105,7 +105,7 @@ def is_exportable(df): fields = [df for df in fields if is_exportable(df)] if "name" in fieldnames: - fields = [name_field] + fields + fields = [name_field, *fields] return fields or [] @@ -163,7 +163,7 @@ def format_column_name(df): parent_data = frappe.db.get_list( self.doctype, filters=filters, - fields=["name"] + parent_fields, + fields=["name", *parent_fields], limit_page_length=self.export_page_length, order_by=order_by, as_list=0, @@ -176,9 +176,13 @@ def format_column_name(df): continue child_table_df = self.meta.get_field(key) child_table_doctype = child_table_df.options - child_fields = ["name", "idx", "parent", "parentfield"] + list( - {format_column_name(df) for df in self.fields if df.parent == child_table_doctype} - ) + child_fields = [ + "name", + "idx", + "parent", + "parentfield", + *list({format_column_name(df) for df in self.fields if df.parent == child_table_doctype}), + ] data = frappe.get_all( child_table_doctype, filters={ diff --git a/frappe/core/doctype/data_import/importer.py b/frappe/core/doctype/data_import/importer.py index bb93b180233..789368a3b5b 100644 --- a/frappe/core/doctype/data_import/importer.py +++ b/frappe/core/doctype/data_import/importer.py @@ -1,7 +1,6 @@ # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors # License: MIT. See LICENSE -import io import json import os import re @@ -484,7 +483,7 @@ def get_data_for_import_preview(self): "read_only": col.df.read_only, } - data = [[row.row_number] + row.as_list() for row in self.data] + data = [[row.row_number, *row.as_list()] for row in self.data] warnings = self.get_warnings() @@ -525,7 +524,6 @@ def parse_next_row_for_import(self, data): # subsequent rows that have blank values in parent columns # are considered as child rows parent_column_indexes = self.header.get_column_indexes(self.doctype) - parent_row_values = first_row.get_values(parent_column_indexes) data_without_first_row = data[1:] for row in data_without_first_row: @@ -656,7 +654,7 @@ def _parse_doc(self, doctype, columns, values, parent_doc=None, table_df=None): for key in frappe.model.default_fields + frappe.model.child_table_fields + ("__islocal",): doc.pop(key, None) - for col, value in zip(columns, values): + for col, value in zip(columns, values, strict=False): df = col.df if value in INVALID_VALUES: value = None @@ -751,7 +749,7 @@ def link_exists(self, value, df): def parse_value(self, value, col): df = col.df - if isinstance(value, (datetime, date)) and df.fieldtype in ["Date", "Datetime"]: + if isinstance(value, datetime | date) and df.fieldtype in ["Date", "Datetime"]: return value value = cstr(value) @@ -774,7 +772,7 @@ def parse_value(self, value, col): return value def get_date(self, value, column): - if isinstance(value, (datetime, date)): + if isinstance(value, datetime | date): return value date_format = column.date_format @@ -938,7 +936,7 @@ def guess_date_format_for_column(self): """ def guess_date_format(d): - if isinstance(d, (datetime, date, time)): + if isinstance(d, datetime | date | time): if self.df.fieldtype == "Date": return "%Y-%m-%d" if self.df.fieldtype == "Datetime": @@ -1137,7 +1135,6 @@ def get_standard_fields(doctype): label = (df.label or "").strip() translated_label = _(label) - parent = df.parent or parent_doctype if parent_doctype == doctype: # for parent doctypes keys will be diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index a2fcd9df56b..ac6f39a8ba7 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -288,7 +288,7 @@ def set_default_in_list_view(self): if not [d.fieldname for d in self.fields if d.in_list_view]: cnt = 0 for d in self.fields: - if d.reqd and not d.hidden and not d.fieldtype in not_allowed_in_list_view: + if d.reqd and not d.hidden and d.fieldtype not in not_allowed_in_list_view: d.in_list_view = 1 cnt += 1 if cnt == 4: @@ -403,7 +403,7 @@ def validate_website(self): if self.has_web_view: # route field must be present - if not "route" in [d.fieldname for d in self.fields]: + if "route" not in [d.fieldname for d in self.fields]: frappe.throw(_('Field "route" is mandatory for Web Views'), title="Missing Field") # clear website cache @@ -1238,7 +1238,7 @@ def check_link_table_options(docname, d): if frappe.flags.in_patch or frappe.flags.in_fixtures: return - if d.fieldtype in ("Link",) + table_fields: + if d.fieldtype in ("Link", *table_fields): if not d.options: frappe.throw( _("{0}: Options required for Link or Table type field {1} in row {2}").format( @@ -1356,11 +1356,9 @@ def check_unique_and_text(docname, d): if not d.get("__islocal") and frappe.db.has_column(d.parent, d.fieldname): has_non_unique_values = frappe.db.sql( - """select `{fieldname}`, count(*) - from `tab{doctype}` where ifnull(`{fieldname}`, '') != '' - group by `{fieldname}` having count(*) > 1 limit 1""".format( - doctype=d.parent, fieldname=d.fieldname - ) + f"""select `{d.fieldname}`, count(*) + from `tab{d.parent}` where ifnull(`{d.fieldname}`, '') != '' + group by `{d.fieldname}` having count(*) > 1 limit 1""" ) if has_non_unique_values and has_non_unique_values[0][0]: @@ -1534,7 +1532,7 @@ def scrub_options_in_select(field): field.options = "\n".join(options_list) def scrub_fetch_from(field): - if hasattr(field, "fetch_from") and getattr(field, "fetch_from"): + if hasattr(field, "fetch_from") and field.fetch_from: field.fetch_from = field.fetch_from.strip("\n").strip() def validate_data_field_type(docfield): @@ -1819,7 +1817,7 @@ def make_module_and_roles(doc, perm_fieldname="permissions"): r.desk_access = 1 r.flags.ignore_mandatory = r.flags.ignore_permissions = True r.insert() - except frappe.DoesNotExistError as e: + except frappe.DoesNotExistError: pass except frappe.db.ProgrammingError as e: if frappe.db.is_table_missing(e): diff --git a/frappe/core/doctype/document_naming_settings/document_naming_settings.py b/frappe/core/doctype/document_naming_settings/document_naming_settings.py index 9e0764c8c3e..4acd02a8f5e 100644 --- a/frappe/core/doctype/document_naming_settings/document_naming_settings.py +++ b/frappe/core/doctype/document_naming_settings/document_naming_settings.py @@ -127,7 +127,7 @@ def set_series_options_in_meta(self, doctype: str, options: str) -> None: self.validate_series_name(series) if options and self.user_must_always_select: - options = [""] + options + options = ["", *options] default = options[0] if options else "" @@ -246,7 +246,7 @@ def preview_series(self) -> str: return "\n".join(NamingSeries(series).get_preview(doc=doc)) except Exception as e: frappe.clear_last_message() - return _("Failed to generate names from the series") + f"\n{str(e)}" + return _("Failed to generate names from the series") + f"\n{e!s}" def _fetch_last_doc_if_available(self): """Fetch last doc for evaluating naming series with fields.""" diff --git a/frappe/core/doctype/domain_settings/domain_settings.py b/frappe/core/doctype/domain_settings/domain_settings.py index 35bafcb02cd..8484399f2a0 100644 --- a/frappe/core/doctype/domain_settings/domain_settings.py +++ b/frappe/core/doctype/domain_settings/domain_settings.py @@ -22,7 +22,7 @@ def set_active_domains(self, domains): active_domains = [d.domain for d in self.active_domains] added = False for d in domains: - if not d in active_domains: + if d not in active_domains: self.append("active_domains", dict(domain=d)) added = True diff --git a/frappe/core/doctype/dynamic_link/dynamic_link.py b/frappe/core/doctype/dynamic_link/dynamic_link.py index 0ff01eb438b..faf78cb4259 100644 --- a/frappe/core/doctype/dynamic_link/dynamic_link.py +++ b/frappe/core/doctype/dynamic_link/dynamic_link.py @@ -32,7 +32,7 @@ def deduplicate_dynamic_links(doc): links, duplicate = [], False for l in doc.links or []: t = (l.link_doctype, l.link_name) - if not t in links: + if t not in links: links.append(t) else: duplicate = True diff --git a/frappe/core/doctype/error_log/test_error_log.py b/frappe/core/doctype/error_log/test_error_log.py index 22eeea329ef..98c87dda52b 100644 --- a/frappe/core/doctype/error_log/test_error_log.py +++ b/frappe/core/doctype/error_log/test_error_log.py @@ -52,15 +52,21 @@ def test_ldap_exceptions(self): frappe.exceptions.ValidationError: what """ -TEST_EXCEPTIONS = { - "erpnext (app)": _RAW_EXC, - "erpnext (app)": _THROW_EXC, -} +TEST_EXCEPTIONS = ( + ( + "erpnext (app)", + _RAW_EXC, + ), + ( + "erpnext (app)", + _THROW_EXC, + ), +) class TestExceptionSourceGuessing(FrappeTestCase): @patch.object(frappe, "get_installed_apps", return_value=["frappe", "erpnext", "3pa"]) def test_exc_source_guessing(self, _installed_apps): - for source, exc in TEST_EXCEPTIONS.items(): + for source, exc in TEST_EXCEPTIONS: result = guess_exception_source(exc) self.assertEqual(result, source) diff --git a/frappe/core/doctype/file/file.py b/frappe/core/doctype/file/file.py index d22786d8dce..0876040eee0 100755 --- a/frappe/core/doctype/file/file.py +++ b/frappe/core/doctype/file/file.py @@ -135,7 +135,7 @@ def validate_attachment_references(self): if not self.attached_to_doctype: return - if not self.attached_to_name or not isinstance(self.attached_to_name, (str, int)): + if not self.attached_to_name or not isinstance(self.attached_to_name, str | int): frappe.throw(_("Attached To Name must be a string or an integer"), frappe.ValidationError) if self.attached_to_field and SPECIAL_CHAR_PATTERN.search(self.attached_to_field): @@ -812,7 +812,7 @@ def has_permission(doc, ptype=None, user=None, debug=False): return False -def get_permission_query_conditions(user: str = None) -> str: +def get_permission_query_conditions(user: str | None = None) -> str: user = user or frappe.session.user if user == "Administrator": return "" diff --git a/frappe/core/doctype/file/utils.py b/frappe/core/doctype/file/utils.py index ee9fd805f02..941457746a4 100644 --- a/frappe/core/doctype/file/utils.py +++ b/frappe/core/doctype/file/utils.py @@ -169,7 +169,7 @@ def delete_file(path: str) -> None: os.remove(path) -def remove_file_by_url(file_url: str, doctype: str = None, name: str = None) -> "Document": +def remove_file_by_url(file_url: str, doctype: str | None = None, name: str | None = None) -> "Document": if doctype and name: fid = frappe.db.get_value( "File", {"file_url": file_url, "attached_to_doctype": doctype, "attached_to_name": name} @@ -276,7 +276,7 @@ def _save_file(match): return content -def get_random_filename(content_type: str = None) -> str: +def get_random_filename(content_type: str | None = None) -> str: extn = None if content_type: extn = mimetypes.guess_extension(content_type) @@ -413,7 +413,7 @@ def decode_file_content(content: bytes) -> bytes: return safe_b64decode(content) -def find_file_by_url(path: str, name: str = None) -> Optional["File"]: +def find_file_by_url(path: str, name: str | None = None) -> Optional["File"]: filters = {"file_url": str(path)} if name: filters["name"] = str(name) diff --git a/frappe/core/doctype/module_def/module_def.py b/frappe/core/doctype/module_def/module_def.py index 8607a9a51bc..43fb71c8a7a 100644 --- a/frappe/core/doctype/module_def/module_def.py +++ b/frappe/core/doctype/module_def/module_def.py @@ -48,7 +48,7 @@ def add_to_modules_txt(self): if not frappe.local.module_app.get(frappe.scrub(self.name)): with open(frappe.get_app_path(self.app_name, "modules.txt")) as f: content = f.read() - if not self.name in content.splitlines(): + if self.name not in content.splitlines(): modules = list(filter(None, content.splitlines())) modules.append(self.name) diff --git a/frappe/core/doctype/page/page.py b/frappe/core/doctype/page/page.py index 81cd6930569..fe92865ece6 100644 --- a/frappe/core/doctype/page/page.py +++ b/frappe/core/doctype/page/page.py @@ -87,14 +87,13 @@ def on_update(self): if not os.path.exists(path + ".js"): with open(path + ".js", "w") as f: f.write( - """frappe.pages['%s'].on_page_load = function(wrapper) { - var page = frappe.ui.make_app_page({ + f"""frappe.pages['{self.name}'].on_page_load = function(wrapper) {{ + var page = frappe.ui.make_app_page({{ parent: wrapper, - title: '%s', + title: '{self.title}', single_column: true - }); -}""" - % (self.name, self.title) + }}); +}}""" ) def as_dict(self, no_nulls=False): diff --git a/frappe/core/doctype/report/report.py b/frappe/core/doctype/report/report.py index 362e38e780f..27c5e078cfa 100644 --- a/frappe/core/doctype/report/report.py +++ b/frappe/core/doctype/report/report.py @@ -301,7 +301,7 @@ def get_standard_report_filters(self, params, filters): if filters: for key, value in filters.items(): condition, _value = "=", value - if isinstance(value, (list, tuple)): + if isinstance(value, list | tuple): condition, _value = value _filters.append([key, condition, _value]) @@ -360,7 +360,7 @@ def build_standard_report_columns(self, columns, group_by_args): def build_data_dict(self, result, columns): data = [] for row in result: - if isinstance(row, (list, tuple)): + if isinstance(row, list | tuple): _row = frappe._dict() for i, val in enumerate(row): _row[columns[i].get("fieldname")] = val diff --git a/frappe/core/doctype/report/test_report.py b/frappe/core/doctype/report/test_report.py index cda004c00c7..b22f4b491c5 100644 --- a/frappe/core/doctype/report/test_report.py +++ b/frappe/core/doctype/report/test_report.py @@ -131,7 +131,12 @@ def test_custom_report(self): self.assertListEqual(["email"], [column.get("fieldname") for column in columns]) admin_dict = frappe.core.utils.find(result, lambda d: d["name"] == "Administrator") self.assertDictEqual( - {"name": "Administrator", "user_type": "System User", "email": "admin@example.com"}, admin_dict + { + "name": "Administrator", + "user_type": "System User", + "email": "admin@example.com", + }, + admin_dict, ) def test_report_with_custom_column(self): @@ -156,10 +161,18 @@ def test_report_with_custom_column(self): ) result = response.get("result") columns = response.get("columns") - self.assertListEqual(["name", "email", "user_type"], [column.get("fieldname") for column in columns]) + self.assertListEqual( + ["name", "email", "user_type"], + [column.get("fieldname") for column in columns], + ) admin_dict = frappe.core.utils.find(result, lambda d: d["name"] == "Administrator") self.assertDictEqual( - {"name": "Administrator", "user_type": "System User", "email": "admin@example.com"}, admin_dict + { + "name": "Administrator", + "user_type": "System User", + "email": "admin@example.com", + }, + admin_dict, ) def test_report_permissions(self): @@ -167,9 +180,7 @@ def test_report_permissions(self): frappe.db.delete("Has Role", {"parent": frappe.session.user, "role": "Test Has Role"}) frappe.db.commit() if not frappe.db.exists("Role", "Test Has Role"): - role = frappe.get_doc({"doctype": "Role", "role_name": "Test Has Role"}).insert( - ignore_permissions=True - ) + frappe.get_doc({"doctype": "Role", "role_name": "Test Has Role"}).insert(ignore_permissions=True) if not frappe.db.exists("Report", "Test Report"): report = frappe.get_doc( @@ -253,18 +264,18 @@ def test_non_standard_script_report(self): report.report_script = """ totals = {} for user in frappe.get_all('User', fields = ['name', 'user_type', 'creation']): - if not user.user_type in totals: - totals[user.user_type] = 0 - totals[user.user_type] = totals[user.user_type] + 1 + if not user.user_type in totals: + totals[user.user_type] = 0 + totals[user.user_type] = totals[user.user_type] + 1 data = [ - [ - {'fieldname': 'type', 'label': 'Type'}, - {'fieldname': 'value', 'label': 'Value'} - ], - [ - {"type":key, "value": value} for key, value in totals.items() - ] + [ + {'fieldname': 'type', 'label': 'Type'}, + {'fieldname': 'value', 'label': 'Value'} + ], + [ + {"type":key, "value": value} for key, value in totals.items() + ] ] """ report.save() @@ -299,13 +310,13 @@ def test_script_report_with_columns(self): report.report_script = """ totals = {} for user in frappe.get_all('User', fields = ['name', 'user_type', 'creation']): - if not user.user_type in totals: - totals[user.user_type] = 0 - totals[user.user_type] = totals[user.user_type] + 1 + if not user.user_type in totals: + totals[user.user_type] = 0 + totals[user.user_type] = totals[user.user_type] + 1 result = [ - {"type":key, "value": value} for key, value in totals.items() - ] + {"type":key, "value": value} for key, value in totals.items() + ] """ report.save() @@ -344,15 +355,40 @@ def test_add_total_row_for_tree_reports(self): report_settings = {"tree": True, "parent_field": "parent_value"} columns = [ - {"fieldname": "parent_column", "label": "Parent Column", "fieldtype": "Data", "width": 10}, - {"fieldname": "column_1", "label": "Column 1", "fieldtype": "Float", "width": 10}, - {"fieldname": "column_2", "label": "Column 2", "fieldtype": "Float", "width": 10}, + { + "fieldname": "parent_column", + "label": "Parent Column", + "fieldtype": "Data", + "width": 10, + }, + { + "fieldname": "column_1", + "label": "Column 1", + "fieldtype": "Float", + "width": 10, + }, + { + "fieldname": "column_2", + "label": "Column 2", + "fieldtype": "Float", + "width": 10, + }, ] result = [ {"parent_column": "Parent 1", "column_1": 200, "column_2": 150.50}, - {"parent_column": "Child 1", "column_1": 100, "column_2": 75.25, "parent_value": "Parent 1"}, - {"parent_column": "Child 2", "column_1": 100, "column_2": 75.25, "parent_value": "Parent 1"}, + { + "parent_column": "Child 1", + "column_1": 100, + "column_2": 75.25, + "parent_value": "Parent 1", + }, + { + "parent_column": "Child 2", + "column_1": 100, + "column_2": 75.25, + "parent_value": "Parent 1", + }, ] result = add_total_row( @@ -369,13 +405,13 @@ def test_add_total_row_for_tree_reports(self): def test_cte_in_query_report(self): cte_query = textwrap.dedent( """ - with enabled_users as ( - select name - from `tabUser` - where enabled = 1 - ) - select * from enabled_users; - """ + with enabled_users as ( + select name + from `tabUser` + where enabled = 1 + ) + select * from enabled_users; + """ ) report = frappe.get_doc( diff --git a/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py b/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py index f1d08ebacf0..712962638d5 100644 --- a/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py +++ b/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py @@ -187,7 +187,7 @@ def run_scheduled_job(job_type: str): print(frappe.get_traceback()) -def sync_jobs(hooks: dict = None): +def sync_jobs(hooks: dict | None = None): frappe.reload_doc("core", "doctype", "scheduled_job_type") scheduler_events = hooks or frappe.get_hooks("scheduler_events") all_events = insert_events(scheduler_events) @@ -224,7 +224,7 @@ def insert_event_jobs(events: list, event_type: str) -> list: return event_jobs -def insert_single_event(frequency: str, event: str, cron_format: str = None): +def insert_single_event(frequency: str, event: str, cron_format: str | None = None): cron_expr = {"cron_format": cron_format} if cron_format else {} try: diff --git a/frappe/core/doctype/server_script/server_script.py b/frappe/core/doctype/server_script/server_script.py index d38538594af..02df54967ac 100644 --- a/frappe/core/doctype/server_script/server_script.py +++ b/frappe/core/doctype/server_script/server_script.py @@ -211,7 +211,7 @@ def get_keys(obj): if key.startswith("_"): continue value = obj[key] - if isinstance(value, (NamespaceDict, dict)) and value: + if isinstance(value, NamespaceDict | dict) and value: if key == "form_dict": out.append(["form_dict", 7]) continue @@ -223,7 +223,7 @@ def get_keys(obj): score = 0 elif isinstance(value, ModuleType): score = 10 - elif isinstance(value, (FunctionType, MethodType)): + elif isinstance(value, FunctionType | MethodType): score = 9 elif isinstance(value, type): score = 8 diff --git a/frappe/core/doctype/server_script/server_script_utils.py b/frappe/core/doctype/server_script/server_script_utils.py index 6ba65e73533..d0ae253d29f 100644 --- a/frappe/core/doctype/server_script/server_script_utils.py +++ b/frappe/core/doctype/server_script/server_script_utils.py @@ -23,7 +23,7 @@ def run_server_script_for_doc_event(doc, event): # run document event method - if not event in EVENT_MAP: + if event not in EVENT_MAP: return if frappe.flags.in_install: diff --git a/frappe/core/doctype/server_script/test_server_script.py b/frappe/core/doctype/server_script/test_server_script.py index cbf3ef977ad..e7735c8fc9f 100644 --- a/frappe/core/doctype/server_script/test_server_script.py +++ b/frappe/core/doctype/server_script/test_server_script.py @@ -218,7 +218,7 @@ def test_scripts_all_the_way_down(self): name="test_nested_scripts_1", script_type="API", api_method="test_nested_scripts_1", - script=f"""log("nothing")""", + script="""log("nothing")""", ) script.insert() script.execute_method() @@ -228,7 +228,7 @@ def test_scripts_all_the_way_down(self): name="test_nested_scripts_2", script_type="API", api_method="test_nested_scripts_2", - script=f"""frappe.call("test_nested_scripts_1")""", + script="""frappe.call("test_nested_scripts_1")""", ) script.insert() script.execute_method() diff --git a/frappe/core/doctype/system_settings/system_settings.py b/frappe/core/doctype/system_settings/system_settings.py index 9205aa7b2a7..3b4636e4f81 100644 --- a/frappe/core/doctype/system_settings/system_settings.py +++ b/frappe/core/doctype/system_settings/system_settings.py @@ -203,7 +203,7 @@ def update_last_reset_password_date(): def load(): from frappe.utils.momentjs import get_all_timezones - if not "System Manager" in frappe.get_roles(): + if "System Manager" not in frappe.get_roles(): frappe.throw(_("Not permitted"), frappe.PermissionError) all_defaults = frappe.db.get_defaults() diff --git a/frappe/core/doctype/user/user.py b/frappe/core/doctype/user/user.py index 7cea7a9e8fa..b53d62d3c25 100644 --- a/frappe/core/doctype/user/user.py +++ b/frappe/core/doctype/user/user.py @@ -575,10 +575,9 @@ def after_rename(self, old_name, new_name, merge=False): has_fields = [d.get("name") for d in desc if d.get("name") in ["owner", "modified_by"]] for field in has_fields: frappe.db.sql( - """UPDATE `%s` - SET `%s` = %s - WHERE `%s` = %s""" - % (tab, field, "%s", field, "%s"), + """UPDATE `{}` + SET `{}` = {} + WHERE `{}` = {}""".format(tab, field, "%s", field, "%s"), (new_name, old_name), ) @@ -621,7 +620,7 @@ def remove_disabled_roles(self): def ensure_unique_roles(self): exists = [] - for i, d in enumerate(self.get("roles")): + for d in self.get("roles"): if (not d.role) or (d.role in exists): self.get("roles").remove(d) else: @@ -807,7 +806,7 @@ def get_perm_info(role): @frappe.whitelist(allow_guest=True) def update_password( - new_password: str, logout_all_sessions: int = 0, key: str = None, old_password: str = None + new_password: str, logout_all_sessions: int = 0, key: str | None = None, old_password: str | None = None ): """Update password for the current user. @@ -1179,7 +1178,7 @@ def handle_password_test_fail(feedback: dict): suggestions = feedback.get("suggestions", []) warning = feedback.get("warning", "") - frappe.throw(msg=" ".join([warning] + suggestions), title=_("Invalid Password")) + frappe.throw(msg=" ".join([warning, *suggestions]), title=_("Invalid Password")) def update_gravatar(name): diff --git a/frappe/core/report/permitted_documents_for_user/permitted_documents_for_user.py b/frappe/core/report/permitted_documents_for_user/permitted_documents_for_user.py index b865c23b11a..bf1694b1641 100644 --- a/frappe/core/report/permitted_documents_for_user/permitted_documents_for_user.py +++ b/frappe/core/report/permitted_documents_for_user/permitted_documents_for_user.py @@ -36,11 +36,7 @@ def get_columns_and_fields(doctype): if df.in_list_view and df.fieldtype in data_fieldtypes: fields.append(f"`{df.fieldname}`") fieldtype = f"Link/{df.options}" if df.fieldtype == "Link" else df.fieldtype - columns.append( - "{label}:{fieldtype}:{width}".format( - label=df.label, fieldtype=fieldtype, width=df.width or 100 - ) - ) + columns.append(f"{df.label}:{fieldtype}:{df.width or 100}") return columns, fields diff --git a/frappe/coverage.py b/frappe/coverage.py index a1f3e26585a..b1e5a96249a 100644 --- a/frappe/coverage.py +++ b/frappe/coverage.py @@ -42,7 +42,8 @@ "*frappe/setup.py", "*/doctype/*/*_dashboard.py", "*/patches/*", -] + TESTED_VIA_CLI + *TESTED_VIA_CLI, +] class CodeCoverage: diff --git a/frappe/custom/doctype/customize_form/customize_form.py b/frappe/custom/doctype/customize_form/customize_form.py index a061f79af89..58cdd45d2d4 100644 --- a/frappe/custom/doctype/customize_form/customize_form.py +++ b/frappe/custom/doctype/customize_form/customize_form.py @@ -591,11 +591,11 @@ def validate_fieldtype_length(self): max_length = cint(frappe.db.type_map.get(df.fieldtype)[1]) fieldname = df.fieldname docs = frappe.db.sql( - """ + f""" SELECT name, {fieldname}, LENGTH({fieldname}) AS len - FROM `tab{doctype}` + FROM `tab{self.doc_type}` WHERE LENGTH({fieldname}) > {max_length} - """.format(fieldname=fieldname, doctype=self.doc_type, max_length=max_length), + """, as_dict=True, ) label = df.label diff --git a/frappe/custom/report/audit_system_hooks/audit_system_hooks.py b/frappe/custom/report/audit_system_hooks/audit_system_hooks.py index 0c3b4952719..07b8e18b4d3 100644 --- a/frappe/custom/report/audit_system_hooks/audit_system_hooks.py +++ b/frappe/custom/report/audit_system_hooks/audit_system_hooks.py @@ -35,7 +35,7 @@ def fmt_hook_values(v): v = delist(v) - if isinstance(v, (dict, list)): + if isinstance(v, dict | list): try: return frappe.as_json(v) except Exception: diff --git a/frappe/database/database.py b/frappe/database/database.py index e636965ac93..6e8ade72ca2 100644 --- a/frappe/database/database.py +++ b/frappe/database/database.py @@ -60,10 +60,10 @@ class Database: VARCHAR_LEN = 140 MAX_COLUMN_LENGTH = 64 - OPTIONAL_COLUMNS = ["_user_tags", "_comments", "_assign", "_liked_by"] - DEFAULT_SHORTCUTS = ["_Login", "__user", "_Full Name", "Today", "__today", "now", "Now"] + OPTIONAL_COLUMNS = ("_user_tags", "_comments", "_assign", "_liked_by") + DEFAULT_SHORTCUTS = ("_Login", "__user", "_Full Name", "Today", "__today", "now", "Now") STANDARD_VARCHAR_COLUMNS = ("name", "owner", "modified_by") - DEFAULT_COLUMNS = ["name", "creation", "modified", "modified_by", "owner", "docstatus", "idx"] + DEFAULT_COLUMNS = ("name", "creation", "modified", "modified_by", "owner", "docstatus", "idx") CHILD_TABLE_COLUMNS = ("parent", "parenttype", "parentfield") MAX_WRITES_PER_TRANSACTION = 200_000 @@ -114,8 +114,8 @@ def setup_type_map(self): def connect(self): """Connects to a database as set in `site_config.json`.""" self.cur_db_name = self.user - self._conn: Union["MariadbConnection", "PostgresConnection"] = self.get_connection() - self._cursor: Union["MariadbCursor", "PostgresCursor"] = self._conn.cursor() + self._conn: "MariadbConnection" | "PostgresConnection" = self.get_connection() + self._cursor: "MariadbCursor" | "PostgresCursor" = self._conn.cursor() try: if execution_timeout := get_query_execution_timeout(): @@ -193,7 +193,7 @@ def sql( {"name": "a%", "owner":"test@example.com"}) """ - if isinstance(query, (MySQLQueryBuilder, PostgreSQLQueryBuilder)): + if isinstance(query, MySQLQueryBuilder | PostgreSQLQueryBuilder): frappe.errprint("Use run method to execute SQL queries generated by Query Engine") debug = debug or getattr(self, "debug", False) @@ -222,7 +222,7 @@ def sql( if values == EmptyQueryValues: values = None - elif not isinstance(values, (tuple, dict, list)): + elif not isinstance(values, tuple | dict | list): values = (values,) query, values = self._transform_query(query, values) @@ -312,7 +312,7 @@ def _return_as_iterator(self, *, pluck, as_dict, as_list, update): elif as_dict: keys = [column[0] for column in self._cursor.description] for row in result: - row = frappe._dict(zip(keys, row)) + row = frappe._dict(zip(keys, row, strict=False)) if update: row.update(update) yield row @@ -381,7 +381,7 @@ def mogrify(self, query: Query, values: QueryValues): return query % { k: frappe.db.escape(v) if isinstance(v, str) else v for k, v in values.items() } - elif isinstance(values, (list, tuple)): + elif isinstance(values, list | tuple): return query % tuple(frappe.db.escape(v) if isinstance(v, str) else v for v in values) return query, values @@ -448,7 +448,7 @@ def fetch_as_dict(self, result) -> list[frappe._dict]: if result: keys = [column[0] for column in self._cursor.description] - return [frappe._dict(zip(keys, row)) for row in result] + return [frappe._dict(zip(keys, row, strict=False)) for row in result] @staticmethod def clear_db_table_cache(query): @@ -1244,7 +1244,7 @@ def multisql(self, sql_dict, values=(), **kwargs): query = sql_dict.get(current_dialect) return self.sql(query, values, **kwargs) - def delete(self, doctype: str, filters: dict | list = None, debug=False, **kwargs): + def delete(self, doctype: str, filters: dict | list | None = None, debug=False, **kwargs): """Delete rows from a table in site which match the passed filters. This does trigger DocType hooks. Simply runs a DELETE query in the database. diff --git a/frappe/database/mariadb/database.py b/frappe/database/mariadb/database.py index f494b859075..3fb5756ae7e 100644 --- a/frappe/database/mariadb/database.py +++ b/frappe/database/mariadb/database.py @@ -289,20 +289,20 @@ def create_auth_table(self): ) def create_global_search_table(self): - if not "__global_search" in self.get_tables(): + if "__global_search" not in self.get_tables(): self.sql( - """create table __global_search( + f"""create table __global_search( doctype varchar(100), - name varchar({0}), - title varchar({0}), + name varchar({self.VARCHAR_LEN}), + title varchar({self.VARCHAR_LEN}), content text, fulltext(content), - route varchar({0}), + route varchar({self.VARCHAR_LEN}), published int(1) not null default 0, unique `doctype_name` (doctype, name)) COLLATE=utf8mb4_unicode_ci ENGINE=MyISAM - CHARACTER SET=utf8mb4""".format(self.VARCHAR_LEN) + CHARACTER SET=utf8mb4""" ) def create_user_settings_table(self): @@ -322,7 +322,7 @@ def get_on_duplicate_update(key=None): def get_table_columns_description(self, table_name): """Returns list of column and its description""" return self.sql( - """select + f"""select column_name as 'name', column_type as 'type', column_default as 'default', @@ -337,7 +337,7 @@ def get_table_columns_description(self, table_name): ), 0) as 'index', column_key = 'UNI' as 'unique' from information_schema.columns as columns - where table_name = '{table_name}' """.format(table_name=table_name), + where table_name = '{table_name}' """, as_dict=1, ) @@ -358,8 +358,8 @@ def get_column_type(self, doctype, column): def has_index(self, table_name, index_name): return self.sql( - """SHOW INDEX FROM `{table_name}` - WHERE Key_name='{index_name}'""".format(table_name=table_name, index_name=index_name) + f"""SHOW INDEX FROM `{table_name}` + WHERE Key_name='{index_name}'""" ) def get_column_index(self, table_name: str, fieldname: str, unique: bool = False) -> frappe._dict | None: @@ -392,7 +392,7 @@ def get_column_index(self, table_name: str, fieldname: str, unique: bool = False if not clustered_index: return index - def add_index(self, doctype: str, fields: list, index_name: str = None): + def add_index(self, doctype: str, fields: list, index_name: str | None = None): """Creates an index with given fields if not already created. Index name will be `fieldname1_fieldname2_index`""" index_name = index_name or self.get_index_name(fields) @@ -400,9 +400,8 @@ def add_index(self, doctype: str, fields: list, index_name: str = None): if not self.has_index(table_name, index_name): self.commit() self.sql( - """ALTER TABLE `%s` - ADD INDEX `%s`(%s)""" - % (table_name, index_name, ", ".join(fields)) + """ALTER TABLE `{}` + ADD INDEX `{}`({})""".format(table_name, index_name, ", ".join(fields)) ) def add_unique(self, doctype, fields, constraint_name=None): @@ -418,9 +417,8 @@ def add_unique(self, doctype, fields, constraint_name=None): ): self.commit() self.sql( - """alter table `tab%s` - add unique `%s`(%s)""" - % (doctype, constraint_name, ", ".join(fields)) + """alter table `tab{}` + add unique `{}`({})""".format(doctype, constraint_name, ", ".join(fields)) ) def updatedb(self, doctype, meta=None): diff --git a/frappe/database/mariadb/setup_db.py b/frappe/database/mariadb/setup_db.py index 30476498037..dd0c1a7aed9 100644 --- a/frappe/database/mariadb/setup_db.py +++ b/frappe/database/mariadb/setup_db.py @@ -110,10 +110,7 @@ def check_database_settings(): result = True for key, expected_value in REQUIRED_MARIADB_CONFIG.items(): if mariadb_variables.get(key) != expected_value: - print( - "For key %s. Expected value %s, found value %s" - % (key, expected_value, mariadb_variables.get(key)) - ) + print(f"For key {key}. Expected value {expected_value}, found value {mariadb_variables.get(key)}") result = False if not result: diff --git a/frappe/database/postgres/database.py b/frappe/database/postgres/database.py index 12a973cc58c..39812488bf1 100644 --- a/frappe/database/postgres/database.py +++ b/frappe/database/postgres/database.py @@ -281,16 +281,16 @@ def create_auth_table(self): ) def create_global_search_table(self): - if not "__global_search" in self.get_tables(): + if "__global_search" not in self.get_tables(): self.sql( - """create table "__global_search"( + f"""create table "__global_search"( doctype varchar(100), - name varchar({0}), - title varchar({0}), + name varchar({self.VARCHAR_LEN}), + title varchar({self.VARCHAR_LEN}), content text, - route varchar({0}), + route varchar({self.VARCHAR_LEN}), published int not null default 0, - unique (doctype, name))""".format(self.VARCHAR_LEN) + unique (doctype, name))""" ) def create_user_settings_table(self): @@ -333,11 +333,11 @@ def check_implicit_commit(self, query): def has_index(self, table_name, index_name): return self.sql( - """SELECT 1 FROM pg_indexes WHERE tablename='{table_name}' - and indexname='{index_name}' limit 1""".format(table_name=table_name, index_name=index_name) + f"""SELECT 1 FROM pg_indexes WHERE tablename='{table_name}' + and indexname='{index_name}' limit 1""" ) - def add_index(self, doctype: str, fields: list, index_name: str = None): + def add_index(self, doctype: str, fields: list, index_name: str | None = None): """Creates an index with given fields if not already created. Index name will be `fieldname1_fieldname2_index`""" table_name = get_table_name(doctype) @@ -363,16 +363,15 @@ def add_unique(self, doctype, fields, constraint_name=None): ): self.commit() self.sql( - """ALTER TABLE `tab%s` - ADD CONSTRAINT %s UNIQUE (%s)""" - % (doctype, constraint_name, ", ".join(fields)) + """ALTER TABLE `tab{}` + ADD CONSTRAINT {} UNIQUE ({})""".format(doctype, constraint_name, ", ".join(fields)) ) def get_table_columns_description(self, table_name): """Returns list of column and its description""" # pylint: disable=W1401 return self.sql( - """ + f""" SELECT a.column_name AS name, CASE LOWER(a.data_type) WHEN 'character varying' THEN CONCAT('varchar(', a.character_maximum_length ,')') @@ -392,7 +391,7 @@ def get_table_columns_description(self, table_name): ON SUBSTRING(b.indexdef, '(.*)') LIKE CONCAT('%', a.column_name, '%') WHERE a.table_name = '{table_name}' GROUP BY a.column_name, a.data_type, a.column_default, a.character_maximum_length; - """.format(table_name=table_name), + """, as_dict=1, ) @@ -435,7 +434,7 @@ def modify_query(query): def modify_values(values): def modify_value(value): - if isinstance(value, (list, tuple)): + if isinstance(value, list | tuple): value = tuple(modify_values(value)) elif isinstance(value, int): @@ -449,7 +448,7 @@ def modify_value(value): if isinstance(values, dict): for k, v in values.items(): values[k] = modify_value(v) - elif isinstance(values, (tuple, list)): + elif isinstance(values, tuple | list): new_values = [] for val in values: new_values.append(modify_value(val)) diff --git a/frappe/database/postgres/schema.py b/frappe/database/postgres/schema.py index dec3a9520ca..9edc6be40f7 100644 --- a/frappe/database/postgres/schema.py +++ b/frappe/database/postgres/schema.py @@ -53,16 +53,14 @@ def create(self): def create_indexes(self): create_index_query = "" - for key, col in self.columns.items(): + for col in self.columns.values(): if ( col.set_index and col.fieldtype in frappe.db.type_map and frappe.db.type_map.get(col.fieldtype)[0] not in ("text", "longtext") ): create_index_query += ( - 'CREATE INDEX IF NOT EXISTS "{index_name}" ON `{table_name}`(`{field}`);'.format( - index_name=col.fieldname, table_name=self.table_name, field=col.fieldname - ) + f'CREATE INDEX IF NOT EXISTS "{col.fieldname}" ON `{self.table_name}`(`{col.fieldname}`);' ) if create_index_query: # nosemgrep @@ -114,9 +112,7 @@ def alter(self): for col in self.add_index: # if index key not exists create_contraint_query += ( - 'CREATE INDEX IF NOT EXISTS "{index_name}" ON `{table_name}`(`{field}`);'.format( - index_name=col.fieldname, table_name=self.table_name, field=col.fieldname - ) + f'CREATE INDEX IF NOT EXISTS "{col.fieldname}" ON `{self.table_name}`(`{col.fieldname}`);' ) for col in self.add_unique: diff --git a/frappe/database/postgres/setup_db.py b/frappe/database/postgres/setup_db.py index 0c2f8c18b7b..5ba39c20dcb 100644 --- a/frappe/database/postgres/setup_db.py +++ b/frappe/database/postgres/setup_db.py @@ -11,7 +11,7 @@ def setup_database(force, source_sql=None, verbose=False): root_conn.sql(f"DROP USER IF EXISTS {frappe.conf.db_name}") root_conn.sql(f"CREATE DATABASE `{frappe.conf.db_name}`") root_conn.sql(f"CREATE user {frappe.conf.db_name} password '{frappe.conf.db_password}'") - root_conn.sql("GRANT ALL PRIVILEGES ON DATABASE `{0}` TO {0}".format(frappe.conf.db_name)) + root_conn.sql(f"GRANT ALL PRIVILEGES ON DATABASE `{frappe.conf.db_name}` TO {frappe.conf.db_name}") root_conn.close() bootstrap_database(frappe.conf.db_name, verbose, source_sql=source_sql) @@ -54,7 +54,7 @@ def import_db_from_sql(source_sql=None, verbose=False): _command = ( f"psql {frappe.conf.db_name} " - f"-h {frappe.conf.db_host} -p {str(frappe.conf.db_port)} " + f"-h {frappe.conf.db_host} -p {frappe.conf.db_port!s} " f"-U {frappe.conf.db_name}" ) diff --git a/frappe/database/query.py b/frappe/database/query.py index 1fb5bb5e241..3e881ef7ca2 100644 --- a/frappe/database/query.py +++ b/frappe/database/query.py @@ -99,7 +99,7 @@ def apply_fields(self, fields): # add fields self.fields = self.parse_fields(fields) if not self.fields: - self.fields = [getattr(self.table, "name")] + self.fields = [self.table.name] self.query._child_queries = [] for field in self.fields: @@ -117,7 +117,7 @@ def apply_filters( if filters is None: return - if isinstance(filters, (str, int)): + if isinstance(filters, str | int): filters = {"name": str(filters)} if isinstance(filters, Criterion): @@ -126,14 +126,14 @@ def apply_filters( elif isinstance(filters, dict): self.apply_dict_filters(filters) - elif isinstance(filters, (list, tuple)): - if all(isinstance(d, (str, int)) for d in filters) and len(filters) > 0: + elif isinstance(filters, list | tuple): + if all(isinstance(d, str | int) for d in filters) and len(filters) > 0: self.apply_dict_filters({"name": ("in", filters)}) else: for filter in filters: - if isinstance(filter, (str, int, Criterion, dict)): + if isinstance(filter, str | int | Criterion | dict): self.apply_filters(filter) - elif isinstance(filter, (list, tuple)): + elif isinstance(filter, list | tuple): self.apply_list_filters(filter) def apply_list_filters(self, filter: list): @@ -150,7 +150,7 @@ def apply_list_filters(self, filter: list): def apply_dict_filters(self, filters: dict[str, str | int | list]): for field, value in filters.items(): operator = "=" - if isinstance(value, (list, tuple)): + if isinstance(value, list | tuple): operator, value = value self._apply_filter(field, value, operator) @@ -187,7 +187,7 @@ def _apply_filter( if isinstance(_value, bool): _value = int(_value) - elif not _value and isinstance(_value, (list, tuple)): + elif not _value and isinstance(_value, list | tuple): _value = ("",) # Nested set @@ -279,7 +279,7 @@ def _sanitize_field(field: str): return MARIADB_SPECIFIC_COMMENT.sub("", stripped_field) return stripped_field - if isinstance(fields, (list, tuple)): + if isinstance(fields, list | tuple): return [_sanitize_field(field) for field in fields] elif isinstance(fields, str): return _sanitize_field(fields) @@ -304,10 +304,10 @@ def parse_fields(self, fields: str | list | tuple | None) -> list: if not fields: return [] fields = self.sanitize_fields(fields) - if isinstance(fields, (list, tuple, set)) and None in fields and Field not in fields: + if isinstance(fields, list | tuple | set) and None in fields and Field not in fields: return [] - if not isinstance(fields, (list, tuple)): + if not isinstance(fields, list | tuple): fields = [fields] def parse_field(field: str): @@ -504,7 +504,7 @@ def get_query(self, parent_names=None) -> QueryBuilder: } return frappe.qb.get_query( self.doctype, - fields=self.fields + ["parent", "parentfield"], + fields=[*self.fields, "parent", "parentfield"], filters=filters, order_by="idx asc", ) diff --git a/frappe/database/schema.py b/frappe/database/schema.py index 99e2e0d4988..f97a9bafab0 100644 --- a/frappe/database/schema.py +++ b/frappe/database/schema.py @@ -47,7 +47,7 @@ def create(self): pass def get_column_definitions(self): - column_list = [] + frappe.db.DEFAULT_COLUMNS + column_list = [*frappe.db.DEFAULT_COLUMNS] ret = [] for k in list(self.columns): if k not in column_list: @@ -141,9 +141,7 @@ def validate(self): try: # check for truncation max_length = frappe.db.sql( - """SELECT MAX(CHAR_LENGTH(`{fieldname}`)) FROM `tab{doctype}`""".format( - fieldname=col.fieldname, doctype=self.doctype - ) + f"""SELECT MAX(CHAR_LENGTH(`{col.fieldname}`)) FROM `tab{self.doctype}`""" ) except frappe.db.InternalError as e: @@ -251,7 +249,7 @@ def build_for_alter_table(self, current_def): if (current_def["index"] and not self.set_index) and column_type not in ("text", "longtext"): self.table.drop_index.append(self) - elif (not current_def["index"] and self.set_index) and not (column_type in ("text", "longtext")): + elif (not current_def["index"] and self.set_index) and column_type not in ("text", "longtext"): self.table.add_index.append(self) def default_changed(self, current_def): diff --git a/frappe/defaults.py b/frappe/defaults.py index 65b145f3383..3a746d17e52 100644 --- a/frappe/defaults.py +++ b/frappe/defaults.py @@ -21,7 +21,7 @@ def get_user_default(key, user=None): d = user_defaults.get(key, None) if is_a_user_permission_key(key): - if d and isinstance(d, (list, tuple)) and len(d) == 1: + if d and isinstance(d, list | tuple) and len(d) == 1: # Use User Permission value when only when it has a single value d = d[0] else: @@ -31,7 +31,7 @@ def get_user_default(key, user=None): # If no default value is found, use the User Permission value d = user_permission_default - value = isinstance(d, (list, tuple)) and d[0] or d + value = isinstance(d, list | tuple) and d[0] or d if not_in_user_permission(key, value, user): return @@ -61,14 +61,14 @@ def get_user_default_as_list(key, user=None): d = user_defaults.get(key, None) if is_a_user_permission_key(key): - if d and isinstance(d, (list, tuple)) and len(d) == 1: + if d and isinstance(d, list | tuple) and len(d) == 1: # Use User Permission value when only when it has a single value d = [d[0]] else: d = user_defaults.get(frappe.scrub(key), None) - d = list(filter(None, (not isinstance(d, (list, tuple))) and [d] or d)) + d = list(filter(None, (not isinstance(d, list | tuple)) and [d] or d)) # filter default values if not found in user permission return [value for value in d if not not_in_user_permission(key, value)] @@ -135,7 +135,7 @@ def add_global_default(key, value): def get_global_default(key): d = get_defaults().get(key, None) - value = isinstance(d, (list, tuple)) and d[0] or d + value = isinstance(d, list | tuple) and d[0] or d if not_in_user_permission(key, value): return diff --git a/frappe/deferred_insert.py b/frappe/deferred_insert.py index 5c7e7a7f0d7..fd370d8ac57 100644 --- a/frappe/deferred_insert.py +++ b/frappe/deferred_insert.py @@ -13,7 +13,7 @@ def deferred_insert(doctype: str, records: list[Union[dict, "Document"]] | str): - if isinstance(records, (dict, list)): + if isinstance(records, dict | list): _records = json.dumps(records) else: _records = records diff --git a/frappe/desk/desktop.py b/frappe/desk/desktop.py index 604b108b8cb..27e422d7d51 100644 --- a/frappe/desk/desktop.py +++ b/frappe/desk/desktop.py @@ -420,7 +420,7 @@ def get_workspace_sidebar_items(): blocked_modules.append("Dummy Module") # adding None to allowed_domains to include pages without domain restriction - allowed_domains = [None] + frappe.get_active_domains() + allowed_domains = [None, *frappe.get_active_domains()] filters = { "restrict_to_domain": ["in", allowed_domains], @@ -560,11 +560,11 @@ def save_new_widget(doc, page, blocks, new_widgets): json_config = widgets and dumps(widgets, sort_keys=True, indent=4) # Error log body - log = """ - page: {} - config: {} - exception: {} - """.format(page, json_config, e) + log = f""" + page: {page} + config: {json_config} + exception: {e} + """ doc.log_error("Could not save customization", log) return False diff --git a/frappe/desk/doctype/dashboard/dashboard.py b/frappe/desk/doctype/dashboard/dashboard.py index 76b80efc447..a40cede53e6 100644 --- a/frappe/desk/doctype/dashboard/dashboard.py +++ b/frappe/desk/doctype/dashboard/dashboard.py @@ -124,9 +124,7 @@ def get_non_standard_warning_message(non_standard_docs_map): def get_html(docs, doctype): html = f"

{frappe.bold(doctype)}

" for doc in docs: - html += ''.format( - doctype=doctype, doc=doc - ) + html += f'' html += "
" return html diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py index b627fa532a3..20ac8da2601 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py @@ -55,7 +55,7 @@ def get_permission_query_conditions(user): module_condition = """`tabDashboard Chart`.`module` in ({allowed_modules}) or `tabDashboard Chart`.`module` is NULL""".format(allowed_modules=",".join(allowed_modules)) - return """ + return f""" ((`tabDashboard Chart`.`chart_type` in ('Count', 'Sum', 'Average') and {doctype_condition}) or @@ -63,11 +63,7 @@ def get_permission_query_conditions(user): and {report_condition})) and ({module_condition}) - """.format( - doctype_condition=doctype_condition, - report_condition=report_condition, - module_condition=module_condition, - ) + """ def has_permission(doc, ptype, user): @@ -248,9 +244,7 @@ def get_heatmap_chart_config(chart, filters, heatmap_year): doctype, fields=[ timestamp_field, - "{aggregate_function}({value_field})".format( - aggregate_function=aggregate_function, value_field=value_field - ), + f"{aggregate_function}({value_field})", ], filters=filters, group_by=f"date({datefield})", @@ -307,7 +301,7 @@ def get_result(data, timegrain, from_date, to_date, chart_type): result = [[date, 0] for date in dates] data_index = 0 if data: - for i, d in enumerate(result): + for d in result: count = 0 while data_index < len(data) and getdate(data[data_index][0]) <= d[0]: d[1] += data[data_index][1] diff --git a/frappe/desk/doctype/dashboard_settings/dashboard_settings.py b/frappe/desk/doctype/dashboard_settings/dashboard_settings.py index 025c5e53956..4e38cda5c9b 100644 --- a/frappe/desk/doctype/dashboard_settings/dashboard_settings.py +++ b/frappe/desk/doctype/dashboard_settings/dashboard_settings.py @@ -51,7 +51,7 @@ def save_chart_config(reset, config, chart_name): chart_config[chart_name] = {} else: config = frappe.parse_json(config) - if not chart_name in chart_config: + if chart_name not in chart_config: chart_config[chart_name] = {} chart_config[chart_name].update(config) diff --git a/frappe/desk/doctype/desktop_icon/desktop_icon.py b/frappe/desk/doctype/desktop_icon/desktop_icon.py index 22cf2006afb..7c31db0c992 100644 --- a/frappe/desk/doctype/desktop_icon/desktop_icon.py +++ b/frappe/desk/doctype/desktop_icon/desktop_icon.py @@ -221,7 +221,7 @@ def add_user_icon(_doctype, _report=None, label=None, link=None, type="link", st icon_name = new_icon.name - except frappe.UniqueValidationError as e: + except frappe.UniqueValidationError: frappe.throw(_("Desktop Icon already exists")) except Exception as e: raise e diff --git a/frappe/desk/doctype/event/event.py b/frappe/desk/doctype/event/event.py index 4e89675122d..d644cdc9bba 100644 --- a/frappe/desk/doctype/event/event.py +++ b/frappe/desk/doctype/event/event.py @@ -220,9 +220,7 @@ def delete_communication(event, reference_doctype, reference_docname): def get_permission_query_conditions(user): if not user: user = frappe.session.user - return """(`tabEvent`.`event_type`='Public' or `tabEvent`.`owner`={user})""".format( - user=frappe.db.escape(user), - ) + return f"""(`tabEvent`.`event_type`='Public' or `tabEvent`.`owner`={frappe.db.escape(user)})""" def has_permission(doc, user): diff --git a/frappe/desk/doctype/kanban_board/kanban_board.py b/frappe/desk/doctype/kanban_board/kanban_board.py index e323258fa29..dee58e14188 100644 --- a/frappe/desk/doctype/kanban_board/kanban_board.py +++ b/frappe/desk/doctype/kanban_board/kanban_board.py @@ -52,9 +52,7 @@ def get_permission_query_conditions(user): if user == "Administrator": return "" - return """(`tabKanban Board`.private=0 or `tabKanban Board`.owner={user})""".format( - user=frappe.db.escape(user) - ) + return f"""(`tabKanban Board`.private=0 or `tabKanban Board`.owner={frappe.db.escape(user)})""" def has_permission(doc, ptype, user): diff --git a/frappe/desk/doctype/list_view_settings/list_view_settings.py b/frappe/desk/doctype/list_view_settings/list_view_settings.py index ff15990ef74..29dcb2343a2 100644 --- a/frappe/desk/doctype/list_view_settings/list_view_settings.py +++ b/frappe/desk/doctype/list_view_settings/list_view_settings.py @@ -93,7 +93,7 @@ def get_default_listview_fields(doctype): fields = [f.get("fieldname") for f in doctype_json.get("fields") if f.get("in_list_view")] if meta.title_field: - if not meta.title_field.strip() in fields: + if meta.title_field.strip() not in fields: fields.append(meta.title_field.strip()) return fields diff --git a/frappe/desk/doctype/number_card/number_card.py b/frappe/desk/doctype/number_card/number_card.py index e0834134432..29f90f3da62 100644 --- a/frappe/desk/doctype/number_card/number_card.py +++ b/frappe/desk/doctype/number_card/number_card.py @@ -101,11 +101,11 @@ def get_permission_query_conditions(user=None): module_condition = """`tabNumber Card`.`module` in ({allowed_modules}) or `tabNumber Card`.`module` is NULL""".format(allowed_modules=",".join(allowed_modules)) - return """ + return f""" {doctype_condition} and {module_condition} - """.format(doctype_condition=doctype_condition, module_condition=module_condition) + """ def has_permission(doc, ptype, user): @@ -141,11 +141,7 @@ def get_result(doc, filters, to_date=None): if function == "count": fields = [f"{function}(*) as result"] else: - fields = [ - "{function}({based_on}) as result".format( - function=function, based_on=doc.aggregate_function_based_on - ) - ] + fields = [f"{function}({doc.aggregate_function_based_on}) as result"] if not filters: filters = [] diff --git a/frappe/desk/doctype/tag/tag.py b/frappe/desk/doctype/tag/tag.py index e1de59a0ad9..6b47f490b2d 100644 --- a/frappe/desk/doctype/tag/tag.py +++ b/frappe/desk/doctype/tag/tag.py @@ -87,7 +87,7 @@ def get_tags(self, dn): def add(self, dn, tag): """add a new user tag""" tl = self.get_tags(dn).split(",") - if not tag in tl: + if tag not in tl: tl.append(tag) if not frappe.db.exists("Tag", tag): frappe.get_doc({"doctype": "Tag", "name": tag}).insert(ignore_permissions=True) diff --git a/frappe/desk/form/linked_with.py b/frappe/desk/form/linked_with.py index 5dcc19117fd..7fd865d165e 100644 --- a/frappe/desk/form/linked_with.py +++ b/frappe/desk/form/linked_with.py @@ -134,7 +134,7 @@ def get_doctype_references(self, doctype): def get_document_sources(self): """Returns list of doctypes from where we access submittable documents.""" - return list(set(self.get_link_sources() + [self.root_doctype])) + return list(set([*self.get_link_sources(), self.root_doctype])) def get_link_sources(self): """limit doctype links to these doctypes.""" @@ -149,15 +149,15 @@ def get_submittable_doctypes(self) -> list[str]: return self._submittable_doctypes -def get_child_tables_of_doctypes(doctypes: list[str] = None): +def get_child_tables_of_doctypes(doctypes: list[str] | None = None): """Returns child tables by doctype.""" filters = [["fieldtype", "=", "Table"]] filters_for_docfield = filters filters_for_customfield = filters if doctypes: - filters_for_docfield = filters + [["parent", "in", tuple(doctypes)]] - filters_for_customfield = filters + [["dt", "in", tuple(doctypes)]] + filters_for_docfield = [*filters, ["parent", "in", tuple(doctypes)]] + filters_for_customfield = [*filters, ["dt", "in", tuple(doctypes)]] links = frappe.get_all( "DocField", @@ -184,7 +184,7 @@ def get_child_tables_of_doctypes(doctypes: list[str] = None): def get_references_across_doctypes( - to_doctypes: list[str] = None, limit_link_doctypes: list[str] = None + to_doctypes: list[str] | None = None, limit_link_doctypes: list[str] | None = None ) -> list: """Find doctype wise foreign key references. @@ -214,14 +214,14 @@ def get_references_across_doctypes( for k, v in references_by_dlink_fields.items(): references.setdefault(k, []).extend(v) - for doctype, links in references.items(): + for links in references.values(): for link in links: link["is_child"] = link["doctype"] in all_child_tables return references def get_references_across_doctypes_by_link_field( - to_doctypes: list[str] = None, limit_link_doctypes: list[str] = None + to_doctypes: list[str] | None = None, limit_link_doctypes: list[str] | None = None ): """Find doctype wise foreign key references based on link fields. @@ -261,7 +261,7 @@ def get_references_across_doctypes_by_link_field( def get_references_across_doctypes_by_dynamic_link_field( - to_doctypes: list[str] = None, limit_link_doctypes: list[str] = None + to_doctypes: list[str] | None = None, limit_link_doctypes: list[str] | None = None ): """Find doctype wise foreign key references based on dynamic link fields. @@ -315,7 +315,7 @@ def get_referencing_documents( reference_names: list[str], link_info: dict, get_parent_if_child_table_doc: bool = True, - parent_filters: list[list] = None, + parent_filters: list[list] | None = None, child_filters=None, allowed_parents=None, ): @@ -439,7 +439,7 @@ def get_linked_docs(doctype: str, name: str, linkinfo: dict | None = None) -> di "fields", { "in_list_view": 1, - "fieldtype": ["not in", ("Image", "HTML", "Button") + frappe.model.table_fields], + "fieldtype": ["not in", ("Image", "HTML", "Button", *frappe.model.table_fields)], }, ) ] + ["name", "modified", "docstatus"] diff --git a/frappe/desk/form/load.py b/frappe/desk/form/load.py index 1573cd212d0..db2696de517 100644 --- a/frappe/desk/form/load.py +++ b/frappe/desk/form/load.py @@ -286,9 +286,9 @@ def get_communication_data( conditions = "" if after: # find after a particular date - conditions += """ - AND C.communication_date > {} - """.format(after) + conditions += f""" + AND C.communication_date > {after} + """ if doctype == "User": conditions += """ @@ -296,23 +296,23 @@ def get_communication_data( """ # communications linked to reference_doctype - part1 = """ + part1 = f""" SELECT {fields} FROM `tabCommunication` as C WHERE C.communication_type IN ('Communication', 'Feedback', 'Automated Message') AND (C.reference_doctype = %(doctype)s AND C.reference_name = %(name)s) {conditions} - """.format(fields=fields, conditions=conditions) + """ # communications linked in Timeline Links - part2 = """ + part2 = f""" SELECT {fields} FROM `tabCommunication` as C INNER JOIN `tabCommunication Link` ON C.name=`tabCommunication Link`.parent WHERE C.communication_type IN ('Communication', 'Feedback', 'Automated Message') AND `tabCommunication Link`.link_doctype = %(doctype)s AND `tabCommunication Link`.link_name = %(name)s {conditions} - """.format(fields=fields, conditions=conditions) + """ return frappe.db.sql( """ diff --git a/frappe/desk/form/utils.py b/frappe/desk/form/utils.py index 353dea305f4..b93ebdb5bcf 100644 --- a/frappe/desk/form/utils.py +++ b/frappe/desk/form/utils.py @@ -105,6 +105,4 @@ def get_next(doctype, value, prev, filters=None, sort_order="desc", sort_field=" def get_pdf_link(doctype, docname, print_format="Standard", no_letterhead=0): - return "/api/method/frappe.utils.print_format.download_pdf?doctype={doctype}&name={docname}&format={print_format}&no_letterhead={no_letterhead}".format( - doctype=doctype, docname=docname, print_format=print_format, no_letterhead=no_letterhead - ) + return f"/api/method/frappe.utils.print_format.download_pdf?doctype={doctype}&name={docname}&format={print_format}&no_letterhead={no_letterhead}" diff --git a/frappe/desk/leaderboard.py b/frappe/desk/leaderboard.py index 91aa2084cca..4800a7d945b 100644 --- a/frappe/desk/leaderboard.py +++ b/frappe/desk/leaderboard.py @@ -45,8 +45,6 @@ def get_energy_point_leaderboard(date_range, company=None, field=None, limit=Non for user in energy_point_users: user_id = user["name"] user["name"] = get_fullname(user["name"]) - user["formatted_name"] = '{}'.format( - user_id, get_fullname(user_id) - ) + user["formatted_name"] = f'{get_fullname(user_id)}' return energy_point_users diff --git a/frappe/desk/notifications.py b/frappe/desk/notifications.py index d29b31431d7..afdc64ac463 100644 --- a/frappe/desk/notifications.py +++ b/frappe/desk/notifications.py @@ -283,7 +283,7 @@ def get_open_count(doctype, name, items=None): try: external_links_data_for_d = get_external_links(d, name, links) out["external_links_found"].append(external_links_data_for_d) - except Exception as e: + except Exception: out["external_links_found"].append({"doctype": d, "open_count": 0, "count": 0}) else: external_links_data_for_d = get_external_links(d, name, links) diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py index be6bb718cc8..d8da0f70ff0 100644 --- a/frappe/desk/query_report.py +++ b/frappe/desk/query_report.py @@ -124,7 +124,7 @@ def normalize_result(result, columns): # Converts to list of dicts from list of lists/tuples data = [] column_names = [column["fieldname"] for column in columns] - if result and isinstance(result[0], (list, tuple)): + if result and isinstance(result[0], list | tuple): for row in result: row_obj = {} for idx, column_name in enumerate(column_names): @@ -291,7 +291,7 @@ def get_report_data(doc, data): report_data = get_report_data(doc, data) except Exception as e: doc.log_error("Prepared report render failed") - frappe.msgprint(_("Prepared report render failed") + f": {str(e)}") + frappe.msgprint(_("Prepared report render failed") + f": {e!s}") doc = None return report_data | {"prepared_report": True, "doc": doc} @@ -507,7 +507,7 @@ def get_data_for_custom_field(doctype, field, names=None): filters = {} if names: - if isinstance(names, (str, bytearray)): + if isinstance(names, str | bytearray): names = frappe.json.loads(names) filters.update({"name": ["in", names]}) @@ -659,7 +659,7 @@ def has_match( cell_value = None if isinstance(row, dict): cell_value = row.get(idx) - elif isinstance(row, (list, tuple)): + elif isinstance(row, list | tuple): cell_value = row[idx] if ( @@ -691,10 +691,10 @@ def get_linked_doctypes(columns, data): columns_dict = get_columns_dict(columns) - for idx, col in enumerate(columns): + for idx in range(len(columns)): df = columns_dict[idx] if df.get("fieldtype") == "Link": - if data and isinstance(data[0], (list, tuple)): + if data and isinstance(data[0], list | tuple): linked_doctypes[df["options"]] = idx else: # dict @@ -705,7 +705,7 @@ def get_linked_doctypes(columns, data): for row in data: if row: if len(row) != len(columns_with_value): - if isinstance(row, (list, tuple)): + if isinstance(row, list | tuple): row = enumerate(row) elif isinstance(row, dict): row = row.items() diff --git a/frappe/desk/reportview.py b/frappe/desk/reportview.py index 6012e33bd4f..0e394f534e9 100644 --- a/frappe/desk/reportview.py +++ b/frappe/desk/reportview.py @@ -199,7 +199,7 @@ def get_meta_and_docfield(fieldname, data): def update_wildcard_field_param(data): if (isinstance(data.fields, str) and data.fields == "*") or ( - isinstance(data.fields, (list, tuple)) and len(data.fields) == 1 and data.fields[0] == "*" + isinstance(data.fields, list | tuple) and len(data.fields) == 1 and data.fields[0] == "*" ): data.fields = get_permitted_fields(data.doctype, parenttype=data.parenttype) return True @@ -360,8 +360,8 @@ def export_query(): if add_totals_row: ret = append_totals_row(ret) - data = [[_("Sr")] + get_labels(db_query.fields, doctype)] - data.extend([i + 1] + list(row) for i, row in enumerate(ret)) + data = [[_("Sr"), *get_labels(db_query.fields, doctype)]] + data.extend([i + 1, *list(row)] for i, row in enumerate(ret)) data = handle_duration_fieldtype_values(doctype, data, db_query.fields) if file_format_type == "CSV": @@ -390,10 +390,10 @@ def append_totals_row(data): for row in data: for i in range(len(row)): - if isinstance(row[i], (float, int)): + if isinstance(row[i], float | int): totals[i] = (totals[i] or 0) + row[i] - if not isinstance(totals[0], (int, float)): + if not isinstance(totals[0], int | float): totals[0] = "Total" data.append(totals) @@ -543,7 +543,7 @@ def get_stats(stats, doctype, filters=None): tag_count = frappe.get_list( doctype, fields=[column, "count(*)"], - filters=filters + [[column, "!=", ""]], + filters=[*filters, [column, "!=", ""]], group_by=column, as_list=True, distinct=1, @@ -554,7 +554,7 @@ def get_stats(stats, doctype, filters=None): no_tag_count = frappe.get_list( doctype, fields=[column, "count(*)"], - filters=filters + [[column, "in", ("", ",")]], + filters=[*filters, [column, "in", ("", ",")]], as_list=True, group_by=column, order_by=column, @@ -568,7 +568,7 @@ def get_stats(stats, doctype, filters=None): except frappe.db.SQLError: pass - except frappe.db.InternalError as e: + except frappe.db.InternalError: # raised when _user_tags column is added on the fly pass @@ -586,14 +586,14 @@ def get_filter_dashboard_data(stats, doctype, filters=None): columns = frappe.db.get_table_columns(doctype) for tag in tags: - if not tag["name"] in columns: + if tag["name"] not in columns: continue tagcount = [] if tag["type"] not in ["Date", "Datetime"]: tagcount = frappe.get_list( doctype, fields=[tag["name"], "count(*)"], - filters=filters + ["ifnull(`%s`,'')!=''" % tag["name"]], + filters=[*filters, "ifnull(`%s`,'')!=''" % tag["name"]], group_by=tag["name"], as_list=True, ) @@ -615,7 +615,7 @@ def get_filter_dashboard_data(stats, doctype, filters=None): frappe.get_list( doctype, fields=[tag["name"], "count(*)"], - filters=filters + ["({0} = '' or {0} is null)".format(tag["name"])], + filters=[*filters, "({0} = '' or {0} is null)".format(tag["name"])], as_list=True, )[0][1], ] @@ -673,7 +673,7 @@ def get_filters_cond(doctype, filters, conditions, ignore_permissions=None, with for f in filters: if isinstance(f[1], str) and f[1][0] == "!": flt.append([doctype, f[0], "!=", f[1][1:]]) - elif isinstance(f[1], (list, tuple)) and f[1][0].lower() in ( + elif isinstance(f[1], list | tuple) and f[1][0].lower() in ( "=", ">", "<", diff --git a/frappe/desk/search.py b/frappe/desk/search.py index d4d81399088..1523e9326b2 100644 --- a/frappe/desk/search.py +++ b/frappe/desk/search.py @@ -63,7 +63,7 @@ def search_widget( doctype: str, txt: str, query: str | None = None, - searchfield: str = None, + searchfield: str | None = None, start: int = 0, page_length: int = 10, filters: str | None | dict | list = None, diff --git a/frappe/email/__init__.py b/frappe/email/__init__.py index 434592914ee..44165351411 100644 --- a/frappe/email/__init__.py +++ b/frappe/email/__init__.py @@ -19,7 +19,7 @@ def get_contact_list(txt, page_length=20) -> list[dict]: fields = ["first_name", "middle_name", "last_name", "company_name"] contacts = frappe.get_list( "Contact", - fields=fields + ["`tabContact Email`.email_id"], + fields=[*fields, "`tabContact Email`.email_id"], filters=[ ["Contact Email", "email_id", "is", "set"], ], diff --git a/frappe/email/doctype/auto_email_report/auto_email_report.py b/frappe/email/doctype/auto_email_report/auto_email_report.py index 81495f71a8e..48babce7755 100644 --- a/frappe/email/doctype/auto_email_report/auto_email_report.py +++ b/frappe/email/doctype/auto_email_report/auto_email_report.py @@ -321,7 +321,7 @@ def send_daily(): continue try: auto_email_report.send() - except Exception as e: + except Exception: auto_email_report.log_error(f"Failed to send {auto_email_report.name} Auto Email Report") diff --git a/frappe/email/doctype/email_account/email_account.py b/frappe/email/doctype/email_account/email_account.py index 03208027eb6..5e789461666 100755 --- a/frappe/email/doctype/email_account/email_account.py +++ b/frappe/email/doctype/email_account/email_account.py @@ -4,7 +4,6 @@ import email.utils import functools import imaplib -import socket import time from datetime import datetime, timedelta from poplib import error_proto @@ -35,14 +34,14 @@ def wrapper_cache_email_account(*args, **kwargs): setattr(frappe.local, cache_name, {}) cached_accounts = getattr(frappe.local, cache_name) - match_by = list(kwargs.values()) + ["default"] + match_by = [*list(kwargs.values()), "default"] matched_accounts = list(filter(None, [cached_accounts.get(key) for key in match_by])) if matched_accounts: return matched_accounts[0] matched_accounts = func(*args, **kwargs) cached_accounts.update(matched_accounts or {}) - return matched_accounts and list(matched_accounts.values())[0] + return matched_accounts and next(iter(matched_accounts.values())) return wrapper_cache_email_account @@ -96,7 +95,15 @@ class EmailAccount(Document): password: DF.Password | None send_notification_to: DF.SmallText | None send_unsubscribe_message: DF.Check - service: DF.Literal["", "GMail", "Sendgrid", "SparkPost", "Yahoo Mail", "Outlook.com", "Yandex.Mail"] + service: DF.Literal[ + "", + "GMail", + "Sendgrid", + "SparkPost", + "Yahoo Mail", + "Outlook.com", + "Yandex.Mail", + ] signature: DF.TextEditor | None smtp_port: DF.Data | None smtp_server: DF.Data | None @@ -189,20 +196,27 @@ def before_save(self): self.default_incoming = False messages.append( _("{} has been disabled. It can only be enabled if {} is checked.").format( - frappe.bold(_("Default Incoming")), frappe.bold(_("Enable Incoming")) + frappe.bold(_("Default Incoming")), + frappe.bold(_("Enable Incoming")), ) ) if not self.enable_outgoing and self.default_outgoing: self.default_outgoing = False messages.append( _("{} has been disabled. It can only be enabled if {} is checked.").format( - frappe.bold(_("Default Outgoing")), frappe.bold(_("Enable Outgoing")) + frappe.bold(_("Default Outgoing")), + frappe.bold(_("Enable Outgoing")), ) ) if messages: if len(messages) == 1: (as_list, messages) = (0, messages[0]) - frappe.msgprint(messages, as_list=as_list, indicator="orange", title=_("Defaults Updated")) + frappe.msgprint( + messages, + as_list=as_list, + indicator="orange", + title=_("Defaults Updated"), + ) def on_update(self): """Check there is only one default of each type.""" @@ -282,7 +296,11 @@ def check_email_server_connection(self, email_server, in_receive): "loginfailed", ] - other_error_codes = ["err[auth]", "errtemporaryerror", "loginviayourwebbrowser"] + other_error_codes = [ + "err[auth]", + "errtemporaryerror", + "loginviayourwebbrowser", + ] all_error_codes = auth_error_codes + other_error_codes @@ -561,7 +579,15 @@ def process_mail(messages, append_to=None): seen_status = messages.get("seen_status", {}).get(uid) if self.email_sync_option != "UNSEEN" or seen_status != "SEEN": # only append the emails with status != 'SEEN' if sync option is set to 'UNSEEN' - mails.append(InboundMail(message, self, frappe.safe_decode(uid), seen_status, append_to)) + mails.append( + InboundMail( + message, + self, + frappe.safe_decode(uid), + seen_status, + append_to, + ) + ) if not self.enable_incoming: return [] @@ -616,7 +642,9 @@ def handle_bad_emails(self, uid, raw, reason): def send_auto_reply(self, communication, email): """Send auto reply if set.""" - from frappe.core.doctype.communication.email import set_incoming_outgoing_accounts + from frappe.core.doctype.communication.email import ( + set_incoming_outgoing_accounts, + ) if self.enable_auto_reply: set_incoming_outgoing_accounts(communication) @@ -669,7 +697,10 @@ def check_automatic_linking_email_account(self): if not self.enable_incoming: frappe.throw(_("Automatic Linking can be activated only if Incoming is enabled.")) - if frappe.db.exists("Email Account", {"enable_automatic_linking": 1, "name": ("!=", self.name)}): + if frappe.db.exists( + "Email Account", + {"enable_automatic_linking": 1, "name": ("!=", self.name)}, + ): frappe.throw(_("Automatic Linking can be activated only for one Email Account.")) def append_email_to_sent_folder(self, message): @@ -715,7 +746,9 @@ def notify_unreplied(): """Sends email notifications if there are unreplied Communications and `notify_if_unreplied` is set as true.""" for email_account in frappe.get_all( - "Email Account", "name", filters={"enable_incoming": 1, "notify_if_unreplied": 1} + "Email Account", + "name", + filters={"enable_incoming": 1, "notify_if_unreplied": 1}, ): email_account = frappe.get_doc("Email Account", email_account.name) @@ -772,7 +805,12 @@ def pull(now=False): doctype = frappe.qb.DocType("Email Account") email_accounts = ( frappe.qb.from_(doctype) - .select(doctype.name, doctype.auth_method, doctype.connected_app, doctype.connected_user) + .select( + doctype.name, + doctype.auth_method, + doctype.connected_app, + doctype.connected_user, + ) .where(doctype.enable_incoming == 1) .where(doctype.awaiting_password == 0) .run(as_dict=1) @@ -810,10 +848,9 @@ def pull_from_email_account(email_account): def get_max_email_uid(email_account): - # get maximum uid of emails - max_uid = 1 + """get maximum uid of emails""" - result = frappe.get_all( + if result := frappe.get_all( "Communication", filters={ "communication_medium": "Email", @@ -821,12 +858,9 @@ def get_max_email_uid(email_account): "email_account": email_account, }, fields=["max(uid) as uid"], - ) - - if not result: - return 1 - else: + ): return cint(result[0].get("uid", 0)) + 1 + return 1 def setup_user_email_inbox(email_account, awaiting_password, email_id, enable_outgoing, used_oauth): @@ -858,7 +892,11 @@ def add_user_email(user): # check if inbox is alreay configured user_inbox = ( - frappe.db.get_value("User Email", {"email_account": email_account, "parent": user_name}, ["name"]) + frappe.db.get_value( + "User Email", + {"email_account": email_account, "parent": user_name}, + ["name"], + ) or None ) @@ -885,7 +923,11 @@ def remove_user_email_inbox(email_account): if not email_account: return - users = frappe.get_all("User Email", filters={"email_account": email_account}, fields=["parent as name"]) + users = frappe.get_all( + "User Email", + filters={"email_account": email_account}, + fields=["parent as name"], + ) for user in users: doc = frappe.get_doc("User", user.get("name")) diff --git a/frappe/email/doctype/email_account/test_email_account.py b/frappe/email/doctype/email_account/test_email_account.py index 16364104fa7..4728881d7e8 100644 --- a/frappe/email/doctype/email_account/test_email_account.py +++ b/frappe/email/doctype/email_account/test_email_account.py @@ -413,10 +413,13 @@ def test_append_to_with_imap_folders(self): @patch("frappe.email.receive.EmailServer.select_imap_folder", return_value=True) @patch("frappe.email.receive.EmailServer.logout", side_effect=lambda: None) def mocked_get_inbound_mails( - email_account, messages={}, mocked_logout=None, mocked_select_imap_folder=None + email_account, messages=None, mocked_logout=None, mocked_select_imap_folder=None ): from frappe.email.receive import EmailServer + if messages is None: + messages = {} + def get_mocked_messages(**kwargs): return messages.get(kwargs["folder"], {}) @@ -427,7 +430,12 @@ def get_mocked_messages(**kwargs): @patch("frappe.email.receive.EmailServer.select_imap_folder", return_value=True) @patch("frappe.email.receive.EmailServer.logout", side_effect=lambda: None) - def mocked_email_receive(email_account, messages={}, mocked_logout=None, mocked_select_imap_folder=None): + def mocked_email_receive( + email_account, messages=None, mocked_logout=None, mocked_select_imap_folder=None + ): + if messages is None: + messages = {} + def get_mocked_messages(**kwargs): return messages.get(kwargs["folder"], {}) diff --git a/frappe/email/doctype/email_group/email_group.py b/frappe/email/doctype/email_group/email_group.py index 03b22f6092c..0a258d7ea00 100755 --- a/frappe/email/doctype/email_group/email_group.py +++ b/frappe/email/doctype/email_group/email_group.py @@ -37,11 +37,11 @@ def onload(self): def import_from(self, doctype): """Extract Email Addresses from given doctype and add them to the current list""" meta = frappe.get_meta(doctype) - email_field = [ + email_field = next( d.fieldname for d in meta.fields if d.fieldtype in ("Data", "Small Text", "Text", "Code") and d.options == "Email" - ][0] + ) unsubscribed_field = "unsubscribed" if meta.get_field("unsubscribed") else None added = 0 @@ -105,7 +105,7 @@ def import_from(name, doctype): @frappe.whitelist() def add_subscribers(name, email_list): - if not isinstance(email_list, (list, tuple)): + if not isinstance(email_list, list | tuple): email_list = email_list.replace(",", "\n").split("\n") template = frappe.db.get_value("Email Group", name, "welcome_email_template") diff --git a/frappe/email/doctype/email_queue/email_queue.py b/frappe/email/doctype/email_queue/email_queue.py index 3ac460e2560..2778ec9012d 100644 --- a/frappe/email/doctype/email_queue/email_queue.py +++ b/frappe/email/doctype/email_queue/email_queue.py @@ -725,7 +725,7 @@ def send_emails(self, queue_data, final_recipients): # This re-uses smtp server instance to minimize the cost of new session creation smtp_server_instance = None for r in final_recipients: - recipients = list(set([r] + self.final_cc() + self.bcc)) + recipients = list(set([r, *self.final_cc(), *self.bcc])) q = EmailQueue.new({**queue_data, **{"recipients": recipients}}, ignore_permissions=True) if not smtp_server_instance: email_account = q.get_email_account(raise_error=True) diff --git a/frappe/email/receive.py b/frappe/email/receive.py index 561cc4c24c3..2fdd499b24f 100644 --- a/frappe/email/receive.py +++ b/frappe/email/receive.py @@ -668,7 +668,7 @@ def replace_inline_images(self, attachments): # replace inline images content = self.content for file in attachments: - if file.name in self.cid_map and self.cid_map[file.name]: + if self.cid_map.get(file.name): content = content.replace(f"cid:{self.cid_map[file.name]}", file.unique_url) return content diff --git a/frappe/frappeclient.py b/frappe/frappeclient.py index feec802fc1c..7470c487b2d 100644 --- a/frappe/frappeclient.py +++ b/frappe/frappeclient.py @@ -356,7 +356,7 @@ def post_request(self, data): def preprocess(self, params): """convert dicts, lists to json""" for key, value in params.items(): - if isinstance(value, (dict, list)): + if isinstance(value, dict | list): params[key] = json.dumps(value) return params diff --git a/frappe/gettext/extractors/navbar.py b/frappe/gettext/extractors/navbar.py new file mode 100644 index 00000000000..924d3a44030 --- /dev/null +++ b/frappe/gettext/extractors/navbar.py @@ -0,0 +1,51 @@ +import importlib +from pathlib import Path + +from frappe.utils import get_bench_path + + +def extract(fileobj, *args, **kwargs): + """Extract standard navbar and help items from a python file. + + :param fileobj: file-like object to extract messages from. Should be a + python file containing two global variables `standard_navbar_items` and + `standard_help_items` which are lists of dicts. + """ + module = get_module(fileobj.name) + + if hasattr(module, "standard_navbar_items"): + standard_navbar_items = module.standard_navbar_items + for nav_item in standard_navbar_items: + if label := nav_item.get("item_label"): + item_type = nav_item.get("item_type") + yield ( + None, + "_", + label, + [ + "Label of a standard navbar item", + f"Type: {item_type}", + ], + ) + + if hasattr(module, "standard_help_items"): + standard_help_items = module.standard_help_items + for help_item in standard_help_items: + if label := help_item.get("item_label"): + item_type = nav_item.get("item_type") + yield ( + None, + "_", + label, + [ + "Label of a standard help item", + f"Type: {item_type}", + ], + ) + + +def get_module(path): + _path = Path(path) + rel_path = _path.relative_to(get_bench_path()) + import_path = ".".join(rel_path.parts[2:]).rstrip(".py") + return importlib.import_module(import_path) diff --git a/frappe/handler.py b/frappe/handler.py index 91c4f3adf6e..f468dd7a475 100644 --- a/frappe/handler.py +++ b/frappe/handler.py @@ -243,7 +243,7 @@ def upload_file(): ).save(ignore_permissions=ignore_permissions) -def check_write_permission(doctype: str = None, name: str = None): +def check_write_permission(doctype: str | None = None, name: str | None = None): check_doctype = doctype and not name if doctype and name: try: diff --git a/frappe/installer.py b/frappe/installer.py index 24e4a77f45b..52c04cbe462 100644 --- a/frappe/installer.py +++ b/frappe/installer.py @@ -214,7 +214,7 @@ def fetch_details_from_tag(_tag: str) -> tuple[str, str, str]: try: repo, tag = app_tag except ValueError: - repo, tag = app_tag + [None] + repo, tag = [*app_tag, None] try: org, repo = org_repo @@ -773,8 +773,6 @@ def is_downgrade(sql_file_path, verbose=False): from semantic_version import Version - head = "INSERT INTO `tabInstalled Application` VALUES" - with open(sql_file_path) as f: header = f.readline() # Example first line: @@ -815,7 +813,7 @@ def partial_restore(sql_file_path, verbose=False): " partial restore operation for PostreSQL databases", fg="yellow", ) - warnings.warn(warn) + warnings.warn(warn, stacklevel=1) import_db_from_sql(source_sql=sql_file, verbose=verbose) diff --git a/frappe/integrations/doctype/dropbox_settings/dropbox_settings.py b/frappe/integrations/doctype/dropbox_settings/dropbox_settings.py index 39f208f3c12..dd6ae7f9ae9 100644 --- a/frappe/integrations/doctype/dropbox_settings/dropbox_settings.py +++ b/frappe/integrations/doctype/dropbox_settings/dropbox_settings.py @@ -106,7 +106,7 @@ def take_backup_to_dropbox(retry_count=0, upload_db_backup=True): if isinstance(error_log, str): error_message = error_log + "\n" + frappe.get_traceback() else: - file_and_error = [" - ".join(f) for f in zip(did_not_upload, error_log)] + file_and_error = [" - ".join(f) for f in zip(did_not_upload, error_log, strict=False)] error_message = "\n".join(file_and_error) + "\n" + frappe.get_traceback() send_email(False, "Dropbox", "Dropbox Settings", "send_notifications_to", error_message) diff --git a/frappe/integrations/doctype/ldap_settings/ldap_settings.py b/frappe/integrations/doctype/ldap_settings/ldap_settings.py index 1a6c7bf0a27..23b69643a47 100644 --- a/frappe/integrations/doctype/ldap_settings/ldap_settings.py +++ b/frappe/integrations/doctype/ldap_settings/ldap_settings.py @@ -183,7 +183,7 @@ def update_user_fields(cls, user: "User", user_data: dict): setattr(user, key, value) user.save(ignore_permissions=True) - def sync_roles(self, user: "User", additional_groups: list = None): + def sync_roles(self, user: "User", additional_groups: list | None = None): current_roles = {d.role for d in user.get("roles")} if self.default_user_type == "System User": needed_roles = {self.default_role} @@ -203,7 +203,7 @@ def sync_roles(self, user: "User", additional_groups: list = None): user.remove_roles(*roles_to_remove) - def create_or_update_user(self, user_data: dict, groups: list = None): + def create_or_update_user(self, user_data: dict, groups: list | None = None): user: "User" = None role: str = None diff --git a/frappe/integrations/doctype/ldap_settings/test_ldap_settings.py b/frappe/integrations/doctype/ldap_settings/test_ldap_settings.py index ee0b89df814..46a63698df3 100644 --- a/frappe/integrations/doctype/ldap_settings/test_ldap_settings.py +++ b/frappe/integrations/doctype/ldap_settings/test_ldap_settings.py @@ -4,6 +4,7 @@ import functools import os import ssl +import typing from unittest import TestCase, mock import ldap3 @@ -18,7 +19,7 @@ class LDAP_TestCase: TEST_LDAP_SERVER = None # must match the 'LDAP Settings' field option TEST_LDAP_SEARCH_STRING = None LDAP_USERNAME_FIELD = None - DOCUMENT_GROUP_MAPPINGS = [] + DOCUMENT_GROUP_MAPPINGS: typing.ClassVar[list] = [] LDAP_SCHEMA = None LDAP_LDIF_JSON = None TEST_VALUES_LDAP_COMPLEX_SEARCH_STRING = None @@ -489,7 +490,7 @@ def test_create_or_update_user(self): @mock_ldap_connection def test_get_ldap_attributes(self): method_return = self.test_class.get_ldap_attributes() - self.assertTrue(type(method_return) is list) + self.assertTrue(isinstance(method_return, list)) @mock_ldap_connection def test_fetch_ldap_groups(self): @@ -599,14 +600,14 @@ def test_convert_ldap_entry_to_dict(self): test_ldap_entry = self.connection.entries[0] method_return = self.test_class.convert_ldap_entry_to_dict(test_ldap_entry) - self.assertTrue(type(method_return) is dict) # must be dict + self.assertTrue(isinstance(method_return, dict)) # must be dict self.assertTrue(len(method_return) == 6) # there are 6 fields in mock_ldap for use class Test_OpenLDAP(LDAP_TestCase, TestCase): TEST_LDAP_SERVER = "OpenLDAP" TEST_LDAP_SEARCH_STRING = "(uid={0})" - DOCUMENT_GROUP_MAPPINGS = [ + DOCUMENT_GROUP_MAPPINGS: typing.ClassVar[list] = [ { "doctype": "LDAP Group Mapping", "ldap_group": "Administrators", @@ -619,7 +620,7 @@ class Test_OpenLDAP(LDAP_TestCase, TestCase): LDAP_SCHEMA = OFFLINE_SLAPD_2_4 LDAP_LDIF_JSON = "test_data_ldif_openldap.json" - TEST_VALUES_LDAP_COMPLEX_SEARCH_STRING = [ + TEST_VALUES_LDAP_COMPLEX_SEARCH_STRING: typing.ClassVar[list] = [ "(uid={0})", "(&(objectclass=posixaccount)(uid={0}))", "(&(description=*ACCESS:test1*)(uid={0}))", # OpenLDAP has no member of group, use description to filter posix.user has equivilent of AD 'memberOf' @@ -630,7 +631,7 @@ class Test_OpenLDAP(LDAP_TestCase, TestCase): class Test_ActiveDirectory(LDAP_TestCase, TestCase): TEST_LDAP_SERVER = "Active Directory" TEST_LDAP_SEARCH_STRING = "(samaccountname={0})" - DOCUMENT_GROUP_MAPPINGS = [ + DOCUMENT_GROUP_MAPPINGS: typing.ClassVar[list] = [ { "doctype": "LDAP Group Mapping", "ldap_group": "Domain Administrators", @@ -647,7 +648,7 @@ class Test_ActiveDirectory(LDAP_TestCase, TestCase): LDAP_SCHEMA = OFFLINE_AD_2012_R2 LDAP_LDIF_JSON = "test_data_ldif_activedirectory.json" - TEST_VALUES_LDAP_COMPLEX_SEARCH_STRING = [ + TEST_VALUES_LDAP_COMPLEX_SEARCH_STRING: typing.ClassVar[dict] = [ "(samaccountname={0})", "(&(objectclass=user)(samaccountname={0}))", "(&(description=*ACCESS:test1*)(samaccountname={0}))", # OpenLDAP has no member of group, use description to filter posix.user has equivilent of AD 'memberOf' diff --git a/frappe/integrations/doctype/social_login_key/test_social_login_key.py b/frappe/integrations/doctype/social_login_key/test_social_login_key.py index 961cd41b657..2a32ab8aa1d 100644 --- a/frappe/integrations/doctype/social_login_key/test_social_login_key.py +++ b/frappe/integrations/doctype/social_login_key/test_social_login_key.py @@ -90,7 +90,7 @@ def test_force_enabled_signups(self): def make_social_login_key(**kwargs): kwargs["doctype"] = "Social Login Key" - if not "provider_name" in kwargs: + if "provider_name" not in kwargs: kwargs["provider_name"] = "Test OAuth2 Provider" return frappe.get_doc(kwargs) diff --git a/frappe/integrations/frappe_providers/frappecloud.py b/frappe/integrations/frappe_providers/frappecloud.py index bae811d41d6..f2760044270 100644 --- a/frappe/integrations/frappe_providers/frappecloud.py +++ b/frappe/integrations/frappe_providers/frappecloud.py @@ -12,11 +12,7 @@ def frappecloud_migrator(local_site): request = requests.get(request_url) if request.status_code / 100 != 2: - print( - "Request exitted with Status Code: {}\nPayload: {}".format( - request.status_code, html2text(request.text) - ) - ) + print(f"Request exited with Status Code: {request.status_code}\nPayload: {html2text(request.text)}") click.secho( "Some errors occurred while recovering the migration script. Please contact us @ Frappe Cloud if this issue persists", fg="yellow", diff --git a/frappe/integrations/google_oauth.py b/frappe/integrations/google_oauth.py index 2070f292271..92943b17276 100644 --- a/frappe/integrations/google_oauth.py +++ b/frappe/integrations/google_oauth.py @@ -39,7 +39,7 @@ def __init__(self, domain: str, validate: bool = True): self.domain = domain.lower() self.scopes = ( " ".join(_SCOPES[self.domain]) - if isinstance(_SCOPES[self.domain], (list, tuple)) + if isinstance(_SCOPES[self.domain], list | tuple) else _SCOPES[self.domain] ) @@ -165,7 +165,7 @@ def is_valid_access_token(access_token: str) -> bool: @frappe.whitelist(methods=["GET"]) -def callback(state: str, code: str = None, error: str = None) -> None: +def callback(state: str, code: str | None = None, error: str | None = None) -> None: """Common callback for google integrations. Invokes functions using `frappe.get_attr` and also adds required (keyworded) arguments along with committing and redirecting us back to frappe site.""" diff --git a/frappe/integrations/offsite_backup_utils.py b/frappe/integrations/offsite_backup_utils.py index 46a552224c2..b965b460b81 100644 --- a/frappe/integrations/offsite_backup_utils.py +++ b/frappe/integrations/offsite_backup_utils.py @@ -29,11 +29,11 @@ def send_email(success, service_name, doctype, email_field, error_status=None): ) else: subject = "[Warning] Backup Upload Failed" - message = """ + message = f"""

Backup Upload Failed!

-

Oops, your automated backup to {} failed.

-

Error message: {}

-

Please contact your system manager for more information.

""".format(service_name, error_status) +

Oops, your automated backup to {service_name} failed.

+

Error message: {error_status}

+

Please contact your system manager for more information.

""" frappe.sendmail(recipients=recipients, subject=subject, message=message) diff --git a/frappe/integrations/utils.py b/frappe/integrations/utils.py index 3293a432118..b15f2d8b3f5 100644 --- a/frappe/integrations/utils.py +++ b/frappe/integrations/utils.py @@ -107,5 +107,5 @@ def get_json(obj): def json_handler(obj): - if isinstance(obj, (datetime.date, datetime.timedelta, datetime.datetime)): + if isinstance(obj, datetime.date | datetime.timedelta | datetime.datetime): return str(obj) diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py index 4d7e6e3744a..862bfb6e69a 100644 --- a/frappe/model/base_document.py +++ b/frappe/model/base_document.py @@ -216,7 +216,7 @@ def get(self, key, filters=None, limit=None, default=None): value = self.__dict__.get(key, default) - if limit and isinstance(value, (list, tuple)) and len(value) > limit: + if limit and isinstance(value, list | tuple) and len(value) > limit: value = value[:limit] return value @@ -395,7 +395,7 @@ def get_valid_dict( value = None if convert_dates_to_str and isinstance( - value, (datetime.datetime, datetime.date, datetime.time, datetime.timedelta) + value, datetime.datetime | datetime.date | datetime.time | datetime.timedelta ): value = str(value) @@ -608,7 +608,7 @@ def db_update(self): SET {values} WHERE `name`=%s""".format( doctype=self.doctype, values=", ".join("`" + c + "`=%s" for c in columns) ), - list(d.values()) + [name], + [*list(d.values()), name], ) except Exception as e: if frappe.db.is_unique_key_violation(e): @@ -1177,7 +1177,7 @@ def get_formatted( if not doc: doc = getattr(self, "parent_doc", None) or self - if (absolute_value or doc.get("absolute_value")) and isinstance(val, (int, float)): + if (absolute_value or doc.get("absolute_value")) and isinstance(val, int | float): val = abs(self.get(fieldname)) return format_value(val, df=df, doc=doc, currency=currency, format=format) @@ -1284,7 +1284,7 @@ def _filter(data, filters, limit=None): for f in filters: fval = filters[f] - if not isinstance(fval, (tuple, list)): + if not isinstance(fval, tuple | list): if fval is True: fval = ("not None", fval) elif fval is False: diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py index 8b5b0d44358..5f333a5f5e3 100644 --- a/frappe/model/db_query.py +++ b/frappe/model/db_query.py @@ -223,15 +223,12 @@ def build_and_run(self): if frappe.db.db_type == "postgres" and args.order_by and args.group_by: args = self.prepare_select_args(args) - query = ( - """select %(fields)s - from %(tables)s - %(conditions)s - %(group_by)s - %(order_by)s - %(limit)s""" - % args - ) + query = """select {fields} + from {tables} + {conditions} + {group_by} + {order_by} + {limit}""".format(**args) return frappe.db.sql( query, @@ -1265,7 +1262,7 @@ def get_between_date_filter(value, df=None): from_date = frappe.utils.nowdate() to_date = frappe.utils.nowdate() - if value and isinstance(value, (list, tuple)): + if value and isinstance(value, list | tuple): if len(value) >= 1: from_date = value[0] if len(value) >= 2: diff --git a/frappe/model/delete_doc.py b/frappe/model/delete_doc.py index 5967e9c5c96..c6cce21a06e 100644 --- a/frappe/model/delete_doc.py +++ b/frappe/model/delete_doc.py @@ -354,8 +354,8 @@ def check_if_doc_is_dynamically_linked(doc, method="Delete"): def raise_link_exists_exception(doc, reference_doctype, reference_docname, row=""): - doc_link = '{1}'.format(doc.doctype, doc.name) - reference_link = '{1}'.format(reference_doctype, reference_docname) + doc_link = f'{doc.name}' + reference_link = f'{reference_docname}' # hack to display Single doctype only once in message if reference_doctype == reference_docname: @@ -407,12 +407,12 @@ def clear_references( reference_name_field="reference_name", ): frappe.db.sql( - """update - `tab{0}` + f"""update + `tab{doctype}` set - {1}=NULL, {2}=NULL + {reference_doctype_field}=NULL, {reference_name_field}=NULL where - {1}=%s and {2}=%s""".format(doctype, reference_doctype_field, reference_name_field), # nosec + {reference_doctype_field}=%s and {reference_name_field}=%s""", # nosec (reference_doctype, reference_name), ) diff --git a/frappe/model/document.py b/frappe/model/document.py index ad60fa3f084..71751d325f2 100644 --- a/frappe/model/document.py +++ b/frappe/model/document.py @@ -157,7 +157,7 @@ def load_from_db(self): else: get_value_kwargs = {"for_update": self.flags.for_update, "as_dict": True} - if not isinstance(self.name, (dict, list)): + if not isinstance(self.name, dict | list): get_value_kwargs["order_by"] = None d = frappe.db.get_value( @@ -895,7 +895,7 @@ def _validate_mandatory(self): if not missing: return - for fieldname, msg in missing: + for idx, msg in missing: # noqa: B007 msgprint(msg) if frappe.flags.print_messages: @@ -1264,7 +1264,7 @@ def save_version(self): doc_to_compare = frappe.get_doc(self.doctype, amended_from) version = frappe.new_doc("Version") - if is_useful_diff := version.update_version_info(doc_to_compare, self): + if version.update_version_info(doc_to_compare, self): version.insert(ignore_permissions=True) if not frappe.flags.in_migrate: @@ -1533,7 +1533,7 @@ def lock(self, timeout=None): if file_lock.lock_exists(signature): lock_exists = True if timeout: - for i in range(timeout): + for _ in range(timeout): time.sleep(1) if not file_lock.lock_exists(signature): lock_exists = False diff --git a/frappe/model/dynamic_links.py b/frappe/model/dynamic_links.py index 96545f9f754..e4b86ac1455 100644 --- a/frappe/model/dynamic_links.py +++ b/frappe/model/dynamic_links.py @@ -47,7 +47,7 @@ def get_dynamic_link_map(for_delete=False): ) for doctype in links: dynamic_link_map.setdefault(doctype, []).append(df) - except frappe.db.TableMissingError: # noqa: E722 + except frappe.db.TableMissingError: pass frappe.local.dynamic_link_map = dynamic_link_map diff --git a/frappe/model/meta.py b/frappe/model/meta.py index e7c14cbd6cd..6aee68681f7 100644 --- a/frappe/model/meta.py +++ b/frappe/model/meta.py @@ -105,10 +105,10 @@ class Meta(Document): "DocType State", ) ) - standard_set_once_fields = [ + standard_set_once_fields = ( frappe._dict(fieldname="creation", fieldtype="Datetime"), frappe._dict(fieldname="owner", fieldtype="Data"), - ] + ) def __init__(self, doctype): if isinstance(doctype, Document): @@ -146,7 +146,7 @@ def as_dict(self, no_nulls=False): def serialize(doc): out = {} for key, value in doc.__dict__.items(): - if isinstance(value, (list, tuple)): + if isinstance(value, list | tuple): if not value or not isinstance(value[0], BaseDocument): # non standard list object, skip continue @@ -154,7 +154,7 @@ def serialize(doc): value = [serialize(d) for d in value] if (not no_nulls and value is None) or isinstance( - value, (str, int, float, datetime, list, tuple) + value, str | int | float | datetime | list | tuple ): out[key] = value @@ -712,9 +712,7 @@ def get_web_template(self, suffix=""): module_name, "doctype", doctype, "templates", doctype + suffix + ".html" ) if os.path.exists(template_path): - return "{module_name}/doctype/{doctype_name}/templates/{doctype_name}{suffix}.html".format( - module_name=module_name, doctype_name=doctype, suffix=suffix - ) + return f"{module_name}/doctype/{doctype}/templates/{doctype}{suffix}.html" return None def is_nested_set(self): diff --git a/frappe/model/naming.py b/frappe/model/naming.py index d9183a388d7..795d02f2c11 100644 --- a/frappe/model/naming.py +++ b/frappe/model/naming.py @@ -101,7 +101,7 @@ def fake_counter(_prefix, digits): # ignore B023: binding `count` is not necessary because # function is evaluated immediately and it can not be done # because of function signature requirement - return str(count).zfill(digits) # noqa: B023 + return str(count).zfill(digits) generated_names.append(parse_naming_series(self.series, doc=doc, number_generator=fake_counter)) return generated_names @@ -465,12 +465,10 @@ def append_number_if_name_exists(doctype, value, fieldname="name", separator="-" if exists: last = frappe.db.sql( - """SELECT `{fieldname}` FROM `tab{doctype}` - WHERE `{fieldname}` {regex_character} %s + f"""SELECT `{fieldname}` FROM `tab{doctype}` + WHERE `{fieldname}` {frappe.db.REGEX_CHARACTER} %s ORDER BY length({fieldname}) DESC, - `{fieldname}` DESC LIMIT 1""".format( - doctype=doctype, fieldname=fieldname, regex_character=frappe.db.REGEX_CHARACTER - ), + `{fieldname}` DESC LIMIT 1""", regex, ) diff --git a/frappe/model/rename_doc.py b/frappe/model/rename_doc.py index 380fddd0da2..d8d21bd3bf3 100644 --- a/frappe/model/rename_doc.py +++ b/frappe/model/rename_doc.py @@ -47,7 +47,7 @@ def update_document_title( # TODO: omit this after runtime type checking (ref: https://github.com/frappe/frappe/pull/14927) for obj in [docname, updated_title, updated_name]: - if not isinstance(obj, (str, NoneType)): + if not isinstance(obj, str | NoneType): frappe.throw(f"{obj=} must be of type str or None") # handle bad API usages @@ -119,7 +119,7 @@ def update_document_title( def rename_doc( doctype: str | None = None, old: str | None = None, - new: str = None, + new: str | None = None, force: bool = False, merge: bool = False, ignore_permissions: bool = False, @@ -390,7 +390,7 @@ def validate_rename( def rename_doctype(doctype: str, old: str, new: str) -> None: # change options for fieldtype Table, Table MultiSelect and Link - fields_with_options = ("Link",) + frappe.model.table_fields + fields_with_options = ("Link", *frappe.model.table_fields) for fieldtype in fields_with_options: update_options_for_fieldtype(fieldtype, old, new) diff --git a/frappe/model/utils/__init__.py b/frappe/model/utils/__init__.py index f8f5b21de48..f5118aa991f 100644 --- a/frappe/model/utils/__init__.py +++ b/frappe/model/utils/__init__.py @@ -3,7 +3,6 @@ import re import frappe -from frappe import _ from frappe.build import html_to_js_template from frappe.utils import cstr from frappe.utils.caching import site_cache @@ -30,9 +29,8 @@ def set_default(doc, key): frappe.db.set(doc, "is_default", 1) frappe.db.sql( - """update `tab%s` set `is_default`=0 - where `%s`=%s and name!=%s""" - % (doc.doctype, key, "%s", "%s"), + """update `tab{}` set `is_default`=0 + where `{}`={} and name!={}""".format(doc.doctype, key, "%s", "%s"), (doc.get(key), doc.name), ) @@ -62,7 +60,7 @@ def render_include(content): content = cstr(content) # try 5 levels of includes - for i in range(5): + for _ in range(5): if "{% include" in content: paths = INCLUDE_DIRECTIVE_PATTERN.findall(content) if not paths: diff --git a/frappe/model/utils/rename_field.py b/frappe/model/utils/rename_field.py index e3b0835ddd2..23c934da744 100644 --- a/frappe/model/utils/rename_field.py +++ b/frappe/model/utils/rename_field.py @@ -27,9 +27,8 @@ def rename_field(doctype, old_fieldname, new_fieldname, validate=True): if new_field.fieldtype in table_fields: # change parentfield of table mentioned in options frappe.db.sql( - """update `tab%s` set parentfield=%s - where parentfield=%s""" - % (new_field.options.split("\n", 1)[0], "%s", "%s"), + """update `tab{}` set parentfield={} + where parentfield={}""".format(new_field.options.split("\n", 1)[0], "%s", "%s"), (new_fieldname, old_fieldname), ) @@ -142,9 +141,8 @@ def update_users_report_view_settings(doctype, ref_fieldname, new_fieldname): if columns_modified: frappe.db.sql( - """update `tabDefaultValue` set defvalue=%s - where defkey=%s""" - % ("%s", "%s"), + """update `tabDefaultValue` set defvalue={} + where defkey={}""".format("%s", "%s"), (json.dumps(new_columns), key), ) diff --git a/frappe/model/workflow.py b/frappe/model/workflow.py index 69d9a5065f1..460b7cced0b 100644 --- a/frappe/model/workflow.py +++ b/frappe/model/workflow.py @@ -120,7 +120,7 @@ def apply_workflow(doc, action): doc.set(workflow.workflow_state_field, transition.next_state) # find settings for the next state - next_state = [d for d in workflow.states if d.state == transition.next_state][0] + next_state = next(d for d in workflow.states if d.state == transition.next_state) # update any additional field if next_state.update_field: diff --git a/frappe/modules/import_file.py b/frappe/modules/import_file.py index 6c085cced65..e2d60f8a876 100644 --- a/frappe/modules/import_file.py +++ b/frappe/modules/import_file.py @@ -41,7 +41,7 @@ def calculate_hash(path: str) -> str: def import_files(module, dt=None, dn=None, force=False, pre_process=None, reset_permissions=False): - if type(module) is list: + if isinstance(module, list): return [ import_file( m[0], @@ -78,7 +78,7 @@ def import_file_by_path( force: bool = False, data_import: bool = False, pre_process=None, - ignore_version: bool = None, + ignore_version: bool | None = None, reset_permissions: bool = False, ): """Import file from the given path @@ -213,11 +213,7 @@ def import_doc( docdict["__islocal"] = 1 controller = get_controller(docdict["doctype"]) - if ( - controller - and hasattr(controller, "prepare_for_import") - and callable(getattr(controller, "prepare_for_import")) - ): + if controller and hasattr(controller, "prepare_for_import") and callable(controller.prepare_for_import): controller.prepare_for_import(docdict) doc = frappe.get_doc(docdict) diff --git a/frappe/modules/utils.py b/frappe/modules/utils.py index b480eab8c7b..bfd02e3db7d 100644 --- a/frappe/modules/utils.py +++ b/frappe/modules/utils.py @@ -188,8 +188,8 @@ def get_doc_path(module: str, doctype: str, name: str) -> str: def reload_doc( module: str, - dt: str = None, - dn: str = None, + dt: str | None = None, + dn: str | None = None, force: bool = False, reset_permissions: bool = False, ): diff --git a/frappe/parallel_test_runner.py b/frappe/parallel_test_runner.py index 8f101be8838..b0d3244e183 100644 --- a/frappe/parallel_test_runner.py +++ b/frappe/parallel_test_runner.py @@ -143,7 +143,7 @@ def split_by_weight(work, weights, chunk_count): chunk_no = 0 chunk_weight = 0 - for task, weight in zip(work, weights): + for task, weight in zip(work, weights, strict=False): if chunk_weight > expected_weight: chunk_weight = 0 chunk_no += 1 diff --git a/frappe/patches/v11_0/delete_duplicate_user_permissions.py b/frappe/patches/v11_0/delete_duplicate_user_permissions.py index a8bb291769a..27af1e98c70 100644 --- a/frappe/patches/v11_0/delete_duplicate_user_permissions.py +++ b/frappe/patches/v11_0/delete_duplicate_user_permissions.py @@ -12,7 +12,7 @@ def execute(): for record in duplicateRecords: frappe.db.sql( - """delete from `tabUser Permission` - where allow=%s and user=%s and for_value=%s limit {}""".format(record.count - 1), + f"""delete from `tabUser Permission` + where allow=%s and user=%s and for_value=%s limit {record.count - 1}""", (record.allow, record.user, record.for_value), ) diff --git a/frappe/patches/v11_0/update_list_user_settings.py b/frappe/patches/v11_0/update_list_user_settings.py index 5cbcd3bc0ab..f90871af274 100644 --- a/frappe/patches/v11_0/update_list_user_settings.py +++ b/frappe/patches/v11_0/update_list_user_settings.py @@ -12,8 +12,8 @@ def execute(): for user in users: # get user_settings for each user settings = frappe.db.sql( - "select * from `__UserSettings` \ - where user={}".format(frappe.db.escape(user.user)), + f"select * from `__UserSettings` \ + where user={frappe.db.escape(user.user)}", as_dict=True, ) diff --git a/frappe/patches/v12_0/delete_duplicate_indexes.py b/frappe/patches/v12_0/delete_duplicate_indexes.py index 45f495fe695..6724f28e261 100644 --- a/frappe/patches/v12_0/delete_duplicate_indexes.py +++ b/frappe/patches/v12_0/delete_duplicate_indexes.py @@ -38,7 +38,7 @@ def execute(): frappe.db.sql_ddl(f"ALTER TABLE `{table_name}` DROP INDEX `{index}`") except Exception as e: frappe.log_error("Failed to drop index") - print(f"x Failed to drop index {index} from {table_name}\n {str(e)}") + print(f"x Failed to drop index {index} from {table_name}\n {e!s}") else: print(f"✓ dropped {index} index from {table}") diff --git a/frappe/patches/v12_0/move_email_and_phone_to_child_table.py b/frappe/patches/v12_0/move_email_and_phone_to_child_table.py index 7511a2ee1d8..e1fcb72e212 100644 --- a/frappe/patches/v12_0/move_email_and_phone_to_child_table.py +++ b/frappe/patches/v12_0/move_email_and_phone_to_child_table.py @@ -22,7 +22,6 @@ def execute(): phone_values = [] for count, contact_detail in enumerate(contact_details): phone_counter = 1 - is_primary = 1 if contact_detail.email_id: email_values.append( ( diff --git a/frappe/patches/v12_0/replace_null_values_in_tables.py b/frappe/patches/v12_0/replace_null_values_in_tables.py index 1dc8d964a10..617e9886f69 100644 --- a/frappe/patches/v12_0/replace_null_values_in_tables.py +++ b/frappe/patches/v12_0/replace_null_values_in_tables.py @@ -18,7 +18,7 @@ def execute(): update_column_table_map.setdefault(field.TABLE_NAME, []) update_column_table_map[field.TABLE_NAME].append( - "`{fieldname}`=COALESCE(`{fieldname}`, 0)".format(fieldname=field.COLUMN_NAME) + f"`{field.COLUMN_NAME}`=COALESCE(`{field.COLUMN_NAME}`, 0)" ) for table in frappe.db.get_tables(): diff --git a/frappe/patches/v13_0/update_date_filters_in_user_settings.py b/frappe/patches/v13_0/update_date_filters_in_user_settings.py index 6565641d9b8..030ea3936dc 100644 --- a/frappe/patches/v13_0/update_date_filters_in_user_settings.py +++ b/frappe/patches/v13_0/update_date_filters_in_user_settings.py @@ -9,12 +9,12 @@ def execute(): for user in users: user_settings = frappe.db.sql( - """ + f""" select * from `__UserSettings` where - user='{user}' - """.format(user=user.user), + user='{user.user}' + """, as_dict=True, ) diff --git a/frappe/patches/v14_0/drop_unused_indexes.py b/frappe/patches/v14_0/drop_unused_indexes.py index 896ea78fed0..a2b3f468c2c 100644 --- a/frappe/patches/v14_0/drop_unused_indexes.py +++ b/frappe/patches/v14_0/drop_unused_indexes.py @@ -50,7 +50,7 @@ def drop_index_if_exists(table: str, index: str): frappe.db.sql_ddl(f"ALTER TABLE `{table}` DROP INDEX `{index}`") except Exception as e: frappe.log_error("Failed to drop index") - click.secho(f"x Failed to drop index {index} from {table}\n {str(e)}", fg="red") + click.secho(f"x Failed to drop index {index} from {table}\n {e!s}", fg="red") return click.echo(f"✓ dropped {index} index from {table}") diff --git a/frappe/permissions.py b/frappe/permissions.py index 944909ccc3a..79f9156fcb5 100644 --- a/frappe/permissions.py +++ b/frappe/permissions.py @@ -122,7 +122,7 @@ def has_permission( meta = frappe.get_meta(doctype) if doc: - if isinstance(doc, (str, int)): + if isinstance(doc, str | int): doc = frappe.get_doc(meta.name, doc) perm = get_doc_permissions(doc, user=user, ptype=ptype, debug=debug).get(ptype) if not perm: @@ -466,7 +466,7 @@ def get_valid_perms(doctype=None, user=None): doctypes_with_custom_perms = get_doctypes_with_custom_docperms() for p in perms: - if not p.parent in doctypes_with_custom_perms: + if p.parent not in doctypes_with_custom_perms: custom_perms.append(p) if doctype: diff --git a/frappe/query_builder/builder.py b/frappe/query_builder/builder.py index 535284f34d0..45042c0820d 100644 --- a/frappe/query_builder/builder.py +++ b/frappe/query_builder/builder.py @@ -1,3 +1,4 @@ +import types import typing from pypika import MySQLQuery, Order, PostgreSQLQuery, terms @@ -62,8 +63,8 @@ def from_(cls, table, *args, **kwargs): class Postgres(Base, PostgreSQLQuery): - field_translation = {"table_name": "relname", "table_rows": "n_tup_ins"} - schema_translation = {"tables": "pg_stat_all_tables"} + field_translation = types.MappingProxyType({"table_name": "relname", "table_rows": "n_tup_ins"}) + schema_translation = types.MappingProxyType({"tables": "pg_stat_all_tables"}) # TODO: Find a better way to do this # These are interdependent query changes that need fixing. These # translations happen in the same query. But there is no check to see if diff --git a/frappe/rate_limiter.py b/frappe/rate_limiter.py index d82ddd60176..25d2fbf1730 100644 --- a/frappe/rate_limiter.py +++ b/frappe/rate_limiter.py @@ -88,7 +88,7 @@ def respond(self): def rate_limit( - key: str = None, + key: str | None = None, limit: int | Callable = 5, seconds: int = 24 * 60 * 60, methods: str | list = "ALL", diff --git a/frappe/realtime.py b/frappe/realtime.py index b795df3782e..78726314988 100644 --- a/frappe/realtime.py +++ b/frappe/realtime.py @@ -20,13 +20,13 @@ def publish_progress(percent, title=None, doctype=None, docname=None, descriptio def publish_realtime( - event: str = None, - message: dict = None, - room: str = None, - user: str = None, - doctype: str = None, - docname: str = None, - task_id: str = None, + event: str | None = None, + message: dict | None = None, + room: str | None = None, + user: str | None = None, + doctype: str | None = None, + docname: str | None = None, + task_id: str | None = None, after_commit: bool = False, ): """Publish real-time updates diff --git a/frappe/recorder.py b/frappe/recorder.py index a0b4c178808..974f98b3712 100644 --- a/frappe/recorder.py +++ b/frappe/recorder.py @@ -81,7 +81,7 @@ def get_current_stack_frames(): try: current = inspect.currentframe() frames = inspect.getouterframes(current, context=10) - for frame, filename, lineno, function, context, index in list(reversed(frames))[:-2]: + for frame, filename, lineno, function, context, index in list(reversed(frames))[:-2]: # noqa: B007 if "/apps/" in filename or SERVER_SCRIPT_FILE_PREFIX in filename: yield { "filename": TRACEBACK_PATH_PATTERN.sub("", filename), diff --git a/frappe/search/website_search.py b/frappe/search/website_search.py index 2b35b86de74..ae046f7bf32 100644 --- a/frappe/search/website_search.py +++ b/frappe/search/website_search.py @@ -98,7 +98,7 @@ def slugs_with_web_view(_items_to_index): fields = ["route", doctype.website_search_field] filters = ({doctype.is_published_field: 1},) if doctype.website_search_field: - docs = frappe.get_all(doctype.name, filters=filters, fields=fields + ["title"]) + docs = frappe.get_all(doctype.name, filters=filters, fields=[*fields, "title"]) for doc in docs: content = frappe.utils.md_to_html(getattr(doc, doctype.website_search_field)) soup = BeautifulSoup(content, "html.parser") diff --git a/frappe/social/doctype/energy_point_log/energy_point_log.py b/frappe/social/doctype/energy_point_log/energy_point_log.py index 0c1a6d4b59c..5327a2f0f5b 100644 --- a/frappe/social/doctype/energy_point_log/energy_point_log.py +++ b/frappe/social/doctype/energy_point_log/energy_point_log.py @@ -257,7 +257,7 @@ def get_user_energy_and_review_points(user=None, from_date=None, as_dict=True): values.from_date = from_date points_list = frappe.db.sql( - """ + f""" SELECT SUM(CASE WHEN `type` != 'Review' THEN `points` ELSE 0 END) AS energy_points, SUM(CASE WHEN `type` = 'Review' THEN `points` ELSE 0 END) AS review_points, @@ -271,7 +271,7 @@ def get_user_energy_and_review_points(user=None, from_date=None, as_dict=True): {conditions} GROUP BY `user` ORDER BY `energy_points` DESC - """.format(conditions=conditions, given_points_condition=given_points_condition), + """, values=values, as_dict=1, ) diff --git a/frappe/social/doctype/energy_point_rule/energy_point_rule.py b/frappe/social/doctype/energy_point_rule/energy_point_rule.py index 5dd181781e0..a59a55e3f2d 100644 --- a/frappe/social/doctype/energy_point_rule/energy_point_rule.py +++ b/frappe/social/doctype/energy_point_rule/energy_point_rule.py @@ -77,7 +77,7 @@ def apply(self, doc): {"points": points, "user": user, "rule": rule}, self.apply_only_once, ) - except Exception as e: + except Exception: self.log_error("Energy points failed") def rule_condition_satisfied(self, doc): diff --git a/frappe/test_runner.py b/frappe/test_runner.py index 35911269cb1..a5d404eba8b 100644 --- a/frappe/test_runner.py +++ b/frappe/test_runner.py @@ -195,7 +195,7 @@ def run_tests_for_doctype( junit_xml_output=False, ): modules = [] - if not isinstance(doctypes, (list, tuple)): + if not isinstance(doctypes, list | tuple): doctypes = [doctypes] for doctype in doctypes: @@ -248,7 +248,7 @@ def _run_unittest( test_suite = unittest.TestSuite() - if not isinstance(modules, (list, tuple)): + if not isinstance(modules, list | tuple): modules = [modules] for module in modules: diff --git a/frappe/tests/test_api.py b/frappe/tests/test_api.py index d2ccf6bd73e..0a8f164da84 100644 --- a/frappe/tests/test_api.py +++ b/frappe/tests/test_api.py @@ -1,5 +1,6 @@ import json import sys +import typing from contextlib import contextmanager from functools import cached_property from random import choice @@ -40,7 +41,7 @@ def make_request( target: str, args: tuple | None = None, kwargs: dict | None = None, - site: str = None, + site: str | None = None, ) -> TestResponse: t = ThreadWithReturnValue(target=target, args=args, kwargs=kwargs, site=site) t.start() @@ -54,7 +55,9 @@ def patch_request_header(key, *args, **kwargs): class ThreadWithReturnValue(Thread): - def __init__(self, group=None, target=None, name=None, args=(), kwargs={}, *, site=None): + def __init__(self, group=None, target=None, name=None, args=(), kwargs=None, *, site=None): + if kwargs is None: + kwargs = {} Thread.__init__(self, group, target, name, args, kwargs) self._return = None self.site = site or _site @@ -130,7 +133,7 @@ def delete(self, path, **kwargs) -> TestResponse: class TestResourceAPI(FrappeAPITestCase): DOCTYPE = "ToDo" - GENERATED_DOCUMENTS = [] + GENERATED_DOCUMENTS: typing.ClassVar = [] @classmethod def setUpClass(cls): @@ -278,7 +281,7 @@ def test_auth_cycle(self): response = self.get(self.method_path("frappe.auth.get_logged_user")) self.assertEqual(response.status_code, 401) - authorization_token = f"NonExistentKey:INCORRECT" + authorization_token = "NonExistentKey:INCORRECT" response = self.get(self.method_path("frappe.auth.get_logged_user")) self.assertEqual(response.status_code, 401) @@ -378,7 +381,7 @@ def after_request(*args, **kwargs): class TestResponse(FrappeAPITestCase): def test_generate_pdf(self): response = self.get( - f"/api/method/frappe.utils.print_format.download_pdf", + "/api/method/frappe.utils.print_format.download_pdf", {"sid": self.sid, "doctype": "User", "name": "Guest"}, ) self.assertEqual(response.status_code, 200) diff --git a/frappe/tests/test_api_v2.py b/frappe/tests/test_api_v2.py index 753e95b4d45..e2f0cba0a5a 100644 --- a/frappe/tests/test_api_v2.py +++ b/frappe/tests/test_api_v2.py @@ -1,3 +1,4 @@ +import typing from random import choice import requests @@ -19,7 +20,7 @@ class TestResourceAPIV2(FrappeAPITestCase): version = "v2" DOCTYPE = "ToDo" - GENERATED_DOCUMENTS = [] + GENERATED_DOCUMENTS: typing.ClassVar[list] = [] @classmethod def setUpClass(cls): diff --git a/frappe/tests/test_caching.py b/frappe/tests/test_caching.py index 950a07ef945..ccc44bbcfcd 100644 --- a/frappe/tests/test_caching.py +++ b/frappe/tests/test_caching.py @@ -15,7 +15,7 @@ def request_specific_api(a: list | tuple | dict | int, b: int) -> int: # API that takes very long to return a result todays_value = external_service() - if not isinstance(a, (int, float)): + if not isinstance(a, int | float): a = 1 return a**b * todays_value @@ -44,7 +44,9 @@ def test_request_cache(self): frappe.get_last_doc("DocType"), frappe._dict(), ] - same_output_received = lambda: all([x for x in set(retval) if x == retval[0]]) + + def same_output_received(): + return all([x for x in set(retval) if x == retval[0]]) # ensure that external service was called only once # thereby return value of request_specific_api is cached diff --git a/frappe/tests/test_commands.py b/frappe/tests/test_commands.py index c5ce61f2382..92d02d7677c 100644 --- a/frappe/tests/test_commands.py +++ b/frappe/tests/test_commands.py @@ -8,6 +8,7 @@ import os import shlex import subprocess +import types import unittest from contextlib import contextmanager from functools import wraps @@ -517,15 +518,17 @@ def test_set_global_conf(self): class TestBackups(BaseTestCommands): - backup_map = { - "includes": { - "includes": [ - "ToDo", - "Note", - ] - }, - "excludes": {"excludes": ["Activity Log", "Access Log", "Error Log"]}, - } + backup_map = types.MappingProxyType( + { + "includes": { + "includes": [ + "ToDo", + "Note", + ] + }, + "excludes": {"excludes": ["Activity Log", "Access Log", "Error Log"]}, + } + ) home = os.path.expanduser("~") site_backup_path = frappe.utils.get_site_path("private", "backups") diff --git a/frappe/tests/test_db.py b/frappe/tests/test_db.py index ae3d57405fe..fcdef0ca7e5 100644 --- a/frappe/tests/test_db.py +++ b/frappe/tests/test_db.py @@ -2,7 +2,6 @@ # License: MIT. See LICENSE import datetime -import inspect from math import ceil from random import choice from unittest.mock import patch @@ -17,7 +16,7 @@ from frappe.query_builder.functions import Concat_ws from frappe.tests.test_query_builder import db_type_is, run_only_if from frappe.tests.utils import FrappeTestCase -from frappe.utils import add_days, cint, now, random_string, set_request +from frappe.utils import add_days, now, random_string, set_request from frappe.utils.testutils import clear_custom_fields @@ -347,36 +346,40 @@ def add_custom_field(field): random_value = random_string(20) # Testing read - self.assertEqual(list(frappe.get_all("ToDo", fields=[random_field], limit=1)[0])[0], random_field) + self.assertEqual(next(iter(frappe.get_all("ToDo", fields=[random_field], limit=1)[0])), random_field) self.assertEqual( - list(frappe.get_all("ToDo", fields=[f"`{random_field}` as total"], limit=1)[0])[0], "total" + next(iter(frappe.get_all("ToDo", fields=[f"`{random_field}` as total"], limit=1)[0])), "total" ) # Testing read for distinct and sql functions self.assertEqual( - list( - frappe.get_all( - "ToDo", - fields=[f"`{random_field}` as total"], - distinct=True, - limit=1, - )[0] - )[0], + next( + iter( + frappe.get_all( + "ToDo", + fields=[f"`{random_field}` as total"], + distinct=True, + limit=1, + )[0] + ) + ), "total", ) self.assertEqual( - list( - frappe.get_all( - "ToDo", - fields=[f"`{random_field}`"], - distinct=True, - limit=1, - )[0] - )[0], + next( + iter( + frappe.get_all( + "ToDo", + fields=[f"`{random_field}`"], + distinct=True, + limit=1, + )[0] + ) + ), random_field, ) self.assertEqual( - list(frappe.get_all("ToDo", fields=[f"count(`{random_field}`)"], limit=1)[0])[0], + next(iter(frappe.get_all("ToDo", fields=[f"count(`{random_field}`)"], limit=1)[0])), "count" if frappe.conf.db_type == "postgres" else f"count(`{random_field}`)", ) @@ -444,7 +447,7 @@ def test_transaction_writes_error(self): frappe.db.MAX_WRITES_PER_TRANSACTION = 1 note = frappe.get_last_doc("ToDo") note.description = "changed" - with self.assertRaises(frappe.TooManyWritesError) as tmw: + with self.assertRaises(frappe.TooManyWritesError): note.save() frappe.db.MAX_WRITES_PER_TRANSACTION = Database.MAX_WRITES_PER_TRANSACTION diff --git a/frappe/tests/test_db_query.py b/frappe/tests/test_db_query.py index 89c29e98924..862e49bef4d 100644 --- a/frappe/tests/test_db_query.py +++ b/frappe/tests/test_db_query.py @@ -57,7 +57,11 @@ def test_extract_tables(self): db_query = DatabaseQuery("DocType") add_custom_field("DocType", "test_tab_field", "Data") - db_query.fields = ["tabNote.creation", "test_tab_field", "tabDocType.test_tab_field"] + db_query.fields = [ + "tabNote.creation", + "test_tab_field", + "tabDocType.test_tab_field", + ] db_query.extract_tables() self.assertIn("`tabNote`", db_query.tables) self.assertIn("`tabDocType`", db_query.tables) @@ -148,7 +152,10 @@ def test_child_table_join(self): frappe.get_doc( doctype="Parent DocType 1", title="test", - child=[{"title": "parent 1 child record 1"}, {"title": "parent 1 child record 2"}], + child=[ + {"title": "parent 1 child record 1"}, + {"title": "parent 1 child record 2"}, + ], __newname="test_parent", ).insert(ignore_if_duplicate=True) frappe.get_doc( @@ -295,7 +302,8 @@ def test_between_filters(self): # if both from and to_date values are passed data = DatabaseQuery("Event").execute( - filters={"starts_on": ["between", ["2016-07-06", "2016-07-07"]]}, fields=["name"] + filters={"starts_on": ["between", ["2016-07-06", "2016-07-07"]]}, + fields=["name"], ) self.assertIn({"name": event2.name}, data) @@ -316,7 +324,8 @@ def test_between_filters(self): # test between is formatted for creation column data = DatabaseQuery("Event").execute( - filters={"creation": ["between", ["2016-07-06", "2016-07-07"]]}, fields=["name"] + filters={"creation": ["between", ["2016-07-06", "2016-07-07"]]}, + fields=["name"], ) def test_between_filters_date_bounds(self): @@ -368,7 +377,10 @@ def test_query_fields_sanitizer(self): self.assertRaises( frappe.DataError, DatabaseQuery("DocType").execute, - fields=["name", "issingle, IF(issingle=1, (select name from tabUser), count(name))"], + fields=[ + "name", + "issingle, IF(issingle=1, (select name from tabUser), count(name))", + ], limit_start=0, limit_page_length=1, ) @@ -392,7 +404,10 @@ def test_query_fields_sanitizer(self): self.assertRaises( frappe.DataError, DatabaseQuery("DocType").execute, - fields=["name", "issingle, IF(issingle=1, (SELECT name from tabUser), count(*))"], + fields=[ + "name", + "issingle, IF(issingle=1, (SELECT name from tabUser), count(*))", + ], limit_start=0, limit_page_length=1, ) @@ -466,14 +481,20 @@ def test_query_fields_sanitizer(self): self.assertTrue("_relevance" in data[0]) data = DatabaseQuery("DocType").execute( - fields=["name", "issingle", "date(creation) as creation"], limit_start=0, limit_page_length=1 + fields=["name", "issingle", "date(creation) as creation"], + limit_start=0, + limit_page_length=1, ) self.assertTrue("creation" in data[0]) if frappe.db.db_type != "postgres": # datediff function does not exist in postgres data = DatabaseQuery("DocType").execute( - fields=["name", "issingle", "datediff(modified, creation) as date_diff"], + fields=[ + "name", + "issingle", + "datediff(modified, creation) as date_diff", + ], limit_start=0, limit_page_length=1, ) @@ -481,14 +502,22 @@ def test_query_fields_sanitizer(self): with self.assertRaises(frappe.DataError): DatabaseQuery("DocType").execute( - fields=["name", "issingle", "if (issingle=1, (select name from tabUser), count(name))"], + fields=[ + "name", + "issingle", + "if (issingle=1, (select name from tabUser), count(name))", + ], limit_start=0, limit_page_length=1, ) with self.assertRaises(frappe.DataError): DatabaseQuery("DocType").execute( - fields=["name", "issingle", "if(issingle=1, (select name from tabUser), count(name))"], + fields=[ + "name", + "issingle", + "if(issingle=1, (select name from tabUser), count(name))", + ], limit_start=0, limit_page_length=1, ) @@ -578,7 +607,10 @@ def test_filter_sanitizer(self): DatabaseQuery("DocType").execute, fields=["name"], filters={"editable_grid,": 1}, - or_filters=[["DocType", "istable", "=", 1], ["DocType", "beta and 1=1", "=", 0]], + or_filters=[ + ["DocType", "istable", "=", 1], + ["DocType", "beta and 1=1", "=", 0], + ], limit_start=0, limit_page_length=1, ) @@ -600,13 +632,18 @@ def test_filter_sanitizer(self): self.assertTrue("Role Permission for Page and Report" in [d["name"] for d in out]) out = DatabaseQuery("DocType").execute( - fields=["name"], filters={"track_changes": 1, "module": "Core"}, order_by="creation" + fields=["name"], + filters={"track_changes": 1, "module": "Core"}, + order_by="creation", ) self.assertTrue("File" in [d["name"] for d in out]) out = DatabaseQuery("DocType").execute( fields=["name"], - filters=[["DocType", "ifnull(track_changes, 0)", "=", 0], ["DocType", "module", "=", "Core"]], + filters=[ + ["DocType", "ifnull(track_changes, 0)", "=", 0], + ["DocType", "module", "=", "Core"], + ], order_by="creation", ) self.assertTrue("DefaultValue" in [d["name"] for d in out]) @@ -743,7 +780,7 @@ def test_is_set_is_not_set(self): def test_set_field_tables(self): # Tests _in_standard_sql_methods method in test_set_field_tables # The following query will break if the above method is broken - data = frappe.db.get_list( + frappe.db.get_list( "Web Form", filters=[["Web Form Field", "reqd", "=", 1]], fields=["count(*) as count"], @@ -755,7 +792,7 @@ def test_virtual_field_get_list(self): try: frappe.get_list("Prepared Report", ["*"]) frappe.get_list("Scheduled Job Type", ["*"]) - except Exception as e: + except Exception: print(frappe.get_traceback()) self.fail("get_list not working with virtual field") @@ -805,21 +842,30 @@ def test_column_comparison(self): def test_permlevel_fields(self): with setup_patched_blog_post(), setup_test_user(set_user=True): data = frappe.get_list( - "Blog Post", filters={"published": 1}, fields=["name", "published"], limit=1 + "Blog Post", + filters={"published": 1}, + fields=["name", "published"], + limit=1, ) self.assertFalse("published" in data[0]) self.assertTrue("name" in data[0]) self.assertEqual(len(data[0]), 1) data = frappe.get_list( - "Blog Post", filters={"published": 1}, fields=["name", "`published`"], limit=1 + "Blog Post", + filters={"published": 1}, + fields=["name", "`published`"], + limit=1, ) self.assertFalse("published" in data[0]) self.assertTrue("name" in data[0]) self.assertEqual(len(data[0]), 1) data = frappe.get_list( - "Blog Post", filters={"published": 1}, fields=["name", "`tabBlog Post`.`published`"], limit=1 + "Blog Post", + filters={"published": 1}, + fields=["name", "`tabBlog Post`.`published`"], + limit=1, ) self.assertFalse("published" in data[0]) self.assertTrue("name" in data[0]) @@ -836,13 +882,19 @@ def test_permlevel_fields(self): self.assertEqual(len(data[0]), 1) data = frappe.get_list( - "Blog Post", filters={"published": 1}, fields=["name", "MAX(`published`)"], limit=1 + "Blog Post", + filters={"published": 1}, + fields=["name", "MAX(`published`)"], + limit=1, ) self.assertTrue("name" in data[0]) self.assertEqual(len(data[0]), 1) data = frappe.get_list( - "Blog Post", filters={"published": 1}, fields=["name", "LAST(published)"], limit=1 + "Blog Post", + filters={"published": 1}, + fields=["name", "LAST(published)"], + limit=1, ) self.assertTrue("name" in data[0]) self.assertEqual(len(data[0]), 1) @@ -858,12 +910,20 @@ def test_permlevel_fields(self): self.assertEqual(len(data[0]), 2) data = frappe.get_list( - "Blog Post", filters={"published": 1}, fields=["name", "now() abhi"], limit=1 + "Blog Post", + filters={"published": 1}, + fields=["name", "now() abhi"], + limit=1, ) self.assertIsInstance(data[0]["abhi"], datetime.datetime) self.assertEqual(len(data[0]), 2) - data = frappe.get_list("Blog Post", filters={"published": 1}, fields=["name", "'LABEL'"], limit=1) + data = frappe.get_list( + "Blog Post", + filters={"published": 1}, + fields=["name", "'LABEL'"], + limit=1, + ) self.assertTrue("name" in data[0]) self.assertTrue("LABEL" in data[0].values()) self.assertEqual(len(data[0]), 2) @@ -892,7 +952,11 @@ def test_permlevel_fields(self): data = frappe.get_list( "Blog Post", - fields=["name", "blogger.full_name as blogger_full_name", "blog_category.description"], + fields=[ + "name", + "blogger.full_name as blogger_full_name", + "blog_category.description", + ], limit=1, ) self.assertTrue("name" in data[0]) @@ -906,7 +970,11 @@ def test_cast_name(self): dt = new_doctype("autoinc_dt_test", autoname="autoincrement").insert(ignore_permissions=True) query = DatabaseQuery("autoinc_dt_test").execute( - fields=["locate('1', `tabautoinc_dt_test`.`name`)", "name", "locate('1', name)"], + fields=[ + "locate('1', `tabautoinc_dt_test`.`name`)", + "name", + "locate('1', name)", + ], filters={"name": 1}, run=False, ) @@ -929,7 +997,9 @@ def test_fieldname_starting_with_int(self): frappe.delete_doc_if_exists("DocType", "table_dt") table_dt = new_doctype( - "table_dt", istable=1, fields=[{"label": "1field", "fieldname": "2field", "fieldtype": "Data"}] + "table_dt", + istable=1, + fields=[{"label": "1field", "fieldname": "2field", "fieldtype": "Data"}], ).insert() dt = new_doctype( @@ -974,7 +1044,9 @@ def test_fieldname_starting_with_int(self): table_dt.delete() def test_permission_query_condition(self): - from frappe.desk.doctype.dashboard_settings.dashboard_settings import create_dashboard_settings + from frappe.desk.doctype.dashboard_settings.dashboard_settings import ( + create_dashboard_settings, + ) self.doctype = "Dashboard Settings" self.user = "test'5@example.com" @@ -984,11 +1056,11 @@ def test_permission_query_condition(self): create_dashboard_settings(self.user) dashboard_settings = frappe.db.sql( - """ + f""" SELECT name FROM `tabDashboard Settings` - WHERE {condition} - """.format(condition=permission_query_conditions), + WHERE {permission_query_conditions} + """, as_dict=1, )[0] @@ -1006,7 +1078,10 @@ class VirtualDocType: def get_list(args): ... - with patch("frappe.controllers", new={frappe.local.site: {"Virtual DocType": VirtualDocType}}): + with patch( + "frappe.controllers", + new={frappe.local.site: {"Virtual DocType": VirtualDocType}}, + ): VirtualDocType.get_list = MagicMock() frappe.get_all("Virtual DocType", filters={"name": "test"}, fields=["name"], limit=1) @@ -1120,7 +1195,11 @@ def test_get_count(self): ) list_filter_response = execute_cmd("frappe.desk.reportview.get_count") frappe.local.form_dict = frappe._dict( - {"doctype": "DocType", "filters": {"show_title_field_in_link": 1}, "distinct": "true"} + { + "doctype": "DocType", + "filters": {"show_title_field_in_link": 1}, + "distinct": "true", + } ) dict_filter_response = execute_cmd("frappe.desk.reportview.get_count") self.assertIsInstance(list_filter_response, int) diff --git a/frappe/tests/test_form_load.py b/frappe/tests/test_form_load.py index 119bf5bdb2f..d9f031b3e32 100644 --- a/frappe/tests/test_form_load.py +++ b/frappe/tests/test_form_load.py @@ -13,13 +13,13 @@ class TestFormLoad(FrappeTestCase): def test_load(self): getdoctype("DocType") - meta = list(filter(lambda d: d.name == "DocType", frappe.response.docs))[0] + meta = next(filter(lambda d: d.name == "DocType", frappe.response.docs)) self.assertEqual(meta.name, "DocType") self.assertTrue(meta.get("__js")) frappe.response.docs = [] getdoctype("Event") - meta = list(filter(lambda d: d.name == "Event", frappe.response.docs))[0] + meta = next(filter(lambda d: d.name == "Event", frappe.response.docs)) self.assertTrue(meta.get("__calendar_js")) def test_fieldlevel_permissions_in_load(self): diff --git a/frappe/tests/test_naming.py b/frappe/tests/test_naming.py index 51340c7c824..25087812ef8 100644 --- a/frappe/tests/test_naming.py +++ b/frappe/tests/test_naming.py @@ -319,7 +319,7 @@ def test_naming_series_prefix(self): def test_naming_series_validation(self): dns = frappe.get_doc("Document Naming Settings") exisiting_series = dns.get_transactions_and_prefixes()["prefixes"] - valid = ["SINV-", "SI-.{field}.", "SI-#.###", ""] + exisiting_series + valid = ["SINV-", "SI-.{field}.", "SI-#.###", "", *exisiting_series] invalid = ["$INV-", r"WINDOWS\NAMING"] for series in valid: diff --git a/frappe/tests/test_oauth20.py b/frappe/tests/test_oauth20.py index 8bf0ccdde74..83c5625bff3 100644 --- a/frappe/tests/test_oauth20.py +++ b/frappe/tests/test_oauth20.py @@ -316,7 +316,7 @@ def test_login_using_implicit_token(self): frappe.db.commit() def test_openid_code_id_token(self): - client = update_client_for_auth_code_grant(self.client_id) + update_client_for_auth_code_grant(self.client_id) nonce = frappe.generate_hash() # Go to Authorize url diff --git a/frappe/tests/test_patches.py b/frappe/tests/test_patches.py index 3b3b9f17020..2e803708e58 100644 --- a/frappe/tests/test_patches.py +++ b/frappe/tests/test_patches.py @@ -164,7 +164,7 @@ def check_patch_files(app): missing_patches.append(module) if missing_patches: - raise Exception(f"Patches missing in patch.txt: \n" + "\n".join(missing_patches)) + raise Exception("Patches missing in patch.txt: \n" + "\n".join(missing_patches)) def _get_dotted_path(file: Path, app) -> str: @@ -173,4 +173,4 @@ def _get_dotted_path(file: Path, app) -> str: *path, filename = file.relative_to(app_path).parts base_filename = Path(filename).stem - return ".".join([app] + path + [base_filename]) + return ".".join([app, *path, base_filename]) diff --git a/frappe/tests/test_perf.py b/frappe/tests/test_perf.py index c0796809c81..91e8c5c890b 100644 --- a/frappe/tests/test_perf.py +++ b/frappe/tests/test_perf.py @@ -145,7 +145,7 @@ def test_req_per_seconds_basic(self): self.assertGreaterEqual( rps, EXPECTED_RPS * (1 - FAILURE_THREASHOLD), - f"Possible performance regression in basic /api/Resource list requests", + "Possible performance regression in basic /api/Resource list requests", ) def test_homepage_resolver(self): diff --git a/frappe/tests/test_query_report.py b/frappe/tests/test_query_report.py index 8d47a846bb0..5b693a95569 100644 --- a/frappe/tests/test_query_report.py +++ b/frappe/tests/test_query_report.py @@ -3,7 +3,6 @@ import frappe import frappe.utils -from frappe.core.doctype.doctype.test_doctype import new_doctype from frappe.desk.query_report import build_xlsx_data, export_query, run from frappe.tests.utils import FrappeTestCase from frappe.utils.xlsxutils import make_xlsx @@ -138,7 +137,7 @@ def test_report_for_duplicate_column_names(self): {"label": "First Name", "fieldname": "first_name", "fieldtype": "Data"}, {"label": "Last Name", "fieldname": "last_name", "fieldtype": "Data"}, ] - docA = frappe.get_doc( + frappe.get_doc( { "doctype": "DocType", "name": "Doc A", @@ -150,7 +149,7 @@ def test_report_for_duplicate_column_names(self): } ).insert(ignore_if_duplicate=True) - docB = frappe.get_doc( + frappe.get_doc( { "doctype": "DocType", "name": "Doc B", diff --git a/frappe/tests/test_recorder.py b/frappe/tests/test_recorder.py index e2ff7e1ab48..57393b02384 100644 --- a/frappe/tests/test_recorder.py +++ b/frappe/tests/test_recorder.py @@ -109,7 +109,7 @@ def test_multiple_queries(self): self.assertEqual(len(request["calls"]), len(queries)) - for query, call in zip(queries, request["calls"]): + for query, call in zip(queries, request["calls"], strict=False): self.assertEqual( call["query"], sqlparse.format( @@ -134,7 +134,7 @@ def test_duplicate_queries(self): requests = frappe.recorder.get() request = frappe.recorder.get(requests[0]["uuid"]) - for query, call in zip(queries, request["calls"]): + for query, call in zip(queries, request["calls"], strict=False): self.assertEqual(call["exact_copies"], query[1]) def test_error_page_rendering(self): diff --git a/frappe/tests/test_rename_doc.py b/frappe/tests/test_rename_doc.py index 1d484f2c50d..35ab54c5f8b 100644 --- a/frappe/tests/test_rename_doc.py +++ b/frappe/tests/test_rename_doc.py @@ -18,7 +18,7 @@ @contextmanager -def patch_db(endpoints: list[str] = None): +def patch_db(endpoints: list[str] | None = None): patched_endpoints = [] for point in endpoints: diff --git a/frappe/tests/test_safe_exec.py b/frappe/tests/test_safe_exec.py index 0e107c49561..87a8b7c6bf3 100644 --- a/frappe/tests/test_safe_exec.py +++ b/frappe/tests/test_safe_exec.py @@ -93,7 +93,7 @@ def test_enqueue(self): def test_ensure_getattrable_globals(self): def check_safe(objects): for obj in objects: - if isinstance(obj, (types.ModuleType, types.CodeType, types.TracebackType, types.FrameType)): + if isinstance(obj, types.ModuleType | types.CodeType | types.TracebackType | types.FrameType): self.fail(f"{obj} wont work in safe exec.") elif isinstance(obj, dict): check_safe(obj.values()) diff --git a/frappe/tests/test_translate.py b/frappe/tests/test_translate.py index cd4066c6943..7281e92447d 100644 --- a/frappe/tests/test_translate.py +++ b/frappe/tests/test_translate.py @@ -17,7 +17,6 @@ get_language, get_parent_language, get_translation_dict_from_file, - write_translations_file, ) from frappe.utils import get_bench_path, set_request @@ -31,10 +30,10 @@ class TestTranslate(FrappeTestCase): - guest_sessions_required = [ + guest_sessions_required = ( "test_guest_request_language_resolution_with_cookie", "test_guest_request_language_resolution_with_request_header", - ] + ) def setUp(self): if self._testMethodName in self.guest_sessions_required: @@ -57,7 +56,7 @@ def test_extract_message_from_file(self): msg=f"Mismatched output:\nExpected: {expected_output}\nFound: {data}", ) - for extracted, expected in zip(data, expected_output): + for extracted, expected in zip(data, expected_output, strict=False): ext_filename, ext_message, ext_context, ext_line = extracted exp_message, exp_context, exp_line = expected self.assertEqual(ext_filename, exp_filename) @@ -186,7 +185,7 @@ def test_python_extractor(self): output = extract_messages_from_python_code(code) self.assertEqual(len(expected_output), len(output)) - for expected, actual in zip(expected_output, output): + for expected, actual in zip(expected_output, output, strict=False): with self.subTest(): self.assertEqual(expected, actual) @@ -223,7 +222,7 @@ def test_js_extractor(self): output = extract_messages_from_javascript_code(code) self.assertEqual(len(expected_output), len(output)) - for expected, actual in zip(expected_output, output): + for expected, actual in zip(expected_output, output, strict=False): with self.subTest(): self.assertEqual(expected, actual) diff --git a/frappe/tests/test_twofactor.py b/frappe/tests/test_twofactor.py index ca462a9e347..5108df7f759 100644 --- a/frappe/tests/test_twofactor.py +++ b/frappe/tests/test_twofactor.py @@ -232,7 +232,7 @@ def toggle_2fa_all_role(state=None): """Enable or disable 2fa for 'all' role on the system.""" all_role = frappe.get_doc("Role", "All") state = state if state is not None else False - if type(state) != bool: + if not isinstance(state, bool): return all_role.two_factor_auth = cint(state) diff --git a/frappe/tests/test_utils.py b/frappe/tests/test_utils.py index 39669eb9763..4eedae62fb3 100644 --- a/frappe/tests/test_utils.py +++ b/frappe/tests/test_utils.py @@ -462,7 +462,7 @@ def test_validation_for_good_python_expression(self): try: validate_python_code(expr) except Exception as e: - self.fail(f"Invalid error thrown for valid expression: {expr}: {str(e)}") + self.fail(f"Invalid error thrown for valid expression: {expr}: {e!s}") def test_validation_for_bad_python_expression(self): invalid_expressions = [ @@ -700,7 +700,7 @@ class TEST(Enum): self.assertTrue(all([isinstance(x, str) for x in processed_object["time_types"]])) self.assertTrue(all([isinstance(x, float) for x in processed_object["float"]])) - self.assertTrue(all([isinstance(x, (list, str)) for x in processed_object["iter"]])) + self.assertTrue(all([isinstance(x, list | str) for x in processed_object["iter"]])) self.assertIsInstance(processed_object["string"], str) with self.assertRaises(TypeError): json.dumps(BAD_OBJECT, default=json_handler) @@ -983,7 +983,7 @@ def test_url_expansion(self): class TestTypingValidations(FrappeTestCase): - ERR_REGEX = f"^Argument '.*' should be of type '.*' but got '.*' instead.$" + ERR_REGEX = "^Argument '.*' should be of type '.*' but got '.*' instead.$" def test_validate_whitelisted_api(self): @frappe.whitelist() @@ -1019,9 +1019,9 @@ def test_validate_whitelisted_doc_method(self): class TestTBSanitization(FrappeTestCase): def test_traceback_sanitzation(self): try: - password = "42" + password = "42" # noqa: F841 args = {"password": "42", "pwd": "42", "safe": "safe_value"} - args = frappe._dict({"password": "42", "pwd": "42", "safe": "safe_value"}) + args = frappe._dict({"password": "42", "pwd": "42", "safe": "safe_value"}) # noqa: F841 raise Exception except Exception: traceback = frappe.get_traceback(with_context=True) @@ -1183,7 +1183,7 @@ def test_default_rounding(self): self.assertEqual(frappe.get_system_settings("rounding_method"), "Banker's Rounding") -class TestTypingValidations(FrappeTestCase): +class TestArgumentTypingValidations(FrappeTestCase): def test_validate_argument_types(self): from frappe.core.doctype.doctype.doctype import DocType from frappe.utils.typing_validations import FrappeTypeError, validate_argument_types diff --git a/frappe/tests/utils.py b/frappe/tests/utils.py index cbe536253c5..088218596c7 100644 --- a/frappe/tests/utils.py +++ b/frappe/tests/utils.py @@ -60,7 +60,7 @@ def assertDocumentEqual(self, expected, actual): if isinstance(value, list): actual_child_docs = actual.get(field) self.assertEqual(len(value), len(actual_child_docs), msg=f"{field} length should be same") - for exp_child, actual_child in zip(value, actual_child_docs): + for exp_child, actual_child in zip(value, actual_child_docs, strict=False): self.assertDocumentEqual(exp_child, actual_child) else: self._compare_field(value, actual.get(field), actual, field) @@ -73,7 +73,7 @@ def _compare_field(self, expected, actual, doc: BaseDocument, field: str): self.assertAlmostEqual( expected, actual, places=precision, msg=f"{field} should be same to {precision} digits" ) - elif isinstance(expected, (bool, int)): + elif isinstance(expected, bool | int): self.assertEqual(expected, cint(actual), msg=msg) elif isinstance(expected, datetime_like_types): self.assertEqual(str(expected), str(actual), msg=msg) diff --git a/frappe/translate.py b/frappe/translate.py index 179fb317178..096b28f2806 100644 --- a/frappe/translate.py +++ b/frappe/translate.py @@ -57,7 +57,7 @@ USER_TRANSLATION_KEY = "lang_user_translations" -def get_language(lang_list: list = None) -> str: +def get_language(lang_list: list | None = None) -> str: """Set `frappe.local.lang` from HTTP headers at beginning of request Order of priority for setting language: @@ -120,7 +120,7 @@ def get_parent_language(lang: str) -> str: return lang[: lang.index("-")] -def get_user_lang(user: str = None) -> str: +def get_user_lang(user: str | None = None) -> str: """Set frappe.local.lang from user preferences on session beginning or resumption""" user = user or frappe.session.user lang = frappe.cache.hget("lang", user) @@ -331,9 +331,7 @@ def get_translation_dict_from_file(path, lang, app, throw=False) -> dict[str, st elif len(item) in [2, 3]: translation_map[item[0]] = strip(item[1]) elif item: - msg = "Bad translation in '{app}' for language '{lang}': {values}".format( - app=app, lang=lang, values=cstr(item) - ) + msg = f"Bad translation in '{app}' for language '{lang}': {cstr(item)}" frappe.log_error(message=msg, title="Error in translation file") if throw: frappe.throw(msg, title="Error in translation file") @@ -457,7 +455,7 @@ def get_messages_from_doctype(name): if d.fieldtype == "Select" and d.options: options = d.options.split("\n") - if not "icon" in options[0]: + if "icon" not in options[0]: messages.extend(options) if d.fieldtype == "HTML" and d.options: messages.append(d.options) @@ -679,7 +677,7 @@ def get_all_messages_from_js_files(app_name=None): messages = [] for app in [app_name] if app_name else frappe.get_installed_apps(_ensure_on_bench=True): if os.path.exists(frappe.get_app_path(app, "public")): - for basepath, folders, files in os.walk(frappe.get_app_path(app, "public")): + for basepath, folders, files in os.walk(frappe.get_app_path(app, "public")): # noqa: B007 if "frappe/public/js/lib" in basepath: continue @@ -1089,6 +1087,7 @@ def restore_newlines(s): for key, value in zip( frappe.get_file_items(untranslated_file, ignore_empty_lines=False), frappe.get_file_items(translated_file, ignore_empty_lines=False), + strict=False, ): # undo hack in get_untranslated translation_dict[restore_newlines(key)] = restore_newlines(value) diff --git a/frappe/twofactor.py b/frappe/twofactor.py index 66bab40b67f..2909b730b71 100644 --- a/frappe/twofactor.py +++ b/frappe/twofactor.py @@ -221,7 +221,6 @@ def process_2fa_for_sms(user, token, otp_secret): def process_2fa_for_otp_app(user, otp_secret, otp_issuer): """Process OTP App method for 2fa.""" - totp_uri = pyotp.TOTP(otp_secret).provisioning_uri(user, issuer_name=otp_issuer) if get_default(user + "_otplogin"): otp_setup_completed = True else: diff --git a/frappe/utils/__init__.py b/frappe/utils/__init__.py index a2b9c63f769..0f79109d11b 100644 --- a/frappe/utils/__init__.py +++ b/frappe/utils/__init__.py @@ -234,7 +234,7 @@ def validate_url( # Handle scheme validation if isinstance(valid_schemes, str): is_valid = is_valid and (url.scheme == valid_schemes) - elif isinstance(valid_schemes, (list, tuple, set)): + elif isinstance(valid_schemes, list | tuple | set): is_valid = is_valid and (url.scheme in valid_schemes) if not is_valid and throw: @@ -351,7 +351,7 @@ def dict_to_str(args: dict[str, Any], sep: str = "&") -> str: """ Converts a dictionary to URL """ - return sep.join(f"{str(k)}=" + quote(str(args[k] or "")) for k in list(args)) + return sep.join(f"{k!s}=" + quote(str(args[k] or "")) for k in list(args)) def list_to_str(seq, sep=", "): @@ -385,7 +385,7 @@ def remove_blanks(d: dict) -> dict: Returns d with empty ('' or None) values stripped. Mutates inplace. """ for k, v in tuple(d.items()): - if v == "" or v == None: + if not v: del d[k] return d @@ -614,7 +614,7 @@ def update_progress_bar(txt, i, l, absolute=False): complete = int(float(i + 1) / l * col) completion_bar = ("=" * complete).ljust(col, " ") - percent_complete = f"{str(int(float(i + 1) / l * 100))}%" + percent_complete = f"{int(float(i + 1) / l * 100)!s}%" status = f"{i} of {l}" if absolute else percent_complete sys.stdout.write(f"\r{txt}: [{completion_bar}] {status}") sys.stdout.flush() @@ -648,7 +648,7 @@ def is_markdown(text): def is_a_property(x) -> bool: """Get properties (@property, @cached_property) in a controller class""" - return isinstance(x, (property, functools.cached_property)) + return isinstance(x, property | functools.cached_property) def get_sites(sites_path=None): @@ -894,7 +894,7 @@ def get_safe_filters(filters): try: filters = json.loads(filters) - if isinstance(filters, (int, float)): + if isinstance(filters, int | float): filters = frappe.as_unicode(filters) except (TypeError, ValueError): diff --git a/frappe/utils/background_jobs.py b/frappe/utils/background_jobs.py index 0b3d7ceda9e..81e4754c381 100755 --- a/frappe/utils/background_jobs.py +++ b/frappe/utils/background_jobs.py @@ -63,10 +63,10 @@ def enqueue( now: bool = False, enqueue_after_commit: bool = False, *, - on_success: Callable = None, - on_failure: Callable = None, + on_success: Callable | None = None, + on_failure: Callable | None = None, at_front: bool = False, - job_id: str = None, + job_id: str | None = None, deduplicate=False, **kwargs, ) -> Job | Any: @@ -259,7 +259,7 @@ def start_worker( rq_password: str | None = None, burst: bool = False, strategy: DequeueStrategy | None = DequeueStrategy.DEFAULT, -) -> NoReturn | None: # pragma: no cover +) -> None: # pragma: no cover """Wrapper to start rq worker. Connects to redis and monitors these queues.""" if not strategy: @@ -345,9 +345,7 @@ def get_worker_name(queue): if queue: # hostname.pid is the default worker name - name = "{uuid}.{hostname}.{pid}.{queue}".format( - uuid=uuid4().hex, hostname=socket.gethostname(), pid=os.getpid(), queue=queue - ) + name = f"{uuid4().hex}.{socket.gethostname()}.{os.getpid()}.{queue}" return name @@ -467,7 +465,7 @@ def get_redis_conn(username=None, password=None): raise except Exception as e: log( - f"Please make sure that Redis Queue runs @ {frappe.get_conf().redis_queue}. Redis reported error: {str(e)}", + f"Please make sure that Redis Queue runs @ {frappe.get_conf().redis_queue}. Redis reported error: {e!s}", colour="red", ) raise diff --git a/frappe/utils/backups.py b/frappe/utils/backups.py index b2d4716be1c..97aa4d75b6e 100644 --- a/frappe/utils/backups.py +++ b/frappe/utils/backups.py @@ -486,7 +486,7 @@ def send_email(self): db_backup_url = get_url(os.path.join("backups", os.path.basename(self.backup_path_db))) files_backup_url = get_url(os.path.join("backups", os.path.basename(self.backup_path_files))) - msg = """Hello, + msg = f"""Hello, Your backups are ready to be downloaded. @@ -494,10 +494,7 @@ def send_email(self): 2. [Click here to download the files backup]({files_backup_url}) This link will be valid for 24 hours. A new backup will be available for -download only after 24 hours.""".format( - db_backup_url=db_backup_url, - files_backup_url=files_backup_url, - ) +download only after 24 hours.""" datetime_str = datetime.fromtimestamp(os.stat(self.backup_path_db).st_ctime) subject = datetime_str.strftime("%d/%m/%Y %H:%M:%S") + """ - Backup ready to be downloaded""" diff --git a/frappe/utils/boilerplate.py b/frappe/utils/boilerplate.py index 812f84c3b27..3e4520aa99e 100644 --- a/frappe/utils/boilerplate.py +++ b/frappe/utils/boilerplate.py @@ -145,9 +145,7 @@ def _create_app_boilerplate(dest, hooks, no_git=False): with open(os.path.join(dest, hooks.app_name, "README.md"), "w") as f: f.write( frappe.as_unicode( - "## {}\n\n{}\n\n#### License\n\n{}".format( - hooks.app_title, hooks.app_description, hooks.app_license - ) + f"## {hooks.app_title}\n\n{hooks.app_description}\n\n#### License\n\n{hooks.app_license}" ) ) license_body = get_license_text(license_name=hooks.app_license) @@ -272,7 +270,7 @@ def create_patch_file(self): raise Exception(f"Patch {self.patch_file} already exists") *path, _filename = self.patch_file.relative_to(self.app_dir.parents[0]).parts - dotted_path = ".".join(path + [self.patch_file.stem]) + dotted_path = ".".join([*path, self.patch_file.stem]) patches_txt = self.app_dir / "patches.txt" existing_patches = patches_txt.read_text() diff --git a/frappe/utils/caching.py b/frappe/utils/caching.py index 6966a1b4791..ad501072c9c 100644 --- a/frappe/utils/caching.py +++ b/frappe/utils/caching.py @@ -85,7 +85,7 @@ def calculate_pi(): calculate_pi(10) # will calculate value """ - def time_cache_wrapper(func: Callable = None) -> Callable: + def time_cache_wrapper(func: Callable | None = None) -> Callable: func_key = f"{func.__module__}.{func.__name__}" def clear_cache(): @@ -138,7 +138,7 @@ def redis_cache(ttl: int | None = 3600, user: str | bool | None = None) -> Calla user: `true` should cache be specific to session user. """ - def wrapper(func: Callable = None) -> Callable: + def wrapper(func: Callable | None = None) -> Callable: func_key = f"{func.__module__}.{func.__qualname__}" def clear_cache(): diff --git a/frappe/utils/change_log.py b/frappe/utils/change_log.py index 3da9c5d757d..93f35d25cf7 100644 --- a/frappe/utils/change_log.py +++ b/frappe/utils/change_log.py @@ -118,9 +118,7 @@ def get_versions(): if versions[app]["branch"] != "master": branch_version = app_hooks.get("{}_version".format(versions[app]["branch"])) if branch_version: - versions[app]["branch_version"] = branch_version[0] + " ({})".format( - get_app_last_commit_ref(app) - ) + versions[app]["branch_version"] = branch_version[0] + f" ({get_app_last_commit_ref(app)})" try: versions[app]["version"] = frappe.get_attr(app + ".__version__") diff --git a/frappe/utils/csvutils.py b/frappe/utils/csvutils.py index 6199454312d..8a31a943264 100644 --- a/frappe/utils/csvutils.py +++ b/frappe/utils/csvutils.py @@ -174,7 +174,7 @@ def import_doc(d, doctype, overwrite, row_idx, submit=False, ignore_links=False) def getlink(doctype, name): - return '%(name)s' % locals() + return '{name}'.format(**locals()) def get_csv_content_from_google_sheets(url): diff --git a/frappe/utils/dashboard.py b/frappe/utils/dashboard.py index 4ec3f090fc2..5ff9d5d3423 100644 --- a/frappe/utils/dashboard.py +++ b/frappe/utils/dashboard.py @@ -72,7 +72,6 @@ def generate_and_cache_results(args, function, cache_key, chart): def get_dashboards_with_link(docname, doctype): - dashboards = [] links = [] if doctype == "Dashboard Chart": @@ -111,4 +110,4 @@ def make_records(path, filters=None): if os.path.isdir(join(path, fname)): if fname == "__pycache__": continue - import_file_by_path("{path}/{fname}/{fname}.json".format(path=path, fname=fname)) + import_file_by_path(f"{path}/{fname}/{fname}.json") diff --git a/frappe/utils/data.py b/frappe/utils/data.py index dc331d2ec66..d2137b6d49d 100644 --- a/frappe/utils/data.py +++ b/frappe/utils/data.py @@ -23,8 +23,8 @@ import frappe from frappe.desk.utils import slug -DateTimeLikeObject = Union[str, datetime.date, datetime.datetime] -NumericType = Union[int, float] +DateTimeLikeObject = str | datetime.date | datetime.datetime +NumericType = int | float if typing.TYPE_CHECKING: @@ -109,10 +109,10 @@ def get_datetime( if datetime_str is None: return now_datetime() - if isinstance(datetime_str, (datetime.datetime, datetime.timedelta)): + if isinstance(datetime_str, datetime.datetime | datetime.timedelta): return datetime_str - elif isinstance(datetime_str, (list, tuple)): + elif isinstance(datetime_str, list | tuple): return datetime.datetime(datetime_str) elif isinstance(datetime_str, datetime.date): @@ -1175,7 +1175,7 @@ def encode(obj, encoding="utf-8"): def parse_val(v): """Converts to simple datatypes from SQL query results""" - if isinstance(v, (datetime.date, datetime.datetime)): + if isinstance(v, datetime.date | datetime.datetime): v = str(v) elif isinstance(v, datetime.timedelta): v = ":".join(str(v).split(":")[:2]) @@ -1512,7 +1512,7 @@ def comma_and(some_list, add_quotes=True): def comma_sep(some_list, pattern, add_quotes=True): - if isinstance(some_list, (list, tuple)): + if isinstance(some_list, list | tuple): # list(some_list) is done to preserve the existing list some_list = [str(s) for s in list(some_list)] if not some_list: @@ -1527,7 +1527,7 @@ def comma_sep(some_list, pattern, add_quotes=True): def new_line_sep(some_list): - if isinstance(some_list, (list, tuple)): + if isinstance(some_list, list | tuple): # list(some_list) is done to preserve the existing list some_list = [str(s) for s in list(some_list)] if not some_list: @@ -1721,7 +1721,7 @@ def evaluate_filters(doc, filters: dict | list | tuple): if not compare(doc.get(f.fieldname), f.operator, f.value, f.fieldtype): return False - elif isinstance(filters, (list, tuple)): + elif isinstance(filters, list | tuple): for d in filters: f = get_filter(None, d) if not compare(doc.get(f.fieldname), f.operator, f.value, f.fieldtype): @@ -1758,7 +1758,7 @@ def get_filter(doctype: str, f: dict | list | tuple, filters_config=None) -> "fr key, value = next(iter(f.items())) f = make_filter_tuple(doctype, key, value) - if not isinstance(f, (list, tuple)): + if not isinstance(f, list | tuple): frappe.throw(frappe._("Filter must be a tuple or list (in a list)")) if len(f) == 3: @@ -1794,7 +1794,8 @@ def get_filter(doctype: str, f: dict | list | tuple, filters_config=None) -> "fr "timespan", "previous", "next", - ) + NestedSetHierarchy + *NestedSetHierarchy, + ) if filters_config: additional_operators = [key.lower() for key in filters_config] @@ -1825,7 +1826,7 @@ def get_filter(doctype: str, f: dict | list | tuple, filters_config=None) -> "fr def make_filter_tuple(doctype, key, value): """return a filter tuple like [doctype, key, operator, value]""" - if isinstance(value, (list, tuple)): + if isinstance(value, list | tuple): return [doctype, key, value[0], value[1]] else: return [doctype, key, "=", value] @@ -2174,7 +2175,7 @@ def parse_timedelta(s: str) -> datetime.timedelta: return datetime.timedelta(**{key: float(val) for key, val in m.groupdict().items()}) -def get_job_name(key: str, doctype: str = None, doc_name: str = None) -> str: +def get_job_name(key: str, doctype: str | None = None, doc_name: str | None = None) -> str: job_name = key if doctype: job_name += f"_{doctype}" diff --git a/frappe/utils/dateutils.py b/frappe/utils/dateutils.py index 73880d29074..26607aa861e 100644 --- a/frappe/utils/dateutils.py +++ b/frappe/utils/dateutils.py @@ -54,7 +54,7 @@ def parse_date(date): date = date.split(" ", 1)[0] # why the sorting? checking should be done in a predictable order - check_formats = [None] + sorted(list(dateformats), reverse=not get_user_date_format().startswith("dd")) + check_formats = [None, *sorted(list(dateformats), reverse=not get_user_date_format().startswith("dd"))] for f in check_formats: try: @@ -66,9 +66,8 @@ def parse_date(date): if not parsed_date: raise Exception( - """Cannot understand date - '%s'. - Try formatting it like your default format - '%s'""" - % (date, get_user_date_format()) + f"""Cannot understand date - '{date}'. + Try formatting it like your default format - '{get_user_date_format()}'""" ) return parsed_date diff --git a/frappe/utils/formatters.py b/frappe/utils/formatters.py index 12b3ee11be9..289a001cdec 100644 --- a/frappe/utils/formatters.py +++ b/frappe/utils/formatters.py @@ -108,7 +108,7 @@ def format_value(value, df=None, doc=None, currency=None, translated=False, form elif df.get("fieldtype") == "Table MultiSelect": values = [] meta = frappe.get_meta(df.options) - link_field = [df for df in meta.fields if df.fieldtype == "Link"][0] + link_field = next(df for df in meta.fields if df.fieldtype == "Link") for v in value: v.update({"__link_titles": doc.get("__link_titles")}) formatted_value = frappe.format_value(v.get(link_field.fieldname, ""), link_field, v) diff --git a/frappe/utils/global_search.py b/frappe/utils/global_search.py index cef7ab21888..955c3846a73 100644 --- a/frappe/utils/global_search.py +++ b/frappe/utils/global_search.py @@ -397,7 +397,7 @@ def sync_values(values: list): GlobalSearch = frappe.qb.Table("__global_search") conflict_fields = ["content", "published", "title", "route"] - query = frappe.qb.into(GlobalSearch).columns(["doctype", "name"] + conflict_fields).insert(*values) + query = frappe.qb.into(GlobalSearch).columns(["doctype", "name", *conflict_fields]).insert(*values) if frappe.db.db_type == "postgres": query = query.on_conflict(GlobalSearch.doctype, GlobalSearch.name) @@ -511,7 +511,7 @@ def search(text, start=0, limit=20, doctype=""): # sort results based on allowed_doctype's priority for doctype in allowed_doctypes: - for index, r in enumerate(results): + for r in results: if r.doctype == doctype and r.rank > 0.0: try: meta = frappe.get_meta(r.doctype) diff --git a/frappe/utils/image.py b/frappe/utils/image.py index 8544d4e5328..4a4b05e6db7 100644 --- a/frappe/utils/image.py +++ b/frappe/utils/image.py @@ -10,7 +10,7 @@ def resize_images(path, maxdim=700): size = (maxdim, maxdim) - for basepath, folders, files in os.walk(path): + for basepath, folders, files in os.walk(path): # noqa: B007 for fname in files: extn = fname.rsplit(".", 1)[1] if extn in ("jpg", "jpeg", "png", "gif"): diff --git a/frappe/utils/jinja_globals.py b/frappe/utils/jinja_globals.py index e1efb6d1dd6..848f41c5276 100644 --- a/frappe/utils/jinja_globals.py +++ b/frappe/utils/jinja_globals.py @@ -11,7 +11,7 @@ def resolve_class(*classes): if classes is False: return "" - if isinstance(classes, (list, tuple)): + if isinstance(classes, list | tuple): return " ".join(resolve_class(c) for c in classes).strip() if isinstance(classes, dict): diff --git a/frappe/utils/make_random.py b/frappe/utils/make_random.py index 381f2266a30..f982a332ec7 100644 --- a/frappe/utils/make_random.py +++ b/frappe/utils/make_random.py @@ -18,7 +18,7 @@ def add_random_children(doc: "Document", fieldname: str, rows, randomize: dict, if rows > 1: nrows = random.randrange(1, rows) - for i in range(nrows): + for _ in range(nrows): d = {} for key, val in randomize.items(): if isinstance(val[0], str): @@ -33,7 +33,7 @@ def add_random_children(doc: "Document", fieldname: str, rows, randomize: dict, doc.append(fieldname, d) -def get_random(doctype: str, filters: dict = None, doc: bool = False): +def get_random(doctype: str, filters: dict | None = None, doc: bool = False): condition = [] if filters: condition.extend("{}='{}'".format(key, str(val).replace("'", "'")) for key, val in filters.items()) @@ -41,12 +41,10 @@ def get_random(doctype: str, filters: dict = None, doc: bool = False): out = frappe.db.multisql( { - "mariadb": """select name from `tab%s` %s - order by RAND() limit 1 offset 0""" - % (doctype, condition), - "postgres": """select name from `tab%s` %s - order by RANDOM() limit 1 offset 0""" - % (doctype, condition), + "mariadb": f"""select name from `tab{doctype}` {condition} + order by RAND() limit 1 offset 0""", + "postgres": f"""select name from `tab{doctype}` {condition} + order by RANDOM() limit 1 offset 0""", } ) diff --git a/frappe/utils/oauth.py b/frappe/utils/oauth.py index c077b475ce0..adc34aea397 100644 --- a/frappe/utils/oauth.py +++ b/frappe/utils/oauth.py @@ -151,7 +151,7 @@ def get_info_via_oauth(provider: str, code: str, decoder: Callable | None = None if provider == "github" and not info.get("email"): emails = session.get("/user/emails", params=api_endpoint_args).json() - email_dict = list(filter(lambda x: x.get("primary"), emails))[0] + email_dict = next(filter(lambda x: x.get("primary"), emails)) info["email"] = email_dict.get("email") if not (info.get("email_verified") or info.get("email")): diff --git a/frappe/utils/print_format.py b/frappe/utils/print_format.py index 05f3a46e592..37bbba1746b 100644 --- a/frappe/utils/print_format.py +++ b/frappe/utils/print_format.py @@ -70,7 +70,7 @@ def download_multi_pdf(doctype, name, format=None, no_letterhead=False, letterhe result = json.loads(name) # Concatenating pdf files - for i, ss in enumerate(result): + for ss in result: pdf_writer = frappe.get_print( doctype, ss, diff --git a/frappe/utils/redis_wrapper.py b/frappe/utils/redis_wrapper.py index 45be0c63e81..5402ab3e54b 100644 --- a/frappe/utils/redis_wrapper.py +++ b/frappe/utils/redis_wrapper.py @@ -134,7 +134,7 @@ def delete_value(self, keys, user=None, make_keys=True, shared=False): if not keys: return - if not isinstance(keys, (list, tuple)): + if not isinstance(keys, list | tuple): keys = (keys,) if make_keys: diff --git a/frappe/utils/response.py b/frappe/utils/response.py index ffe3d2e5853..f61294744ab 100644 --- a/frappe/utils/response.py +++ b/frappe/utils/response.py @@ -205,7 +205,7 @@ def json_handler(obj): from collections.abc import Iterable from re import Match - if isinstance(obj, (datetime.date, datetime.datetime, datetime.time)): + if isinstance(obj, datetime.date | datetime.datetime | datetime.time): return str(obj) elif isinstance(obj, datetime.timedelta): @@ -232,7 +232,7 @@ def json_handler(obj): return repr(obj) else: - raise TypeError(f"""Object of type {type(obj)} with value of {repr(obj)} is not JSON serializable""") + raise TypeError(f"""Object of type {type(obj)} with value of {obj!r} is not JSON serializable""") def as_page(): diff --git a/frappe/utils/safe_exec.py b/frappe/utils/safe_exec.py index d7571849749..82a79c829f4 100644 --- a/frappe/utils/safe_exec.py +++ b/frappe/utils/safe_exec.py @@ -478,7 +478,7 @@ def _validate_attribute_read(object, name): if isinstance(name, str) and (name in UNSAFE_ATTRIBUTES): raise SyntaxError(f"{name} is an unsafe attribute") - if isinstance(object, (types.ModuleType, types.CodeType, types.TracebackType, types.FrameType)): + if isinstance(object, types.ModuleType | types.CodeType | types.TracebackType | types.FrameType): raise SyntaxError(f"Reading {object} attributes is not allowed") if name.startswith("_"): @@ -489,16 +489,14 @@ def _write(obj): # guard function for RestrictedPython if isinstance( obj, - ( - types.ModuleType, - types.CodeType, - types.TracebackType, - types.FrameType, - type, - types.FunctionType, # covers lambda - types.MethodType, - types.BuiltinFunctionType, # covers methods - ), + types.ModuleType + | types.CodeType + | types.TracebackType + | types.FrameType + | type + | types.FunctionType + | types.MethodType + | types.BuiltinFunctionType, ): raise SyntaxError(f"Not allowed to write to object {obj} of type {type(obj)}") return obj diff --git a/frappe/utils/typing_validations.py b/frappe/utils/typing_validations.py index ce521e996c9..c11507f0c97 100644 --- a/frappe/utils/typing_validations.py +++ b/frappe/utils/typing_validations.py @@ -1,7 +1,7 @@ from collections.abc import Callable from functools import lru_cache, wraps from inspect import _empty, isclass, signature -from types import EllipsisType, NoneType +from types import EllipsisType from typing import ForwardRef, TypeVar, Union from pydantic import ConfigDict @@ -52,7 +52,9 @@ def qualified_name(obj) -> str: return f"{module}.{qualname}" -def raise_type_error(arg_name: str, arg_type: type, arg_value: object, current_exception: Exception = None): +def raise_type_error( + arg_name: str, arg_type: type, arg_value: object, current_exception: Exception | None = None +): """ Raise a TypeError with a message that includes the name of the argument, the expected type and the actual type of the value passed. @@ -93,11 +95,11 @@ def transform_parameter_types(func: Callable, args: tuple, kwargs: dict): elif kwargs: arg_values = args or func.__defaults__ or [] - prepared_args = dict(zip(arg_names, arg_values)) + prepared_args = dict(zip(arg_names, arg_values, strict=False)) prepared_args.update(kwargs) else: - prepared_args = dict(zip(arg_names, args)) + prepared_args = dict(zip(arg_names, args, strict=False)) # check if type hints dont match the default values func_signature = signature(func) @@ -111,9 +113,9 @@ def transform_parameter_types(func: Callable, args: tuple, kwargs: dict): current_arg_value = prepared_args[current_arg] # if the type is a ForwardRef or str, ignore it - if isinstance(current_arg_type, (ForwardRef, str)): + if isinstance(current_arg_type, ForwardRef | str): continue - elif any(isinstance(x, (ForwardRef, str)) for x in getattr(current_arg_type, "__args__", [])): + elif any(isinstance(x, ForwardRef | str) for x in getattr(current_arg_type, "__args__", [])): continue # allow slack for Frappe types @@ -127,12 +129,12 @@ def transform_parameter_types(func: Callable, args: tuple, kwargs: dict): if isinstance(current_arg_type, tuple): if type(param_def.default) not in current_arg_type: current_arg_type += (type(param_def.default),) - current_arg_type = Union[current_arg_type] + current_arg_type = Union[current_arg_type] # noqa: UP007 elif param_def.default != current_arg_type: - current_arg_type = Union[current_arg_type, type(param_def.default)] + current_arg_type = Union[current_arg_type, type(param_def.default)] # noqa: UP007 elif isinstance(current_arg_type, tuple): - current_arg_type = Union[current_arg_type] + current_arg_type = Union[current_arg_type] # noqa: UP007 # validate the type set using pydantic - raise a TypeError if Validation is raised or Ellipsis is returned try: diff --git a/frappe/utils/user.py b/frappe/utils/user.py index 3bb0fe92b53..eb583958a7f 100644 --- a/frappe/utils/user.py +++ b/frappe/utils/user.py @@ -330,7 +330,7 @@ def add_system_manager( first_name: str | None = None, last_name: str | None = None, send_welcome_email: bool = False, - password: str = None, + password: str | None = None, ) -> "User": # add user user = frappe.new_doc("User") diff --git a/frappe/website/doctype/blog_post/blog_post.py b/frappe/website/doctype/blog_post/blog_post.py index b12d94dc3ab..a5840a87aa4 100644 --- a/frappe/website/doctype/blog_post/blog_post.py +++ b/frappe/website/doctype/blog_post/blog_post.py @@ -382,7 +382,7 @@ def get_blog_list(doctype, txt=None, filters=None, limit_start=0, limit_page_len if ( post.avatar - and (not "http:" in post.avatar and not "https:" in post.avatar) + and ("http:" not in post.avatar and "https:" not in post.avatar) and not post.avatar.startswith("/") ): post.avatar = "/" + post.avatar diff --git a/frappe/website/doctype/blog_post/test_blog_post.py b/frappe/website/doctype/blog_post/test_blog_post.py index b11f2f34972..1d13a26e905 100644 --- a/frappe/website/doctype/blog_post/test_blog_post.py +++ b/frappe/website/doctype/blog_post/test_blog_post.py @@ -62,7 +62,7 @@ def test_category_link(self): # On blog post page find link to the category page soup = BeautifulSoup(blog_page_html, "html.parser") - category_page_link = list(soup.find_all("a", href=re.compile(blog.blog_category)))[0] + category_page_link = next(iter(soup.find_all("a", href=re.compile(blog.blog_category)))) category_page_url = category_page_link["href"] cached_value = frappe.db.value_cache.get(("DocType", "Blog Post", "name")) @@ -84,7 +84,7 @@ def test_blog_pagination(self): # Create some Blog Posts for a Blog Category category_title, blogs, BLOG_COUNT = "List Category", [], 4 - for index in range(BLOG_COUNT): + for _ in range(BLOG_COUNT): blog = make_test_blog(category_title) blogs.append(blog) diff --git a/frappe/website/doctype/web_page/web_page.py b/frappe/website/doctype/web_page/web_page.py index c2188aa4203..fd2310be58b 100644 --- a/frappe/website/doctype/web_page/web_page.py +++ b/frappe/website/doctype/web_page/web_page.py @@ -126,7 +126,7 @@ def render_dynamic(self, context): frappe.flags.web_block_styles = {} try: context["main_section"] = render_template(context.main_section, context) - if not "" in context.main_section: + if "" not in context.main_section: context["no_cache"] = 1 except TemplateSyntaxError: raise @@ -138,13 +138,13 @@ def set_breadcrumbs(self, context): """Build breadcrumbs template""" if self.breadcrumbs: context.parents = frappe.safe_eval(self.breadcrumbs, {"_": _}) - if not "no_breadcrumbs" in context: + if "no_breadcrumbs" not in context: if "" in context.main_section: context.no_breadcrumbs = 1 def set_title_and_header(self, context): """Extract and set title and header from content or context.""" - if not "no_header" in context: + if "no_header" not in context: if "" in context.main_section: context.no_header = 1 diff --git a/frappe/website/doctype/website_settings/website_settings.py b/frappe/website/doctype/website_settings/website_settings.py index e97a4acd06c..0dc5f7f8059 100644 --- a/frappe/website/doctype/website_settings/website_settings.py +++ b/frappe/website/doctype/website_settings/website_settings.py @@ -236,7 +236,7 @@ def get_website_settings(context=None): for key in via_hooks: context[key] = via_hooks[key] if key not in ("top_bar_items", "footer_items", "post_login") and isinstance( - context[key], (list, tuple) + context[key], list | tuple ): context[key] = context[key][-1] diff --git a/frappe/website/page_renderers/template_page.py b/frappe/website/page_renderers/template_page.py index 84a9d260811..d3b2b075063 100644 --- a/frappe/website/page_renderers/template_page.py +++ b/frappe/website/page_renderers/template_page.py @@ -201,8 +201,8 @@ def set_properties_from_source(self): and "{% extends" not in self.source and "" not in self.source ): - self.source = """{{% extends "{0}" %}} - {{% block page_content %}}{1}{{% endblock %}}""".format(context.base_template, self.source) + self.source = f"""{{% extends "{context.base_template}" %}} + {{% block page_content %}}{self.source}{{% endblock %}}""" self.set_properties_via_comments() diff --git a/frappe/website/path_resolver.py b/frappe/website/path_resolver.py index 6c01c96e0a1..03c296d009e 100644 --- a/frappe/website/path_resolver.py +++ b/frappe/website/path_resolver.py @@ -49,7 +49,8 @@ def resolve(self): return endpoint, TemplatePage(endpoint, self.http_status_code) custom_renderers = self.get_custom_page_renderers() - renderers = custom_renderers + [ + renderers = [ + *custom_renderers, StaticPage, WebFormPage, DocumentPage, @@ -128,7 +129,7 @@ def resolve_redirect(path, query_string=None): try: match = re.match(pattern, path_to_match) - except re.error as e: + except re.error: frappe.log_error("Broken Redirect: " + pattern) if match: diff --git a/frappe/website/report/website_analytics/website_analytics.py b/frappe/website/report/website_analytics/website_analytics.py index a395e5e5828..916852f836e 100644 --- a/frappe/website/report/website_analytics/website_analytics.py +++ b/frappe/website/report/website_analytics/website_analytics.py @@ -80,16 +80,16 @@ def _get_query_for_mariadb(self): elif filters_range == "Monthly": date_format = "%Y-%m-01" - query = """ + query = f""" SELECT - DATE_FORMAT({0}, %s) as date, + DATE_FORMAT({field}, %s) as date, COUNT(*) as count, COUNT(CASE WHEN is_unique = 1 THEN 1 END) as unique_count FROM `tabWeb Page View` WHERE creation BETWEEN %s AND %s - GROUP BY DATE_FORMAT({0}, %s) + GROUP BY DATE_FORMAT({field}, %s) ORDER BY creation - """.format(field) + """ values = (date_format, self.filters.from_date, self.filters.to_date, date_format) @@ -106,16 +106,16 @@ def _get_query_for_postgres(self): elif filters_range == "Monthly": granularity = "day" - query = """ + query = f""" SELECT - DATE_TRUNC(%s, {0}) as date, + DATE_TRUNC(%s, {field}) as date, COUNT(*) as count, COUNT(CASE WHEN CAST(is_unique as Integer) = 1 THEN 1 END) as unique_count FROM "tabWeb Page View" - WHERE coalesce("tabWeb Page View".{0}, '0001-01-01') BETWEEN %s AND %s - GROUP BY date_trunc(%s, {0}) + WHERE coalesce("tabWeb Page View".{field}, '0001-01-01') BETWEEN %s AND %s + GROUP BY date_trunc(%s, {field}) ORDER BY date - """.format(field) + """ values = (granularity, self.filters.from_date, self.filters.to_date, granularity) diff --git a/frappe/website/router.py b/frappe/website/router.py index 576eded2944..4d944a03966 100644 --- a/frappe/website/router.py +++ b/frappe/website/router.py @@ -107,14 +107,14 @@ def get_pages_from_path(start, app, app_path): pages = {} start_path = os.path.join(app_path, start) if os.path.exists(start_path): - for basepath, folders, files in os.walk(start_path): + for basepath, folders, files in os.walk(start_path): # noqa: B007 # add missing __init__.py - if not "__init__.py" in files and frappe.conf.get("developer_mode"): + if "__init__.py" not in files and frappe.conf.get("developer_mode"): open(os.path.join(basepath, "__init__.py"), "a").close() for fname in files: fname = frappe.utils.cstr(fname) - if not "." in fname: + if "." not in fname: continue page_name, extn = fname.rsplit(".", 1) if extn in ("js", "css") and os.path.exists(os.path.join(basepath, page_name + ".html")): diff --git a/frappe/workflow/doctype/workflow/workflow.py b/frappe/workflow/doctype/workflow/workflow.py index def345e9669..9fe7465d0aa 100644 --- a/frappe/workflow/doctype/workflow/workflow.py +++ b/frappe/workflow/doctype/workflow/workflow.py @@ -69,14 +69,14 @@ def update_default_workflow_status(self): docstatus_map = {} states = self.get("states") for d in states: - if not d.doc_status in docstatus_map: + if d.doc_status not in docstatus_map: frappe.db.sql( - """ - UPDATE `tab{doctype}` - SET `{field}` = %s - WHERE ifnull(`{field}`, '') = '' + f""" + UPDATE `tab{self.document_type}` + SET `{self.workflow_state_field}` = %s + WHERE ifnull(`{self.workflow_state_field}`, '') = '' AND `docstatus` = %s - """.format(doctype=self.document_type, field=self.workflow_state_field), + """, (d.state, d.doc_status), ) diff --git a/frappe/workflow/doctype/workflow_action/workflow_action.py b/frappe/workflow/doctype/workflow_action/workflow_action.py index 283296761c7..45c5ded724e 100644 --- a/frappe/workflow/doctype/workflow_action/workflow_action.py +++ b/frappe/workflow/doctype/workflow_action/workflow_action.py @@ -73,10 +73,10 @@ def get_permission_query_conditions(user): .where(WorkflowActionPermittedRole.role.isin(roles)) ).get_sql() - return """(`tabWorkflow Action`.`name` in ({permitted_workflow_actions}) - or `tabWorkflow Action`.`user`={user}) + return f"""(`tabWorkflow Action`.`name` in ({permitted_workflow_actions}) + or `tabWorkflow Action`.`user`={frappe.db.escape(user)}) and `tabWorkflow Action`.`status`='Open' - """.format(permitted_workflow_actions=permitted_workflow_actions, user=frappe.db.escape(user)) + """ def has_permission(doc, user): diff --git a/frappe/www/list.py b/frappe/www/list.py index bd0db9fd053..8c29b7de2d1 100644 --- a/frappe/www/list.py +++ b/frappe/www/list.py @@ -129,9 +129,7 @@ def set_route(context): elif context.doc and getattr(context.doc, "route", None): context.route = context.doc.route else: - context.route = "{}/{}".format( - context.pathname or quoted(context.doc.doctype), quoted(context.doc.name) - ) + context.route = f"{context.pathname or quoted(context.doc.doctype)}/{quoted(context.doc.name)}" def prepare_filters(doctype, controller, kwargs): @@ -154,7 +152,7 @@ def prepare_filters(doctype, controller, kwargs): filters[key] = val # filter the filters to include valid fields only - for fieldname, val in list(filters.items()): + for fieldname in list(filters.keys()): if not meta.has_field(fieldname): del filters[fieldname] diff --git a/frappe/www/printview.py b/frappe/www/printview.py index 000b87529ce..0c51d13233a 100644 --- a/frappe/www/printview.py +++ b/frappe/www/printview.py @@ -453,7 +453,7 @@ def append_empty_field_dict_to_page_column(page): if df.fieldtype == "Section Break" or page == []: if len(page) > 1: - if page[-1]["has_data"] == False: + if not page[-1]["has_data"]: # truncate last section if empty del page[-1] diff --git a/frappe/www/qrcode.py b/frappe/www/qrcode.py index e6d683694f9..39156ce797a 100644 --- a/frappe/www/qrcode.py +++ b/frappe/www/qrcode.py @@ -18,7 +18,7 @@ def get_query_key(): query_string = frappe.local.request.query_string query = dict(parse_qsl(query_string)) query = {key.decode(): val.decode() for key, val in query.items()} - if not "k" in list(query): + if "k" not in list(query): frappe.throw(_("Not Permitted"), frappe.PermissionError) query = (query["k"]).strip() if False in [i.isalpha() or i.isdigit() for i in query]: diff --git a/frappe/www/search.py b/frappe/www/search.py index c59e64cf233..9c2fa4a11c7 100644 --- a/frappe/www/search.py +++ b/frappe/www/search.py @@ -20,7 +20,7 @@ def get_context(context): @frappe.whitelist(allow_guest=True) -def get_search_results(text: str, scope: str = None, start: int = 0, as_html: bool = False): +def get_search_results(text: str, scope: str | None = None, start: int = 0, as_html: bool = False): results = web_search(text, scope, start, limit=21) out = frappe._dict() diff --git a/pyproject.toml b/pyproject.toml index ab8bbbd26ac..7b793048b44 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -111,6 +111,7 @@ select = [ "I", "UP", "B", + "RUF", ] ignore = [ "B017", # assertRaises(Exception) - should be more specific @@ -127,9 +128,11 @@ ignore = [ "F722", # syntax error in forward type annotation "F821", # undefined name "W191", # indentation contains tabs + "RUF001", # string contains ambiguous unicode character ] [tool.ruff.format] quote-style = "double" indent-style = "tab" docstring-code-format = true + From 88a09be151df22bb278482b4e8a612c2a1afbfba Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 22 Feb 2024 11:32:44 +0000 Subject: [PATCH 17/48] fix: wrap read_only functions correctly (#25018) (#25020) `functools.wraps` preserves signature useful for introspection. (cherry picked from commit 1ff7b841408e71fe07ae2760cdbb2a2f9a453aad) Co-authored-by: Ankush Menat --- frappe/__init__.py | 1 + frappe/desk/notifications.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index 70ccb4a7a11..bc552e77848 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -853,6 +853,7 @@ def is_whitelisted(method): def read_only(): def innfn(fn): + @functools.wraps(fn) def wrapper_fn(*args, **kwargs): # frappe.read_only could be called from nested functions, in such cases don't swap the # connection again. diff --git a/frappe/desk/notifications.py b/frappe/desk/notifications.py index d29b31431d7..16a4d45263a 100644 --- a/frappe/desk/notifications.py +++ b/frappe/desk/notifications.py @@ -242,7 +242,7 @@ def get_filters_for(doctype): @frappe.whitelist() @frappe.read_only() -def get_open_count(doctype, name, items=None): +def get_open_count(doctype: str, name: str, items=None): """Get count for internal and external links for given transactions :param doctype: Reference DocType From d440feffdeee93ce6730598bdfb735e76914d7e6 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 22 Feb 2024 18:17:13 +0530 Subject: [PATCH 18/48] fix(grid): Add type attribute to buttons (#25021) (#25022) (cherry picked from commit 8deed2e519af46c321f2146e90ef1132d2a91ecf) Co-authored-by: Corentin Flr <10946971+cogk@users.noreply.github.com> --- frappe/public/js/frappe/form/grid.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/frappe/public/js/frappe/form/grid.js b/frappe/public/js/frappe/form/grid.js index eb11f7c7476..9ba4693588b 100644 --- a/frappe/public/js/frappe/form/grid.js +++ b/frappe/public/js/frappe/form/grid.js @@ -85,29 +85,29 @@ export default class Grid {