I wrote a small utilitarian function to convert a list of class instances to a list of dictionaries. The implementation is the following:
from pydantic import RootModel, BaseModel
def convert_pydantic_list_to_dict_list(pydantic_sequence):
return RootModel(pydantic_sequence).model_dump()
# For the sake of the example
class MyModel(BaseModel): ...
However, I figured that this function accepts both list of pydantic models (that are children of BaseModel) or tuple of pydantic models. I tried to write overloads of this function like this:
@overload
def convert_pydantic_list_to_dict_list(
pydantic_list: list[BaseModel],
) -> list[dict]: ...
@overload
def convert_pydantic_list_to_dict_list(
pydantic_list: tuple[BaseModel],
) -> tuple[dict]: ...
but I got a mypy error when I was passing a list of models
var = list[MyModel(), MyModel()]
convert_pydantic_list_to_dict_list(var)
# Argument 1 to "convert_pydantic_list_to_dict_list" has incompatible type
# "list[MyModel]"; expected "list[BaseModel]".
After reading some documentation about invariance, covariance, etc., I figured that the solution was lying in TypeVars. The following works:
T = TypeVar("T", bound=BaseModel)
@overload
def convert_pydantic_list_to_dict_list(pydantic_list: list[T]) -> list[dict]: ...
@overload
def convert_pydantic_list_to_dict_list(pydantic_list: tuple[T]) -> tuple[dict]: ...
I don’t fully understand why bounding a typevar to BaseModel (saying that it can be BaseModel or any of its sub class) then typing the function with list[T]
, is different from directly typing the function with list[BaseModel]
.
In both case the list is invariant, and the type its elements can have are sub type of BaseModel
, isn’t it? Is it because of contravariance in callable arguments?
Thank you for your time.