Skip to content

Commit

Permalink
fix: pydantic cannot validate asdf TaggedDict (#41)
Browse files Browse the repository at this point in the history
ASDF passes TaggedDict as YAML tree node to converter. Due to its
multiple-inheritance implementation (UserDict, dict), Pydantic does not
properly validate it and gives missing field errors.
  • Loading branch information
ketozhang authored Nov 18, 2024
2 parents 537dca3 + 6bb80d8 commit 09f56ec
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 2 deletions.
10 changes: 8 additions & 2 deletions asdf_pydantic/model.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from typing import ClassVar
from typing import Any, ClassVar

import yaml
from asdf.extension import TagDefinition
from pydantic import BaseModel, ConfigDict
from asdf.tagged import TaggedDict
from pydantic import BaseModel, ConfigDict, model_validator
from typing_extensions import deprecated

from asdf_pydantic.schema import DEFAULT_ASDF_SCHEMA_REF_TEMPLATE, GenerateAsdfSchema
Expand Down Expand Up @@ -42,6 +43,11 @@ def asdf_yaml_tree(self) -> dict:

return d

@model_validator(mode="before")
@classmethod
def handle_asdf_tagged_dict_compat(cls, data: Any) -> dict:
return dict(data) if isinstance(data, TaggedDict) else data

@classmethod
def get_tag_definition(cls):
if isinstance(cls._tag, str):
Expand Down
59 changes: 59 additions & 0 deletions tests/examples/test_nested.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import asdf
import pytest
import yaml
from asdf.extension import Extension

from asdf_pydantic import AsdfPydanticConverter, AsdfPydanticModel


class MetaModel(AsdfPydanticModel):
_tag = "asdf://asdf-pydantic/examples/tags/meta-model-1.0.0"
author: str


class DataModel(AsdfPydanticModel):
_tag = "asdf://asdf-pydantic/examples/tags/data-model-1.0.0"
value: int


class MainModel(AsdfPydanticModel):
_tag = "asdf://asdf-pydantic/examples/tags/main-model-1.0.0"
meta: MetaModel
data: DataModel


@pytest.fixture()
def asdf_extension():
"""Registers an ASDF extension containing models for this test."""

converter = AsdfPydanticConverter()
converter.add_models(MainModel, DataModel, MetaModel)

class TestExtension(Extension):
extension_uri = "asdf://asdf-pydantic/examples/extensions/test-1.0.0"

converters = [converter] # type: ignore
tags = [MainModel.get_tag_definition()] # type: ignore

with asdf.config_context() as asdf_config:
asdf_config.add_resource_mapping(
{
yaml.safe_load(MainModel.model_asdf_schema())[
"id"
]: MainModel.model_asdf_schema()
}
)
asdf_config.add_extension(TestExtension())
yield asdf_config


@pytest.mark.usefixtures("asdf_extension")
def test_can_write_valid_asdf_file(tmp_path):
"""Tests using the model to write an ASDF file validates its own schema."""
af = asdf.AsdfFile()
af["root"] = MainModel(data=DataModel(value=1), meta=MetaModel(author="foobar"))
af.validate()
af.write_to(tmp_path / "test.asdf")

with asdf.open(tmp_path / "test.asdf") as af:
assert af.tree

0 comments on commit 09f56ec

Please sign in to comment.