1 March 2024

Pydantic (and FastAPI which depends heavily on it) seems to be one of the most popular python libraries lately. I did some work with it over the past year, and am now working to extricate it from all of our code. It's really convenient to use at first, but the deeper I got into it, the more problems I ran into. In the end I've come to the conclusion that the problems it solves are better solved with dataclasses and msgspec.

Here are some of the reasons why I think Pydantic is a hot mess.

V1 to V2 Transition Poor

Pretty much no thought seems to have been given to upgrading existing codebases. Most libraries seem to choose a cut-off version after which they go from supporting only v1 to only v2. Litestar managed to support both versions, but it required this mess.

Obviously this isn't an ongoing problem, unless future breaking changes are handled in the same way.

Validation with Serialization and Deserialization for Custom Objects

Good luck! For example, I wanted to deserialize a list of "Record" (another pydantic model) types into a custom list object that has some helper functions. This is what I ended up with (minus the helper functions):

class RecordList(UserList[Record]):
    @classmethod
    def __get_pydantic_core_schema__(
        cls,
        source_type: Any,  # noqa: ANN401
        handler: pydantic.GetCoreSchemaHandler,
    ) -> cs.CoreSchema:
        # Tell pydantic that it can validate this class with the same validator it would use for `Sequence[Record]`
        base_schema = handler.generate_schema(Sequence[Record])
        return cs.no_info_after_validator_function(
            function=cls,
            schema=base_schema,
            serialization=cs.plain_serializer_function_ser_schema(lambda x: list(x), return_schema=base_schema),
        )

The documentation on how to do this is very incomplete and difficult to follow.

Comments from the Author of Cattrs

Certainly biased, and some of this is out of date with pydantic 2, but a most of it is still valid.

https://threeofwands.com/why-i-use-attrs-instead-of-pydantic/

Handling of Sets

This is considered not a bug.

import pydantic

class SubModel(pydantic.BaseModel, frozen=True):
    v: str = "foo"

class Model(pydantic.BaseModel):
    sub: set[SubModel]

m = Model(sub={SubModel()})

m.model_dump() # Fails with TypeError - dict is not hashable

msgspec/dataclasses do not have this problem

import msgspec

@dataclass(frozen=True)
class SubDC:
    v: str = "foo"

@dataclass
class DC:
    sub: set[SubDC]

obj = DC(sub={SubDC()})
js = msgspec.json.encode(obj)
obj1 = msgspec.json.decode(js, type=DC)
assert obj1 == obj