Allow passing custom globals to use when converting dataclasses
Description
Currently there's an issue where msgspec cannot convert a dataclass that contains forward references. This isn't unusual or msgspec specific, it is however a problem when trying to use msgspec to convert dataclasses that you don't have control over, i.e. cannot change the fact that they contain forward references.
# a.py
from __future__ import annotations
from dataclasses import dataclass
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from typing import Any
@dataclass
class Some:
value: Any
# b.py
from a import Some
import msgspec
# this fails with NameError: name 'Any' is not defined
msgspec.convert({"value": "something"}, type=Some)
A simple interface that would allow passing a custom set of globals to the internally used get_class_annotations would do the trick I think. Or is there another solution here that I'm not aware of? :)
A possible workaround is injecting the types imported under the TYPE_CHECKING condition directly into the module. This prevents the error without modifying the a.py. Example b.py:
# b.py
from a import Some
import msgspec
# Insert the type `Any` into module containing class `Some`
import sys
from typing import Any
a_module = sys.modules.get(Some.__module__)
a_module.__dict__['Any'] = Any
# No longer fails
msgspec.convert({"value": "something"}, type=Some)
But it is quite ugly workaround.
At some point __future__ == datetime.datetime.now() and all annotations will be like this.
It's more about if TYPE_CHECKING: which removes those definitions at runtime, and AFAIK is unnecessary with that __future__ import
No. The __future__ import makes it possible to do the if TYPE_CHECKING: at all; without that import, you'd get a NameError already in the a module instead of the convert call.
PS: I guess we're both right... Ditching the if TYPE_CHECKING would fix it :)
Right, without from __future__ import you'd need to quote those names.
And yes, removing the conditional would have fixed it, except OP mentioned it's a 3rd party code that needs fixing. So should msgspec expect that code be fixed or support it? From what I see, msgspec leans more towards being optimized than rich in such features.
The __future__ import also breaks the new Generic notation:
class MyResponse[T](Struct):
results: list[T]
Because the TypeVar T is not part of the runtime then.