diff --git a/frappe/recorder.py b/frappe/recorder.py index b00f15c6b5..537d1ee996 100644 --- a/frappe/recorder.py +++ b/frappe/recorder.py @@ -1,11 +1,13 @@ # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # License: MIT. See LICENSE import datetime +import functools import inspect import json import re import time from collections import Counter +from typing import Callable import sqlparse @@ -61,9 +63,9 @@ def get_current_stack_frames(): pass -def record(): +def record(force=False): if __debug__: - if frappe.cache().get_value(RECORDER_INTERCEPT_FLAG): + if frappe.cache().get_value(RECORDER_INTERCEPT_FLAG) or force: frappe.local._recorder = Recorder() @@ -78,11 +80,19 @@ class Recorder: self.uuid = frappe.generate_hash(length=10) self.time = datetime.datetime.now() self.calls = [] - self.path = frappe.request.path - self.cmd = frappe.local.form_dict.cmd or "" - self.method = frappe.request.method - self.headers = dict(frappe.local.request.headers) - self.form_dict = frappe.local.form_dict + if frappe.request: + self.path = frappe.request.path + self.cmd = frappe.local.form_dict.cmd or "" + self.method = frappe.request.method + self.headers = dict(frappe.local.request.headers) + self.form_dict = frappe.local.form_dict + else: + self.path = None + self.cmd = None + self.method = None + self.headers = None + self.form_dict = None + _patch() def register(self, data): @@ -125,6 +135,10 @@ def _patch(): frappe.db.sql = sql +def _unpatch(): + frappe.db.sql = frappe.db._sql + + def do_not_record(function): def wrapper(*args, **kwargs): if hasattr(frappe.local, "_recorder"): @@ -189,3 +203,19 @@ def export_data(*args, **kwargs): def delete(*args, **kwargs): frappe.cache().delete_value(RECORDER_REQUEST_SPARSE_HASH) frappe.cache().delete_value(RECORDER_REQUEST_HASH) + + +def record_queries(func: Callable): + """Decorator to profile a specific function using recorder.""" + + @functools.wraps(func) + def wrapped(*args, **kwargs): + record(force=True) + frappe.local._recorder.path = f"Function call: {func.__module__}.{func.__qualname__}" + ret = func(*args, **kwargs) + dump() + _unpatch() + print("Recorded queries, open recorder to view them.") + return ret + + return wrapped diff --git a/frappe/tests/test_recorder.py b/frappe/tests/test_recorder.py index a265ce9b1b..ffe3df4b23 100644 --- a/frappe/tests/test_recorder.py +++ b/frappe/tests/test_recorder.py @@ -123,3 +123,15 @@ class TestRecorder(FrappeTestCase): def test_error_page_rendering(self): content = get_response_content("error") self.assertIn("Error", content) + + +class TestRecorderDeco(FrappeTestCase): + def test_recorder_flag(self): + frappe.recorder.delete() + + @frappe.recorder.record_queries + def test(): + frappe.get_all("User") + + test() + self.assertTrue(frappe.recorder.get())