Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion beanie/odm/utils/encoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,11 @@ def _iter_model_items(
for key, value in obj.__iter__():
field_info = get_model_field(key)
if field_info is not None:
key = field_info.alias or key
key = (
getattr(field_info, "serialization_alias", None)
or field_info.alias
or key
)
if key not in exclude and (value is not None or keep_nulls):
yield key, value

Expand Down
8 changes: 5 additions & 3 deletions beanie/odm/utils/init.py
Original file line number Diff line number Diff line change
Expand Up @@ -403,7 +403,7 @@ def init_document_fields(self, cls) -> None:
if cls._link_fields is None:
cls._link_fields = {}
for k, v in get_model_fields(cls).items():
path = v.alias or k
path = getattr(v, "serialization_alias", None) or v.alias or k
setattr(cls, k, ExpressionField(path))
Comment on lines +406 to 407
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Somehow this comment got lost...
This would be a breaking change, and probably would not be correct if someone used the validation_alias instead to initialize the model...


link_info = self.detect_link(v, k)
Expand Down Expand Up @@ -516,7 +516,9 @@ async def init_indexes(self, cls, allow_index_dropping: bool = False):
IndexModel(
[
(
fvalue.alias or k,
getattr(fvalue, "serialization_alias", None)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Likewise here, this would be a breaking change if the user does not have the allow_index_dropping=True flag set to recreate the indexes with the new preferred names.

Please add some more tests here around indexes and for these scenarios, e.g. model with previous indexes without the serialization alias, and another model (copy of the previous one) that has the serialization_aliases set, check what happens with indexes on the DB side...

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

got it, thanks for the detailed overview, will look into it.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@staticxterm yeah, you were right, I first created a model with a field having index, but no serialization_alias, and it created the correct index, then I modified the field to have a serialization_alias, and it also created a index according to the serialization_alias but doesn't drop the previous one, for it to gets drop, I tried allow_index_dropping=True and then it drops that index.

or fvalue.alias
or k,
indexed_attrs[0],
)
],
Expand Down Expand Up @@ -639,7 +641,7 @@ def init_view_fields(self, cls) -> None:
if cls._link_fields is None:
cls._link_fields = {}
for k, v in get_model_fields(cls).items():
path = v.alias or k
path = getattr(v, "serialization_alias", None) or v.alias or k
setattr(cls, k, ExpressionField(path))
link_info = self.detect_link(v, k)
depth_level = cls.get_settings().max_nesting_depths_per_field.get(
Expand Down
4 changes: 3 additions & 1 deletion beanie/odm/utils/projection.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,7 @@ def get_projection(
document_projection: Dict[str, int] = {}

for name, field in get_model_fields(model).items():
document_projection[field.alias or name] = 1
document_projection[
getattr(field, "serialization_alias", None) or field.alias or name
] = 1
return document_projection
4 changes: 4 additions & 0 deletions beanie/odm/utils/pydantic.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ def get_model_fields(model):

def parse_model(model_type: Type[BaseModel], data: Any):
if IS_PYDANTIC_V2:
for k, field in get_model_fields(model_type).items():
if field.alias and field.alias != field.serialization_alias:
data[k] = data[field.serialization_alias]
del data[field.serialization_alias]
return model_type.model_validate(data)
else:
return model_type.parse_obj(data)
Expand Down
2 changes: 2 additions & 0 deletions tests/odm/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
DocumentTestModelWithIndexFlagsAliases,
DocumentTestModelWithLink,
DocumentTestModelWithModelConfigExtraAllow,
DocumentTestModelWithSerializationAlias,
DocumentTestModelWithSimpleIndex,
DocumentTestModelWithSoftDelete,
DocumentToBeLinked,
Expand Down Expand Up @@ -115,6 +116,7 @@
DocumentWithExtras,
DocumentWithPydanticConfig,
DocumentTestModel,
DocumentTestModelWithSerializationAlias,
DocumentTestModelWithSoftDelete,
DocumentTestModelWithLink,
DocumentTestModelWithCustomCollectionName,
Expand Down
5 changes: 5 additions & 0 deletions tests/odm/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,11 @@ class Settings:
use_state_management = True


class DocumentTestModelWithSerializationAlias(Document):
test_int: int = Field(serialization_alias="test_int_serialize")
test_str: str = Field(serialization_alias="test_str_serialize")


class DocumentTestModelWithLink(Document):
test_link: Link[DocumentTestModel]

Expand Down
27 changes: 27 additions & 0 deletions tests/odm/test_beanie_serialization.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import pytest

from beanie.odm.utils.pydantic import IS_PYDANTIC_V2
from tests.odm.models import DocumentTestModelWithSerializationAlias


def data_maker():
return DocumentTestModelWithSerializationAlias(test_int=1, test_str="test")


@pytest.mark.skipif(
not IS_PYDANTIC_V2,
reason="model serialization_alias is not supported in pydantic V1",
)
async def test_serialization_types_preserved_after_insertion():
result = await DocumentTestModelWithSerializationAlias.insert_one(
data_maker()
)
document = await DocumentTestModelWithSerializationAlias.get(result.id)
assert document is not None
assert document.test_int is not None
assert document.test_str is not None
dumped = document.model_dump(by_alias=True)
assert "test_int_serialize" in dumped
assert "test_str_serialize" in dumped
assert "test_int" not in dumped
assert "test_str" not in dumped