refactor(postgres): memoise representative lines with frappe's request_cache

Three helpers each managed their own dictionary on frappe.local, duplicating
cache lifecycle and key handling. @request_cache does the same thing centrally
and is cleared with the request, so the copies cannot drift apart.

Behaviour is unchanged: the decorator keys on the call arguments, which are the
same tuple each hand-rolled key was built from.
This commit is contained in:
Mihir Kandoi
2026-08-03 01:03:11 +05:30
parent 100d0ee784
commit c8adf9937b
3 changed files with 6 additions and 26 deletions

View File

@@ -11,6 +11,7 @@ from frappe.model.document import Document
from frappe.query_builder import Field
from frappe.query_builder.functions import Count, IfNull, Max, Min, NullIf, Sum
from frappe.utils import cint, cstr, flt, get_link_to_form, parse_json
from frappe.utils.caching import request_cache
from frappe.website.website_generator import WebsiteGenerator
import erpnext
@@ -1253,17 +1254,10 @@ def _apply_representative_lines(rows, doctype, bom, keys):
row[column] = line.get(column)
@request_cache
def _representative_lines(doctype, bom, keys, columns):
"""Cached per request: get_bom_items_as_dict recurses through phantom BOMs, and the same
sub-BOM is commonly reached more than once."""
cache = getattr(frappe.local, "_bom_representative_lines", None)
if cache is None:
cache = frappe.local._bom_representative_lines = {}
cache_key = (doctype, bom, keys, columns)
if cache_key in cache:
return cache[cache_key]
representative = {}
for line in frappe.get_all(
doctype,
@@ -1273,7 +1267,6 @@ def _representative_lines(doctype, bom, keys, columns):
):
representative.setdefault(tuple(line.get(key) for key in keys), line)
cache[cache_key] = representative
return representative

View File

@@ -5,6 +5,7 @@
import frappe
from frappe.query_builder.functions import Count, IfNull, Max, Min, Sum
from frappe.utils.caching import request_cache
from erpnext.manufacturing.doctype.production_plan.services.planning_queries import get_uom_conversion_factor
@@ -67,16 +68,9 @@ def _apply_representative_lines(rows, doctype, bom_no, keys, include_non_stock_i
row.source_warehouse = line.source_warehouse
@request_cache
def _representative_lines(doctype, bom_no, keys, include_non_stock_items):
"""Cached per request: the explosion recurses and commonly revisits the same sub-BOM."""
cache = getattr(frappe.local, "_bom_explosion_representative_lines", None)
if cache is None:
cache = frappe.local._bom_explosion_representative_lines = {}
cache_key = (doctype, bom_no, keys, include_non_stock_items)
if cache_key in cache:
return cache[cache_key]
# only BOM Item carries is_phantom_item, and only its query ORs the phantom flag into the stock
# filter; the explosion table has neither
filters_phantom = doctype == "BOM Item"
@@ -112,7 +106,6 @@ def _representative_lines(doctype, bom_no, keys, include_non_stock_items):
for line in lines:
representative.setdefault(tuple(line.get(key) for key in keys), line)
cache[cache_key] = representative
return representative

View File

@@ -6,6 +6,7 @@
import frappe
from frappe.query_builder.functions import Count, IfNull, Max, Sum
from frappe.utils import flt
from frappe.utils.caching import request_cache
from erpnext.manufacturing.doctype.bom.bom import get_children as get_bom_children
from erpnext.manufacturing.doctype.production_plan.services.planning_queries import (
@@ -207,15 +208,9 @@ def _apply_representative_lines(rows, bom_no):
row.source_warehouse = line.source_warehouse
@request_cache
def _representative_lines(bom_no, keys):
"""Cached per request: sub-assembly resolution recurses and revisits the same BOM."""
cache = getattr(frappe.local, "_sub_assembly_representative_lines", None)
if cache is None:
cache = frappe.local._sub_assembly_representative_lines = {}
if bom_no in cache:
return cache[bom_no]
representative = {}
for line in frappe.get_all(
"BOM Item",
@@ -230,7 +225,6 @@ def _representative_lines(bom_no, keys):
):
representative.setdefault(tuple(line.get(key) for key in keys), line)
cache[bom_no] = representative
return representative