fix: keep normalize_ctx_input's ctx annotation on Python 3.14

Python 3.14 (PEP 649/749) replaced "__annotations__" with "__annotate__"
in functools.WRAPPER_ASSIGNMENTS. normalize_ctx_input excluded only
"__annotations__" when wrapping, so functools.wraps copied the wrapped
function's __annotate__ and the wrapper's permissive ctx annotation
(_dict | Document | dict | str) was overwritten by the narrow
ItemDetailsCtx | str. Now that Frappe casts whitelisted args via
typing_validations, a dict ctx failed the isinstance-only frappe._dict
check and raised FrappeTypeError. Exclude "__annotate__" too.

Cleanup while here:
- Merge the three identical frappe._dict aliases (ItemDetails,
  ItemDetailsCtx, ItemPriceCtx) into ItemDetailsCtx.
- Drop the now-redundant "| str" from decorated signatures; the
  decorator's wrapper union is what typing_validations enforces.
- Decorate get_batch_based_item_price with normalize_ctx_input instead
  of a manual parse_json, renaming its arg pctx -> ctx (JS caller
  updated) so a dict/string payload is normalized to frappe._dict.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Mihir Kandoi
2026-07-01 16:07:45 +05:30
parent 58e5755780
commit 6eeadbdbef
3 changed files with 30 additions and 32 deletions

View File

@@ -179,7 +179,9 @@ def normalize_ctx_input(T: type) -> callable:
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__"))
@functools.wraps(
func, assigned=(a for a in functools.WRAPPER_ASSIGNMENTS if a not in ("__annotations__", "__annotate__"))
)
def wrapper(ctx: T | Document | dict | str, *args, **kwargs):
if isinstance(ctx, Document):
ctx = T(**ctx.as_dict())