From db246c0aa24b5de4ab18b2a4e4ef71c5b60e5083 Mon Sep 17 00:00:00 2001 From: Sagar Vora <16315650+sagarvora@users.noreply.github.com> Date: Thu, 3 Jul 2025 17:29:31 +0530 Subject: [PATCH 1/4] refactor: use global imports --- frappe/utils/typing_validations.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/frappe/utils/typing_validations.py b/frappe/utils/typing_validations.py index 9f0b82d9d8..408fd0f691 100644 --- a/frappe/utils/typing_validations.py +++ b/frappe/utils/typing_validations.py @@ -6,8 +6,10 @@ from typing import ForwardRef, TypeVar, Union from unittest import mock from pydantic import ConfigDict, PydanticUserError -from pydantic import TypeAdapter as PyTypeAdapter +from pydantic import TypeAdapter as PydanticTypeAdapter +from pydantic import ValidationError as PydanticValidationError +import frappe from frappe.exceptions import FrappeTypeError SLACK_DICT = { @@ -76,11 +78,11 @@ def raise_type_error( @lru_cache(maxsize=2048) def TypeAdapter(type_): try: - return PyTypeAdapter(type_, config=FrappePydanticConfig) + return PydanticTypeAdapter(type_, config=FrappePydanticConfig) except PydanticUserError as e: # Cannot set config for types BaseModel, TypedDict and dataclass if e.code == "type-adapter-config-unused": - return PyTypeAdapter(type_) + return PydanticTypeAdapter(type_) raise e @@ -99,10 +101,6 @@ def transform_parameter_types(func: Callable, args: tuple, kwargs: dict): ): return args, kwargs - from pydantic import ValidationError as PyValidationError - - import frappe - annotations = func.__annotations__ new_args, new_kwargs = list(args), kwargs @@ -160,7 +158,7 @@ def transform_parameter_types(func: Callable, args: tuple, kwargs: dict): # validate the type set using pydantic - raise a TypeError if Validation is raised or Ellipsis is returned try: current_arg_value_after = TypeAdapter(current_arg_type).validate_python(current_arg_value) - except (TypeError, PyValidationError) as e: + except (TypeError, PydanticValidationError) as e: raise_type_error(func, current_arg, current_arg_type, current_arg_value, current_exception=e) if isinstance(current_arg_value_after, EllipsisType): From f922af8001ca9a6cb021c05a60cd03b86da72cf0 Mon Sep 17 00:00:00 2001 From: Sagar Vora <16315650+sagarvora@users.noreply.github.com> Date: Thu, 3 Jul 2025 17:30:30 +0530 Subject: [PATCH 2/4] refactor: avoid using callable as default --- frappe/utils/typing_validations.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/utils/typing_validations.py b/frappe/utils/typing_validations.py index 408fd0f691..b4ada1b50a 100644 --- a/frappe/utils/typing_validations.py +++ b/frappe/utils/typing_validations.py @@ -21,7 +21,7 @@ T = TypeVar("T") FrappePydanticConfig = ConfigDict(arbitrary_types_allowed=True) -def validate_argument_types(func: Callable, apply_condition: Callable = lambda: True): +def validate_argument_types(func: Callable, apply_condition: Callable | None = None): @wraps(func) def wrapper(*args, **kwargs): """Validate argument types of whitelisted functions. @@ -29,7 +29,7 @@ def validate_argument_types(func: Callable, apply_condition: Callable = lambda: :param args: Function arguments. :param kwargs: Function keyword arguments.""" - if apply_condition(): + if apply_condition is None or apply_condition(): args, kwargs = transform_parameter_types(func, args, kwargs) return func(*args, **kwargs) From 9634fa58b0db2703c45d13e366fe566f234d34a2 Mon Sep 17 00:00:00 2001 From: Sagar Vora <16315650+sagarvora@users.noreply.github.com> Date: Thu, 3 Jul 2025 17:52:17 +0530 Subject: [PATCH 3/4] refactor: define `annotations` earlier --- frappe/utils/typing_validations.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/frappe/utils/typing_validations.py b/frappe/utils/typing_validations.py index b4ada1b50a..a6a1c8c097 100644 --- a/frappe/utils/typing_validations.py +++ b/frappe/utils/typing_validations.py @@ -93,15 +93,16 @@ def transform_parameter_types(func: Callable, args: tuple, kwargs: dict): defined on the function. """ + annotations = func.__annotations__ + if ( not (args or kwargs) - or not func.__annotations__ + or not annotations # No input validations to perform - or (len(func.__annotations__) == 1 and func.__annotations__.get("return")) + or (len(annotations) == 1 and "return" in annotations) ): return args, kwargs - annotations = func.__annotations__ new_args, new_kwargs = list(args), kwargs # generate kwargs dict from args From e14218a6712ef3cb1569ddc63e99d4b539cfc88a Mon Sep 17 00:00:00 2001 From: Sagar Vora <16315650+sagarvora@users.noreply.github.com> Date: Thu, 3 Jul 2025 18:02:10 +0530 Subject: [PATCH 4/4] refactor: simplify `prepared_args` creation, use prepared union type --- frappe/utils/typing_validations.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/frappe/utils/typing_validations.py b/frappe/utils/typing_validations.py index a6a1c8c097..ebd67bdeeb 100644 --- a/frappe/utils/typing_validations.py +++ b/frappe/utils/typing_validations.py @@ -16,6 +16,7 @@ SLACK_DICT = { bool: (int, bool, float), } T = TypeVar("T") +ForwardRefOrStr = ForwardRef | str FrappePydanticConfig = ConfigDict(arbitrary_types_allowed=True) @@ -105,19 +106,17 @@ def transform_parameter_types(func: Callable, args: tuple, kwargs: dict): new_args, new_kwargs = list(args), kwargs - # generate kwargs dict from args - arg_names = func.__code__.co_varnames[: func.__code__.co_argcount] + if args: + # generate kwargs dict from args + arg_names = func.__code__.co_varnames[: func.__code__.co_argcount] + prepared_args = dict(zip(arg_names, args, strict=False)) - if not args: - prepared_args = kwargs - - elif kwargs: - arg_values = args or func.__defaults__ or [] - prepared_args = dict(zip(arg_names, arg_values, strict=False)) - prepared_args.update(kwargs) + if kwargs: + # update prepared_args with kwargs + prepared_args.update(kwargs) else: - prepared_args = dict(zip(arg_names, args, strict=False)) + prepared_args = kwargs # check if type hints dont match the default values func_params = frappe._get_cached_signature_params(func)[0] @@ -130,9 +129,9 @@ def transform_parameter_types(func: Callable, args: tuple, kwargs: dict): current_arg_value = prepared_args[current_arg] # if the type is a ForwardRef or str, ignore it - if isinstance(current_arg_type, ForwardRef | str): + if isinstance(current_arg_type, ForwardRefOrStr): continue - elif any(isinstance(x, ForwardRef | str) for x in getattr(current_arg_type, "__args__", [])): + elif any(isinstance(x, ForwardRefOrStr) for x in getattr(current_arg_type, "__args__", [])): continue # ignore unittest.mock objects elif isinstance(current_arg_value, mock.Mock):