Merge pull request #33217 from sagarvora/perf-typing-validations

This commit is contained in:
Sagar Vora 2025-07-03 12:58:00 +00:00 committed by GitHub
commit ede9e6ed97
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -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):