diff --git a/frappe/utils/typing_validations.py b/frappe/utils/typing_validations.py index 9f0b82d9d8..ebd67bdeeb 100644 --- a/frappe/utils/typing_validations.py +++ b/frappe/utils/typing_validations.py @@ -6,20 +6,23 @@ 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 = { bool: (int, bool, float), } T = TypeVar("T") +ForwardRefOrStr = ForwardRef | str 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. @@ -27,7 +30,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) @@ -76,11 +79,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 @@ -91,34 +94,29 @@ 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 - from pydantic import ValidationError as PyValidationError - - import frappe - - annotations = func.__annotations__ 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] @@ -131,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): @@ -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):