Skip to content

Commit

Permalink
Merge pull request frappe#21276 from gavindsouza/bump-pydantic-v2
Browse files Browse the repository at this point in the history
build(deps): Bump Pydantic from v1 to v2
  • Loading branch information
mergify[bot] committed Jul 2, 2023
2 parents a633987 + 35039ca commit 721035b
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 32 deletions.
2 changes: 2 additions & 0 deletions frappe/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@

# If gc.freeze is done then importing modules before forking allows us to share the memory
if frappe._tune_gc:
import pydantic

import frappe.boot
import frappe.client
import frappe.core.doctype.user.user
Expand Down
41 changes: 41 additions & 0 deletions frappe/tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -1163,3 +1163,44 @@ def test_bankers_rounding_property(self, number, precision):

def test_default_rounding(self):
self.assertEqual(frappe.get_system_settings("rounding_method"), "Banker's Rounding")


class TestTypingValidations(FrappeTestCase):
def test_validate_argument_types(self):
from frappe.core.doctype.doctype.doctype import DocType
from frappe.utils.typing_validations import FrappeTypeError, validate_argument_types

@validate_argument_types
def test_simple_types(a: int, b: float, c: bool):
return a, b, c

@validate_argument_types
def test_sequence(a: str, b: list[dict] | None = None, c: dict[str, int] | None = None):
return a, b, c

@validate_argument_types
def test_doctypes(a: DocType | dict):
return a

self.assertEqual(test_simple_types(True, 2.0, True), (1, 2.0, True))
self.assertEqual(test_simple_types(1, 2, 1), (1, 2.0, True))
self.assertEqual(test_simple_types(1.0, 2, 1), (1, 2.0, True))
self.assertEqual(test_simple_types(1, 2, "1"), (1, 2.0, True))
with self.assertRaises(FrappeTypeError):
test_simple_types(1, 2, "a")
with self.assertRaises(FrappeTypeError):
test_simple_types(1, 2, None)

self.assertEqual(test_sequence("a", [{"a": 1}], {"a": 1}), ("a", [{"a": 1}], {"a": 1}))
self.assertEqual(test_sequence("a", None, None), ("a", None, None))
self.assertEqual(test_sequence("a", [{"a": 1}], None), ("a", [{"a": 1}], None))
self.assertEqual(test_sequence("a", None, {"a": 1}), ("a", None, {"a": 1}))
self.assertEqual(test_sequence("a", [{"a": 1}], {"a": "1.0"}), ("a", [{"a": 1}], {"a": 1}))
with self.assertRaises(FrappeTypeError):
test_sequence("a", [{"a": 1}], True)

doctype = frappe.get_last_doc("DocType")
self.assertEqual(test_doctypes(doctype), doctype)
self.assertEqual(test_doctypes(doctype.as_dict()), doctype.as_dict())
with self.assertRaises(FrappeTypeError):
test_doctypes("a")
39 changes: 8 additions & 31 deletions frappe/utils/typing_validations.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
from functools import lru_cache, wraps
from inspect import _empty, isclass, signature
from types import EllipsisType
from typing import Any, Callable, ForwardRef, TypeVar, Union

from pydantic.config import BaseConfig
from pydantic.error_wrappers import ValidationError as PyValidationError
from pydantic.tools import NameFactory, _generate_parsing_type_name
from typing import Callable, ForwardRef, TypeVar, Union

from frappe.exceptions import FrappeTypeError

Expand Down Expand Up @@ -69,29 +65,10 @@ def raise_type_error(


@lru_cache(maxsize=2048)
def _get_parsing_type(
type_: Any, *, type_name: NameFactory | None = None, config: type[BaseConfig] = None
) -> Any:
# Note: this is a copy of pydantic.tools._get_parsing_type with the addition of allowing a config argument
from pydantic.main import create_model

if type_name is None:
type_name = _generate_parsing_type_name
if not isinstance(type_name, str):
type_name = type_name(type_)
return create_model(type_name, __root__=(type_, ...), __config__=config)


def parse_obj_as(
type_: type[T],
obj: Any,
*,
type_name: NameFactory | None = None,
config: type[BaseConfig] | None = None,
) -> T:
# Note: This is a copy of pydantic.tools.parse_obj_as with the addition of allowing a config argument
model_type = _get_parsing_type(type_, type_name=type_name, config=config) # type: ignore[arg-type]
return model_type(__root__=obj).__root__
def TypeAdapter(type_):
from pydantic import TypeAdapter as PyTypeAdapter

return PyTypeAdapter(type_, config=FrappePydanticConfig)


def transform_parameter_types(func: Callable, args: tuple, kwargs: dict):
Expand All @@ -103,6 +80,8 @@ def transform_parameter_types(func: Callable, args: tuple, kwargs: dict):
if not (args or kwargs) or not func.__annotations__:
return args, kwargs

from pydantic import ValidationError as PyValidationError

annotations = func.__annotations__
new_args, new_kwargs = list(args), kwargs

Expand Down Expand Up @@ -157,9 +136,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 = parse_obj_as(
current_arg_type, current_arg_value, type_name=current_arg, config=FrappePydanticConfig
)
current_arg_value_after = TypeAdapter(current_arg_type).validate_python(current_arg_value)
except (TypeError, PyValidationError) as e:
raise_type_error(current_arg, current_arg_type, current_arg_value, current_exception=e)

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ dependencies = [
"psycopg2-binary~=2.9.1",
"pyOpenSSL~=23.2.0",
"pycryptodome~=3.18.0",
"pydantic~=1.10.8",
"pydantic==2.0",
"pyotp~=2.8.0",
"python-dateutil~=2.8.2",
"pytz==2023.3",
Expand Down

0 comments on commit 721035b

Please sign in to comment.