Skip to content

Commit

Permalink
refactor: make stem utility for normalizing ctx args from dict json-s…
Browse files Browse the repository at this point in the history
…tr or Document (#44228)
  • Loading branch information
blaggacao authored Nov 19, 2024
1 parent af2a622 commit 4ec23b5
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 26 deletions.
33 changes: 33 additions & 0 deletions erpnext/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import functools
import inspect
from typing import TypeVar

import frappe
from frappe.model.document import Document
from frappe.utils.user import is_website_user

__version__ = "16.0.0-dev"
Expand Down Expand Up @@ -160,3 +162,34 @@ def check_app_permission():
return False

return True


T = TypeVar("T")


def normalize_ctx_input(T: type) -> callable:
"""
Normalizes the first argument (ctx) of the decorated function by:
- Converting Document objects to dictionaries
- Parsing JSON strings
- Casting the result to the specified type T
"""

def decorator(func: callable):
# conserve annotations for frappe.utils.typing_validations
@functools.wraps(func, assigned=(a for a in functools.WRAPPER_ASSIGNMENTS if a != "__annotations__"))
def wrapper(ctx: T | Document | dict | str, *args, **kwargs):
if isinstance(ctx, Document):
ctx = T(**ctx.as_dict())
elif isinstance(ctx, dict):
ctx = T(**ctx)
else:
ctx = T(**frappe.parse_json(ctx))

return func(ctx, *args, **kwargs)

# set annotations from function
wrapper.__annotations__.update({k: v for k, v in func.__annotations__.items() if k != "ctx"})
return wrapper

return decorator
38 changes: 12 additions & 26 deletions erpnext/stock/get_item_details.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from frappe.query_builder.functions import IfNull, Sum
from frappe.utils import add_days, add_months, cint, cstr, flt, getdate, parse_json

import erpnext
from erpnext import get_company_currency
from erpnext.accounts.doctype.pricing_rule.pricing_rule import (
get_pricing_rule_for_item,
Expand All @@ -40,21 +41,6 @@
]


def type_narrow_ctx_arg(func: callable) -> callable:
# conserve annotations for frappe.utils.typing_validations
@wraps(func, assigned=(a for a in WRAPPER_ASSIGNMENTS if a != "__annotations__"))
def wrapper(ctx: ItemDetailsCtx | Document | dict | str, *args, **kwargs):
ctx: ItemDetailsCtx = parse_json(ctx)
if isinstance(ctx, Document):
ctx = ctx.as_dict()

return func(ctx, *args, **kwargs)

# set annotations from function
wrapper.__annotations__.update({k: v for k, v in func.__annotations__.items() if k != "ctx"})
return wrapper


def _preprocess_ctx(ctx):
if not ctx.price_list:
ctx.price_list = ctx.selling_price_list or ctx.buying_price_list
Expand All @@ -68,7 +54,7 @@ def _preprocess_ctx(ctx):


@frappe.whitelist()
@type_narrow_ctx_arg
@erpnext.normalize_ctx_input(ItemDetailsCtx)
def get_item_details(
ctx: ItemDetailsCtx, doc=None, for_validate=False, overwrite_warehouse=True
) -> ItemDetails:
Expand Down Expand Up @@ -468,7 +454,7 @@ def get_basic_details(ctx: ItemDetailsCtx, item, overwrite_warehouse=True) -> It
from erpnext.deprecation_dumpster import get_item_warehouse


@type_narrow_ctx_arg
@erpnext.normalize_ctx_input(ItemDetailsCtx)
def get_item_warehouse_(ctx: ItemDetailsCtx, item, overwrite_warehouse, defaults=None):
if not defaults:
defaults = frappe._dict(
Expand Down Expand Up @@ -583,7 +569,7 @@ def get_item_tax_info(company, tax_category, item_codes, item_rates=None, item_t
return out


@type_narrow_ctx_arg
@erpnext.normalize_ctx_input(ItemDetailsCtx)
def get_item_tax_template(ctx: ItemDetailsCtx, item, out: ItemDetails):
"""
Determines item_tax template from item or parent item groups.
Expand Down Expand Up @@ -617,7 +603,7 @@ def get_item_tax_template(ctx: ItemDetailsCtx, item, out: ItemDetails):
out.update(get_fetch_values(ctx.get("child_doctype"), "item_tax_template", item_tax_template))


@type_narrow_ctx_arg
@erpnext.normalize_ctx_input(ItemDetailsCtx)
def _get_item_tax_template(
ctx: ItemDetailsCtx, taxes, out: ItemDetails | None = None, for_validate=False
) -> None | str | list[str]:
Expand Down Expand Up @@ -684,7 +670,7 @@ def _get_item_tax_template(
return None


@type_narrow_ctx_arg
@erpnext.normalize_ctx_input(ItemDetailsCtx)
def is_within_valid_range(ctx: ItemDetailsCtx, tax) -> bool:
"""
Accesses:
Expand Down Expand Up @@ -715,7 +701,7 @@ def get_item_tax_map(company, item_tax_template, as_json=True):


@frappe.whitelist()
@type_narrow_ctx_arg
@erpnext.normalize_ctx_input(ItemDetailsCtx)
def calculate_service_end_date(ctx: ItemDetailsCtx, item=None):
_preprocess_ctx(ctx)
if not item:
Expand Down Expand Up @@ -791,7 +777,7 @@ def get_default_deferred_account(ctx: ItemDetailsCtx, item, fieldname=None):
return None


@type_narrow_ctx_arg
@erpnext.normalize_ctx_input(ItemDetailsCtx)
def get_default_cost_center(ctx: ItemDetailsCtx, item=None, item_group=None, brand=None, company=None):
cost_center = None

Expand Down Expand Up @@ -1007,7 +993,7 @@ def get_batch_based_item_price(pctx: ItemPriceCtx | dict | str, item_code) -> fl
return 0.0


@type_narrow_ctx_arg
@erpnext.normalize_ctx_input(ItemDetailsCtx)
def get_price_list_rate_for(ctx: ItemDetailsCtx, item_code):
"""
:param customer: link to Customer DocType
Expand Down Expand Up @@ -1148,7 +1134,7 @@ def get_party_item_code(ctx: ItemDetailsCtx, item_doc, out: ItemDetails):
from erpnext.deprecation_dumpster import get_pos_profile_item_details


@type_narrow_ctx_arg
@erpnext.normalize_ctx_input(ItemDetailsCtx)
def get_pos_profile_item_details_(ctx: ItemDetailsCtx, company, pos_profile=None, update_data=False):
res = frappe._dict()

Expand Down Expand Up @@ -1278,7 +1264,7 @@ def get_batch_qty(batch_no, warehouse, item_code):


@frappe.whitelist()
@type_narrow_ctx_arg
@erpnext.normalize_ctx_input(ItemDetailsCtx)
def apply_price_list(ctx: ItemDetailsCtx, as_doc=False, doc=None):
"""Apply pricelist on a document-like dict object and return as
{'parent': dict, 'children': list}
Expand Down Expand Up @@ -1452,7 +1438,7 @@ def update_party_blanket_order(ctx: ItemDetailsCtx, out: ItemDetails | dict):


@frappe.whitelist()
@type_narrow_ctx_arg
@erpnext.normalize_ctx_input(ItemDetailsCtx)
def get_blanket_order_details(ctx: ItemDetailsCtx):
blanket_order_details = None

Expand Down

0 comments on commit 4ec23b5

Please sign in to comment.