Open SylvainGuieu opened 10 months ago
Hello @SylvainGuieu,
As you might have noticed, pydantic/pydantic#8209 is still open. That means you cannot use eval_type_backport
in pydantic easily just yet, because it's not merged and released.
When it comes to using eval_type_backport
, I'll write a few lines in the readme soon.
As for now, you might use my (a bit worse) solution https://github.com/bswck/modern_types which simply requires import __modern_types__
line on top of your module and you're all set. If it doesn't work for you, let me know.
@bswck Thanks, I will try this.
Thanks @bswck for responding.
Merging https://github.com/pydantic/pydantic/pull/8209 is indeed the main thing that needs to happen. Once that's released you shouldn't need to do anything from pydantic as long as this package is installed.
I thought I could replace
typing._eval_type
method to this one by I end up with cycling calls.
I think the fact that you can't do this should be considered a bug. It'd also be good for this package to expose some API which does this for you. Then this can easily be used both before whatever version of pydantic releases this and outside pydantic.
The pydantic PR is merged, and pydantic 2.6.0 beta should be released soon.
Hey @bswck (@alexmojaki), I see you wanted to improve the docs?
May I send a quick PR here to show in the README how to use this? I was confused too and thought it would override typing._eval_type
, but you actually have to from eval_type_backport import eval_type_backport
.
Huh actually I'm still not managing to use it correctly :thinking:
With
eval_type( # noqa: PGH001,S307
param.annotation,
exec_globals,
{},
try_default=False,
)
...param.annotation
being "str | None"
def _eval_direct(
value: typing.ForwardRef,
globalns: dict[str, Any] | None = None,
localns: dict[str, Any] | None = None,
):
> tree = ast.parse(value.__forward_arg__, mode='eval')
E AttributeError: 'str' object has no attribute '__forward_arg__'
I suppose I should manually wrap any string annotation into a forward ref?
Yep, this seems to work:
eval_type( # noqa: PGH001,S307
ForwardRef(param.annotation) if isinstance(param.annotation, str) else param.annotation,
exec_globals,
{},
)
With this I can also drop try_default=False
and let eval-type-backport do its thing. Which also means I can drop this:
try:
from eval_type_backport import eval_type_backport as eval_type
except ImportError:
from typing import _eval_type
def eval_type(*args, **kwargs):
kwargs.pop("try_default", None)
return _eval_type(*args, **kwargs)
:+1:
Would be nice though it eval-type-backport would cast strings to forward refs automatically (if that makes sense) :slightly_smiling_face:
try_default=False
is not really meant for 'public' use. Here's how it's used in pydantic:
def eval_type_backport(
value: Any, globalns: dict[str, Any] | None = None, localns: dict[str, Any] | None = None
) -> Any:
"""Like `typing._eval_type`, but falls back to the `eval_type_backport` package if it's
installed to let older Python versions use newer typing features.
Specifically, this transforms `X | Y` into `typing.Union[X, Y]`
and `list[X]` into `typing.List[X]` etc. (for all the types made generic in PEP 585)
if the original syntax is not supported in the current Python version.
"""
try:
return typing._eval_type( # type: ignore
value, globalns, localns
)
except TypeError as e:
if not (isinstance(value, typing.ForwardRef) and is_backport_fixable_error(e)):
raise
try:
from eval_type_backport import eval_type_backport
except ImportError:
raise TypeError(
f'You have a type annotation {value.__forward_arg__!r} '
f'which makes use of newer typing features than are supported in your version of Python. '
f'To handle this error, you should either remove the use of new syntax '
f'or install the `eval_type_backport` package.'
) from e
return eval_type_backport(value, globalns, localns, try_default=False)
def is_backport_fixable_error(e: TypeError) -> bool:
msg = str(e)
return msg.startswith('unsupported operand type(s) for |: ') or "' object is not subscriptable" in msg
Notice how a bunch of code is almost identical to eval_type_backport itself so that it only suggests installing the package if it might be useful. try_default=False
is just there to prevent calling typing._eval_type
a second time to save a bit of time since it's already been checked and failed.
It probably would have been better to just use _eval_direct
in pydantic, but it's a bit late to change it now.
Would be nice though it eval-type-backport would cast strings to forward refs automatically (if that makes sense) 🙂
That would make it behave differently from typing._eval_type
.
Thanks for your quick reply :slightly_smiling_face:
Here's how I now use eval-type-backport: https://github.com/pawamoy/duty/commit/e8ca7c1fb453a6f0b3de3268e2cea3434985c428. Let me know if you'd like me to send a PR to show quick usage in the readme.
Wanted to chime in that this "just worked" after updating pydantic.
Excerpt from pyproject.toml
[tool.poetry.dependencies]
python = ">=3.9, <3.13"
pydantic = "^2.6"
eval-type-backport = { version = "^0.2.0", python = "<3.10"}
Hi, I fill very stupid but ... I followed the issues on pydantic I end-up here, I installed it, but I have no idea how to use it !
What should I do with the
eval_type_backport
to make it work with pydantic ? I though I could replacetyping._eval_type
method to this one by I end up with cycling calls.Could you drop a few lines in the Readme ?
cheers, Sylvain