From 962de86fb29923173315b2ede400cb8ea32c4151 Mon Sep 17 00:00:00 2001 From: Dmitry Pershin Date: Sat, 23 Nov 2024 22:16:36 +0500 Subject: [PATCH 1/4] pydantic 2.10 mypy plugin compatibility fixed. --- pydantic_xml/mypy.py | 74 ++++++++++++++++++++++++-------------------- 1 file changed, 41 insertions(+), 33 deletions(-) diff --git a/pydantic_xml/mypy.py b/pydantic_xml/mypy.py index d62e56a..5dde656 100644 --- a/pydantic_xml/mypy.py +++ b/pydantic_xml/mypy.py @@ -1,7 +1,7 @@ from typing import Callable, Optional, Tuple, Union from mypy import nodes -from mypy.plugin import ClassDefContext, FunctionContext, Plugin, Type +from mypy.plugin import ClassDefContext, Plugin from pydantic.mypy import PydanticModelTransformer, PydanticPlugin MODEL_METACLASS_FULLNAME = 'pydantic_xml.model.XmlModelMeta' @@ -21,38 +21,6 @@ def get_metaclass_hook(self, fullname: str) -> Optional[Callable[[ClassDefContex return self._pydantic_model_metaclass_marker_callback return super().get_metaclass_hook(fullname) - def get_function_hook(self, fullname: str) -> Optional[Callable[[FunctionContext], Type]]: - sym = self.lookup_fully_qualified(fullname) - if sym and sym.fullname == ATTR_FULLNAME: - return self._attribute_callback - elif sym and sym.fullname == ELEMENT_FULLNAME: - return self._element_callback - elif sym and sym.fullname == WRAPPED_FULLNAME: - return self._wrapped_callback - - return super().get_function_hook(fullname) - - def _attribute_callback(self, ctx: FunctionContext) -> Type: - return super()._pydantic_field_callback(self._pop_first_args(ctx, 2)) - - def _element_callback(self, ctx: FunctionContext) -> Type: - return super()._pydantic_field_callback(self._pop_first_args(ctx, 4)) - - def _wrapped_callback(self, ctx: FunctionContext) -> Type: - return super()._pydantic_field_callback(self._pop_first_args(ctx, 4)) - - def _pop_first_args(self, ctx: FunctionContext, num: int) -> FunctionContext: - return FunctionContext( - arg_types=ctx.arg_types[num:], - arg_kinds=ctx.arg_kinds[num:], - callee_arg_names=ctx.callee_arg_names[num:], - arg_names=ctx.arg_names[num:], - default_return_type=ctx.default_return_type, - args=ctx.args[num:], - context=ctx.context, - api=ctx.api, - ) - def _pydantic_model_class_maker_callback(self, ctx: ClassDefContext) -> bool: transformer = PydanticXmlModelTransformer(ctx.cls, ctx.reason, ctx.api, self.plugin_config) return transformer.transform() @@ -100,3 +68,43 @@ def get_alias_info(stmt: nodes.AssignmentStmt) -> Tuple[Union[str, None], bool]: return None, True return PydanticModelTransformer.get_alias_info(stmt) + + @staticmethod + def get_strict(stmt: nodes.AssignmentStmt) -> Optional[bool]: + expr = stmt.rvalue + if ( + isinstance(expr, nodes.CallExpr) and + isinstance(expr.callee, nodes.RefExpr) and + expr.callee.fullname in ENTITIES_FULLNAME + ): + for arg, name in zip(expr.args, expr.arg_names): + if name != 'strict': + continue + if isinstance(arg, nodes.NameExpr): + if arg.fullname == 'builtins.True': + return True + elif arg.fullname == 'builtins.False': + return False + return None + + return PydanticModelTransformer.get_strict(stmt) + + @staticmethod + def is_field_frozen(stmt: nodes.AssignmentStmt) -> bool: + expr = stmt.rvalue + if isinstance(expr, nodes.TempNode): + return False + + if not ( + isinstance(expr, nodes.CallExpr) and + isinstance(expr.callee, nodes.RefExpr) and + expr.callee.fullname in ENTITIES_FULLNAME + ): + return False + + for i, arg_name in enumerate(expr.arg_names): + if arg_name == 'frozen': + arg = expr.args[i] + return isinstance(arg, nodes.NameExpr) and arg.fullname == 'builtins.True' + + return PydanticModelTransformer.is_field_frozen(stmt) From 090660337887b99e876d4c700cf0159d94d37d51 Mon Sep 17 00:00:00 2001 From: Eugene Toder Date: Sat, 23 Nov 2024 01:00:03 -0500 Subject: [PATCH 2/4] Fix typos in examples --- examples/snippets/element_namespace.py | 4 ++-- examples/snippets/element_namespace_global.py | 4 ++-- examples/snippets/element_primitive.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/snippets/element_namespace.py b/examples/snippets/element_namespace.py index 3dd7ed4..08def87 100644 --- a/examples/snippets/element_namespace.py +++ b/examples/snippets/element_namespace.py @@ -11,7 +11,7 @@ class Company(BaseXmlModel, tag='company'): ns='co', nsmap={'co': 'http://www.company.com/co'}, ) - website: HttpUrl = element(tag='web-size') + website: HttpUrl = element(tag='web-site') # [model-end] @@ -19,7 +19,7 @@ class Company(BaseXmlModel, tag='company'): xml_doc = ''' 2002-03-14 - https://www.spacex.com + https://www.spacex.com ''' # [xml-end] diff --git a/examples/snippets/element_namespace_global.py b/examples/snippets/element_namespace_global.py index 5239db3..03f605f 100644 --- a/examples/snippets/element_namespace_global.py +++ b/examples/snippets/element_namespace_global.py @@ -13,7 +13,7 @@ class Company( nsmap={'co': 'http://www.company.com/co'}, ): founded: dt.date = element() - website: HttpUrl = element(tag='web-size', ns='co') + website: HttpUrl = element(tag='web-site', ns='co') # [model-end] @@ -21,7 +21,7 @@ class Company( xml_doc = ''' 2002-03-14 - https://www.spacex.com + https://www.spacex.com ''' # [xml-end] diff --git a/examples/snippets/element_primitive.py b/examples/snippets/element_primitive.py index eba6701..bd978c2 100644 --- a/examples/snippets/element_primitive.py +++ b/examples/snippets/element_primitive.py @@ -8,7 +8,7 @@ # [model-start] class Company(BaseXmlModel, tag='company'): founded: dt.date = element() - website: HttpUrl = element(tag='web-size') + website: HttpUrl = element(tag='web-site') # [model-end] @@ -16,7 +16,7 @@ class Company(BaseXmlModel, tag='company'): xml_doc = ''' 2002-03-14 - https://www.spacex.com + https://www.spacex.com ''' # [xml-end] From 8ff159a0e5742ae764045d3682d7137090e8083a Mon Sep 17 00:00:00 2001 From: Dennis Hilhorst Date: Mon, 18 Nov 2024 14:20:40 +0100 Subject: [PATCH 3/4] omit pydantic(-xml) fields when checking for model field validators --- pydantic_xml/model.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pydantic_xml/model.py b/pydantic_xml/model.py index 34ee69f..8e72409 100644 --- a/pydantic_xml/model.py +++ b/pydantic_xml/model.py @@ -429,7 +429,12 @@ def __init_subclass__( cls.__xml_field_serializers__ = {} cls.__xml_field_validators__ = {} - for attr_name in dir(cls): + + # find custom validators/serializers in all defined attributes + # though we want to skip any Base(Xml)Model attributes, as these can never be field + # serializers/validators, and getting certain pydantic fields, like __pydantic_post_init__ + # may cause recursion errors for recursive / self-referential models + for attr_name in set(dir(cls)) - set(dir(BaseXmlModel)): if func := getattr(cls, attr_name, None): if fields := getattr(func, '__xml_field_serializer__', None): for field in fields: From bb1b697f1962c71984c6311d2f9db8154af24176 Mon Sep 17 00:00:00 2001 From: Dmitry Pershin Date: Thu, 28 Nov 2024 23:50:11 +0500 Subject: [PATCH 4/4] bump version 2.14.1. --- CHANGELOG.rst | 8 ++++++++ pyproject.toml | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ffbb342..854aeeb 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,8 +1,16 @@ Changelog ========= +2.14.1 (2024-11-28) +------------------- + +- pydantic 2.10 mypy plugin compatibility fixed. See https://github.com/dapper91/pydantic-xml/issues/232 +- recursive model bug fixed. See https://github.com/dapper91/pydantic-xml/issues/227. + + 2.14.0 (2024-11-09) ------------------- + - union validation error location fixed. - potential memory leak fixed. See https://github.com/dapper91/pydantic-xml/issues/222. - python 3.13 support added. diff --git a/pyproject.toml b/pyproject.toml index 41dc73b..2109ed5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pydantic-xml" -version = "2.14.0" +version = "2.14.1" description = "pydantic xml extension" authors = ["Dmitry Pershin "] license = "Unlicense"