Description
Hi,
when we upgraded to V2 recently, we were surprised to find that pydantic now upcasts e.g. instances of a subclass of str
to str
. I searched recent issues about it and only found one (Issue 7201: Incorrect parsing of bson.Int64) about the upcasting topic in general. A comment on the fixing PR (#914) from @davidhewitt said
Personally I think we want to go the other way and not upcast in both, I already can't remember why we wanted the upcast in lax mode.
So, my question is: Is the upcast really necessary (in lax mode)?
I see two problems with it:
- The behavior is different for builtins (or their subclasses) and regular classes (e.g. datetime or user-defined classes).
- Some valid use cases of subclasses of builtins are obstructed.
Our use case for subclasses of e.g. str
is pretty straightforward. We often compare pydantic objects in our tests, but most of the time we are only interested in matching a subset of the fields. Thus, we came up with "matcher" classes that work like this:
from datetime import datetime
import pydantic
# Production code
class StrModel(pydantic.BaseModel):
string: str
class DateModel(pydantic.BaseModel):
date: datetime
# Test code
class AnyStr(str):
def __eq__(self, other):
return isinstance(other, str)
# Failing test
def test_any_str_matches():
assert StrModel(string=AnyStr()) == StrModel(string="baz")
assert StrModel(string="baz") == StrModel(string=AnyStr())
class AnyDatetime(datetime):
def __new__(cls):
obj = datetime.__new__(cls, 2000, 1, 1, 0, 0, 0)
return obj
def __eq__(self, other):
return isinstance(other, datetime)
# Working test
def test_any_datetime_matches():
assert DateModel(date=AnyDatetime()) == DateModel(date=datetime.now())
assert DateModel(date=datetime.now()) == DateModel(date=AnyDatetime())
As our use case is "test-only", we do not want to litter the production code with special annotations/validators that might enable the use of the builtin subclasses.
Cheers,
Martin