From fa7ea4bce8604f3835f0831a9e952ae66832bf73 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Sat, 23 Apr 2022 09:01:20 +0530 Subject: [PATCH] perf(BaseDocument): ~50% faster `as_dict` (#16549) Co-authored-by: Pruthvi Patel --- frappe/model/__init__.py | 3 ++ frappe/model/base_document.py | 75 +++++++++++++++++------------------ 2 files changed, 39 insertions(+), 39 deletions(-) diff --git a/frappe/model/__init__.py b/frappe/model/__init__.py index bd607e7119..570df4dab8 100644 --- a/frappe/model/__init__.py +++ b/frappe/model/__init__.py @@ -40,6 +40,9 @@ data_fieldtypes = ( "JSON", ) +float_like_fields = {"Float", "Currency", "Percent"} +datetime_fields = {"Datetime", "Date", "Time"} + attachment_fieldtypes = ( "Attach", "Attach Image", diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py index f8d60d0763..93446fb99e 100644 --- a/frappe/model/base_document.py +++ b/frappe/model/base_document.py @@ -6,7 +6,14 @@ from typing import Dict, List import frappe from frappe import _ -from frappe.model import child_table_fields, default_fields, display_fieldtypes, table_fields +from frappe.model import ( + child_table_fields, + datetime_fields, + default_fields, + display_fieldtypes, + float_like_fields, + table_fields, +) from frappe.model.docstatus import DocStatus from frappe.model.naming import set_new_name from frappe.model.utils.link_count import notify_link_count @@ -256,7 +263,8 @@ class BaseDocument(object): ) -> Dict: d = frappe._dict() for fieldname in self.meta.get_valid_columns(): - d[fieldname] = self.get(fieldname) + # column is valid, we can use getattr + d[fieldname] = getattr(self, fieldname, None) # if no need for sanitization and value is None, continue if not sanitize and d[fieldname] is None: @@ -264,25 +272,24 @@ class BaseDocument(object): df = self.meta.get_field(fieldname) - if df and df.get("is_virtual"): - if ignore_virtual: - del d[fieldname] - continue + if df: + if getattr(df, "is_virtual", False): + if ignore_virtual: + del d[fieldname] + continue - from frappe.utils.safe_exec import get_safe_globals + if d[fieldname] is None and (options := getattr(df, "options", None)): + from frappe.utils.safe_exec import get_safe_globals - if d[fieldname] is None: - if df.get("options"): d[fieldname] = frappe.safe_eval( - code=df.get("options"), + code=options, eval_globals=get_safe_globals(), eval_locals={"doc": self}, ) - else: - _val = getattr(self, fieldname, None) - if _val and not callable(_val): - d[fieldname] = _val - elif df: + + if isinstance(d[fieldname], list) and df.fieldtype not in table_fields: + frappe.throw(_("Value for {0} cannot be a list").format(_(df.label))) + if df.fieldtype == "Check": d[fieldname] = 1 if cint(d[fieldname]) else 0 @@ -292,25 +299,20 @@ class BaseDocument(object): elif df.fieldtype == "JSON" and isinstance(d[fieldname], dict): d[fieldname] = json.dumps(d[fieldname], sort_keys=True, indent=4, separators=(",", ": ")) - elif df.fieldtype in ("Currency", "Float", "Percent") and not isinstance(d[fieldname], float): + elif df.fieldtype in float_like_fields and not isinstance(d[fieldname], float): d[fieldname] = flt(d[fieldname]) - elif df.fieldtype in ("Datetime", "Date", "Time") and d[fieldname] == "": + elif (df.fieldtype in datetime_fields and d[fieldname] == "") or ( + getattr(df, "unique", False) and cstr(d[fieldname]).strip() == "" + ): d[fieldname] = None - elif df.get("unique") and cstr(d[fieldname]).strip() == "": - # unique empty field should be set to None - d[fieldname] = None - - if isinstance(d[fieldname], list) and df.fieldtype not in table_fields: - frappe.throw(_("Value for {0} cannot be a list").format(_(df.label))) - if convert_dates_to_str and isinstance( d[fieldname], (datetime.datetime, datetime.date, datetime.time, datetime.timedelta) ): d[fieldname] = str(d[fieldname]) - if d[fieldname] is None and ignore_nulls: + if ignore_nulls and d[fieldname] is None: del d[fieldname] return d @@ -361,7 +363,7 @@ class BaseDocument(object): convert_dates_to_str=False, no_child_table_fields=False, ) -> Dict: - doc = self.get_valid_dict(convert_dates_to_str=convert_dates_to_str) + doc = self.get_valid_dict(convert_dates_to_str=convert_dates_to_str, ignore_nulls=no_nulls) doc["doctype"] = self.doctype for df in self.meta.get_table_fields(): @@ -376,20 +378,15 @@ class BaseDocument(object): for d in children ] - if no_nulls: - for k in list(doc): - if doc[k] is None: - del doc[k] - if no_default_fields: - for k in list(doc): - if k in default_fields: - del doc[k] + for key in default_fields: + if key in doc: + del doc[key] if no_child_table_fields: - for k in list(doc): - if k in child_table_fields: - del doc[k] + for key in child_table_fields: + if key in doc: + del doc[key] for key in ( "_user_tags", @@ -399,8 +396,8 @@ class BaseDocument(object): "__run_link_triggers", "__unsaved", ): - if self.get(key): - doc[key] = self.get(key) + if value := getattr(self, key, None): + doc[key] = value return doc