Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reconcile behavioral differences between SerializeAsAny annotation and serialize_as_any runtime flag #9049

Open
sydney-runkle opened this issue Mar 19, 2024 · 2 comments

Comments

@sydney-runkle
Copy link
Member

Note - the serialize_as_any runtime flag support will be released with 2.7, so this will need to be addressed once those changes are merged (see pydantic/pydantic-core#1194 and #8830)

Specifically, there are differences in regards to when serialization warnings are raised. For example:

The following test fails with warnings for passing str where int expected:

def test_serialize_as_any_list_types() -> None:
    serializer = SchemaSerializer(core_schema.list_schema(core_schema.int_schema()))

    assert serializer.to_python(['a', 'b', 'c'], serialize_as_any=True) == ['a', 'b', 'c']

The annotated code which is equivalent doesn't warn:

from pydantic import TypeAdapter, SerializeAsAny

TypeAdapter(SerializeAsAny[list[int]]).dump_python(["a", "b", "c"])

Ultimately, it'd be difficult to make the annotation behave more strictly here, given that it transforms the schema it wraps into an Any schema, which is quite permissive. Thus, in order to reconcile these differences, it probably makes the most sense for us to make the runtime flag more permissive.

One potential workaround that we considered was having the runtime flag named duck_type_serialization, but we ended up deciding not to go with this approach, as it's confusing for users to have "duck type serialization" and "serialize as any" as separate concepts. See this comment and surrounding comments for more detail.

@sydney-runkle
Copy link
Member Author

sydney-runkle commented Mar 19, 2024

Here's a case with unrelated models where the annotation usage vs runtime flag usage doesn't result in a behavioral discrepancy. This was one we were curious about given that the models are unrelated...

from pydantic import BaseModel, ConfigDict, TypeAdapter, SerializeAsAny

class Parent(BaseModel):
    x: int

class Other(BaseModel):
    y: str

    model_config = ConfigDict(extra='allow')

ta = TypeAdapter(Parent)
other = Other(x=1, y='hello')
print(ta.dump_python(other))
#> {}

print(ta.dump_python(other, serialize_as_any=False))
#> {}

print(ta.dump_python(other, serialize_as_any=True))
#> {'y': 'hello', 'x': 1}

ta = TypeAdapter(SerializeAsAny[Parent])
other = Other(x=1, y='hello')
print(ta.dump_python(other))
#> {'y': 'hello', 'x': 1}
# note: if extra='ignore', the default was set, we'd get {'y': 'hello'}

@sydney-runkle
Copy link
Member Author

Ah, here's another thing to note, though I'm not sure if this merits a change:

def test_serialize_as_any_with_inner_models() -> None:
    """As with other serialization flags, serialize_as_any affects nested models as well."""
    class Inner(BaseModel):
        x: int

    class Outer(BaseModel):
       inner: Inner
     
    class InnerChild(Inner):
         y: int

    ta = TypeAdapter(Outer)
    inner_child = InnerChild(x=1, y=2)
    outer = Outer(inner=inner_child)

    assert ta.dump_python(outer, serialize_as_any=False) == {'inner': {'x': 1}}
    assert ta.dump_python(outer, serialize_as_any=True) == {'inner': {'x': 1, 'y': 2}}


def test_serialize_as_any_annotation_with_inner_models() -> None:
    """The SerializeAsAny annotation does not affect nested models."""
    class Inner(BaseModel):
        x: int

    class Outer(BaseModel):
       inner: Inner
     
    class InnerChild(Inner):
         y: int

    ta = TypeAdapter(SerializeAsAny[Outer])
    inner_child = InnerChild(x=1, y=2)
    outer = Outer(inner=inner_child)
    assert ta.dump_python(outer) == {'inner': {'x': 1}}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant