-
Notifications
You must be signed in to change notification settings - Fork 5
Description
Right now, if you try to define an attributes model that uses a field with an alias, e.g.
class AliasedAttrs(BaseModel):
array_dimensions: tuple[str, ...] = Field(alias="_ARRAY_DIMENSIONS")Then calling GroupSpec.from_flat on a dict that contains a class that uses this attribute will fail, because the aliased field will not be serialized to dict by its alias (in the above example, the dict would be {"array_dimensions": ,...} instead of {"_ARRAY_DIMENSIONS": ..}
See this example:
# /// script
# requires-python = ">=3.11"
# dependencies = [
# "pydantic-zarr @ git+https://github.com/zarr-developers/pydantic-zarr.git",
# "pytest"
# ]
# ///
from typing import Any
from pydantic import BaseModel, ValidationError, Field
from pydantic_zarr.v2 import GroupSpec
import pytest
def test_from_flat_kwargs() -> None:
"""
Test that from_flat takes keyword arguments for `BaseModel.model_dump`. This is necessary for
handling fields with aliases properly.
"""
class AliasedAttrs(BaseModel):
array_dimensions: tuple[str, ...] = Field(alias="_ARRAY_DIMENSIONS")
class AliasedGroup(GroupSpec[AliasedAttrs, Any]): ...
flat = {"": AliasedGroup(attributes=AliasedAttrs(_ARRAY_DIMENSIONS=("a", "b")))}
msg = r"Field required \[type=missing, input_value=\{'array_dimensions': \('a', 'b'\)}, input_type=dict]"
with pytest.raises(ValidationError, match=msg):
AliasedGroup.from_flat(flat)
assert AliasedGroup.from_flat(flat) == flat[""]
if __name__ == "__main__":
pytest.main([__file__, '-c', 'foo.py'])There are two fixes possible for the code we have today
- Define the serialization behavior of aliases in the
model_configof the attribute class. This is only possible for newer versions of pydantic - Allow
from_flatto take keyword arguments formodel_dump; then you can callfrom_flat(data, by_alias=True), and this will work.
But it's weird that from_flat, which is about deserializing an object, takes keyword arguments for serializing an object. That's because GroupSpec.from_flat looks like this:
@classmethod
def from_flat(cls, data) -> Self:
# this creates an untyped GroupSpec, which we have to then dump, before creating
# our typed groupspec
from_flated = from_flat_group(data)
return cls(**from_flated.model_dump()) # here is where we could insert kwargs for model_dumpthere's an extra GroupSpec in there, that only exists because of our need to call from_flat_group. It would be nice to refactor this function to not require that intermediate groupspec.