Closed ehiggs closed 1 year ago
@ehiggs thanks for reporting this issue.
As you probably know freezegun
monky patches datetime
within with
block, so yours config=Config({datetime: isoparse})
is config=Config({freezegun.api.FakeDatetime: isoparse})
but Foo.timestamp
is still datatime
from the Python standard library. Those types do not match so dacite
doesn't use this type hook at all.
This will work (both datetime
s are monky patched):
with freeze_time("2020-06-23T12:15:00.256Z"):
@dataclass
class Foo:
timestamp: datetime
from_dict(Foo, {"timestamp": "2022-04-04T15:37:51.258Z"}, config=Config({datetime: isoparse}))
TBH I don't see any clean solution - I don't want to add freezgun
as a dependency to my project.
Freezgun is really awesome library (I use it everyday) but because of this hacky monky patching it sometimes doesn't cooperate seamlessly with other tools.
I'm closing this issue for now, but feel free to comment/reopen/provide PR ;)
Thanks for the reply. The proposed solution is less than ideal because it means all imports for tests need to take place in a freeze_time context.
Digging into the code a bit more, the issue seems to be when we check if the type parsed correctly:
if config.check_types and not is_instance(value, field.type):
raise WrongTypeError(field_path=field.name, field_type=field.type, value=value)
So this should fire if the parsed type is not datetime
.
Let's make a dt of type FakeDatetime
:
>>> with freeze_time("2022-01-01"):
... dt = datetime.now()
...
>>> type(dt)
<class 'freezegun.api.FakeDatetime'>
>>> from freezegun.api import FakeDatetime
>>> isinstance(dt, FakeDatetime)
True
Great. Note, this is also isinstance
of datetime
:
>>> isinstance(dt, datetime)
True
But dacite uses a special is_instance
function:
>>> from dacite.types import is_instance
>>> is_instance(dt, datetime)
True
>>> is_instance(dt, FakeDatetime)
True
So a FakeDatetime IS a datetime. So why would this exception be raisd? 🤔
Problem is not that the type doesn't match, problem is that before that, the type hook was not triggered, leading the expected datetime
here to remain a str
type as you can also see in the first post's error message.
field "timestamp" - should be "datetime" instead of value "2022-04-04T15:37:51.258Z" of type "str"
That is caused by the fact that the list of type_hooks
we pass is defined like this: Config(type_hooks={datetime: isoparse})
[added the keyword arg for visibility). When run from a test with freezetime, the type datetime
monkey patched to be freezegun's FakeDatetime
. The call to transform_value
within from_dict
will now not match datetime
with FakeDatetime
, not do the expected transformation, thus returning the original string, which is obviously of type str
. This triggers the WrongTypeError
exception.
So this:
So a FakeDatetime IS a datetime. So why would this exception be raisd? 🤔
is because the type here is in fact not FakeDatetime
or datetime
, it is str
.
Doesn't solve them problem, but knowing this we can discuss whether the type_hooks
behaviour can be modified, as opposed to the type comparison.
A possible workaround to have freezegun and dacity both running:
# define a helper dict wrapper
class FreezegunCompatibleTypeHookWrapper(dict):
def __contains__(self, item):
return super().__contains__(item) or str(item) == "<class 'datetime.datetime'>"
def __getitem__(self, item):
if str(item) == "<class 'datetime.datetime'>":
# replace the datetime class with the (maybe monkeypatched) directly imported datetime module
# reason: https://github.com/konradhalas/dacite/issues/185#issuecomment-1433506657
item = datetime
return super().__getitem__(item)
# and then use:
Config(type_hooks=FreezegunCompatibleTypeHookWrapper({datetime: isoparse}))
freezegun is a super common library used for testing and basically indispensable. But it seems when working with dacite, it can run into issues. As follows:
dacite-1.6.0-py3-none-any.whl