From e89e6826fdc47f6474e4906d37b260ef65709e0f Mon Sep 17 00:00:00 2001 From: Camillo Lugaresi Date: Sun, 5 May 2024 03:20:09 -0700 Subject: [PATCH 1/4] Fix add_field_serializer_for_reverse_relations clearing validators --- ormar/models/helpers/relations.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/ormar/models/helpers/relations.py b/ormar/models/helpers/relations.py index 823efc63a..29b2eea05 100644 --- a/ormar/models/helpers/relations.py +++ b/ormar/models/helpers/relations.py @@ -5,9 +5,7 @@ from pydantic import BaseModel, create_model, field_serializer from pydantic._internal._decorators import DecoratorInfos from pydantic.fields import FieldInfo -from pydantic_core.core_schema import ( - SerializerFunctionWrapHandler, -) +from pydantic_core.core_schema import SerializerFunctionWrapHandler import ormar from ormar import ForeignKey, ManyToMany @@ -188,7 +186,18 @@ def serialize( serialize ) setattr(to_model, f"serialize_{related_name}", decorator) - DecoratorInfos.build(to_model) + # DecoratorInfos.build will overwrite __pydantic_decorators__ on to_model, + # deleting the previous decorators. We need to save them and then merge them. + prev_decorators = getattr(to_model, "__pydantic_decorators__", DecoratorInfos()) + new_decorators = DecoratorInfos.build(to_model) + prev_decorators.validators.update(new_decorators.validators) + prev_decorators.field_validators.update(new_decorators.field_validators) + prev_decorators.root_validators.update(new_decorators.root_validators) + prev_decorators.field_serializers.update(new_decorators.field_serializers) + prev_decorators.model_serializers.update(new_decorators.model_serializers) + prev_decorators.model_validators.update(new_decorators.model_validators) + prev_decorators.computed_fields.update(new_decorators.computed_fields) + setattr(to_model, "__pydantic_decorators__", prev_decorators) def replace_models_with_copy( From 540069dcdb1fb7f66b28741a59ff21597bb4c6f4 Mon Sep 17 00:00:00 2001 From: Camillo Lugaresi Date: Mon, 27 May 2024 00:52:45 +0200 Subject: [PATCH 2/4] add test to check that validators are not removed --- ...st_reverse_relation_preserves_validator.py | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 tests/test_relations/test_reverse_relation_preserves_validator.py diff --git a/tests/test_relations/test_reverse_relation_preserves_validator.py b/tests/test_relations/test_reverse_relation_preserves_validator.py new file mode 100644 index 000000000..14c9a6f91 --- /dev/null +++ b/tests/test_relations/test_reverse_relation_preserves_validator.py @@ -0,0 +1,48 @@ +from typing import Optional + +import pytest_asyncio +from pydantic import field_validator + +import ormar +from tests.lifespan import init_tests +from tests.settings import create_config + +base_ormar_config = create_config() + + +class Author(ormar.Model): + ormar_config = base_ormar_config.copy() + + id: int = ormar.Integer(primary_key=True) + name: str = ormar.String(max_length=80) + + @field_validator("name", mode="before") + @classmethod + def validate_name(cls, v: str | list[str]) -> str: + if isinstance(v, list): + v = " ".join(v) + return v + + +class Post(ormar.Model): + ormar_config = base_ormar_config.copy() + + id: int = ormar.Integer(primary_key=True) + title: str = ormar.String(max_length=200) + author: Optional[Author] = ormar.ForeignKey(Author) + + +create_test_database = init_tests(base_ormar_config) + + +@pytest_asyncio.fixture(scope="function") +async def cleanup(): + yield + async with base_ormar_config.database: + await Post.objects.delete(each=True) + await Author.objects.delete(each=True) + + +def test_validator(): + author = Author(name=["Test", "Author"]) + assert author.name == "Test Author" From 4108fd0358ce1f272a119b32b4b32f8982cb39ad Mon Sep 17 00:00:00 2001 From: Camillo Lugaresi Date: Mon, 27 May 2024 11:04:30 +0200 Subject: [PATCH 3/4] compatibility with old python --- .../test_reverse_relation_preserves_validator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_relations/test_reverse_relation_preserves_validator.py b/tests/test_relations/test_reverse_relation_preserves_validator.py index 14c9a6f91..286c2706c 100644 --- a/tests/test_relations/test_reverse_relation_preserves_validator.py +++ b/tests/test_relations/test_reverse_relation_preserves_validator.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import List, Optional, Union import pytest_asyncio from pydantic import field_validator @@ -18,7 +18,7 @@ class Author(ormar.Model): @field_validator("name", mode="before") @classmethod - def validate_name(cls, v: str | list[str]) -> str: + def validate_name(cls, v: Union[str, List[str]]) -> str: if isinstance(v, list): v = " ".join(v) return v From b1d803a945687d37e6101e981615daaacbf859f5 Mon Sep 17 00:00:00 2001 From: collerek Date: Tue, 28 May 2024 09:08:08 +0200 Subject: [PATCH 4/4] fix test default values --- .../test_relations_with_nested_defaults.py | 20 ++++++++++++++++++- ...st_reverse_relation_preserves_validator.py | 2 +- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/tests/test_fastapi/test_relations_with_nested_defaults.py b/tests/test_fastapi/test_relations_with_nested_defaults.py index ebbb6c77f..ae0eedf44 100644 --- a/tests/test_fastapi/test_relations_with_nested_defaults.py +++ b/tests/test_fastapi/test_relations_with_nested_defaults.py @@ -97,7 +97,25 @@ async def test_related_with_defaults(sample_data): "year": 2021, } ], - "country": {"authors": [{"id": 1}], "id": 1}, + "country": { + "authors": [ + { + "books": [ + { + "author": {"id": 1}, + "id": 1, + "title": "Bug caused by " "default value", + "year": 2021, + } + ], + "country": {"id": 1}, + "id": 1, + "name": "bug", + "rating": 5, + } + ], + "id": 1, + }, "id": 1, "name": "bug", "rating": 5, diff --git a/tests/test_relations/test_reverse_relation_preserves_validator.py b/tests/test_relations/test_reverse_relation_preserves_validator.py index 286c2706c..6728d783a 100644 --- a/tests/test_relations/test_reverse_relation_preserves_validator.py +++ b/tests/test_relations/test_reverse_relation_preserves_validator.py @@ -1,9 +1,9 @@ from typing import List, Optional, Union +import ormar import pytest_asyncio from pydantic import field_validator -import ormar from tests.lifespan import init_tests from tests.settings import create_config