Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to use it #11

Open
SylvainGuieu opened this issue Jan 15, 2024 · 10 comments
Open

How to use it #11

SylvainGuieu opened this issue Jan 15, 2024 · 10 comments

Comments

@SylvainGuieu
Copy link

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 replace typing._eval_type method to this one by I end up with cycling calls.

Could you drop a few lines in the Readme ?

cheers,
Sylvain

@bswck
Copy link
Contributor

bswck commented Jan 15, 2024

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.

@SylvainGuieu
Copy link
Author

@bswck Thanks,
I will try this.

@alexmojaki
Copy link
Owner

Thanks @bswck for responding.

Merging pydantic/pydantic#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.

@alexmojaki
Copy link
Owner

The pydantic PR is merged, and pydantic 2.6.0 beta should be released soon.

@pawamoy
Copy link

pawamoy commented Apr 28, 2024

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.

@pawamoy
Copy link

pawamoy commented Apr 28, 2024

Huh actually I'm still not managing to use it correctly 🤔

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?

@pawamoy
Copy link

pawamoy commented Apr 28, 2024

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)

👍

Would be nice though it eval-type-backport would cast strings to forward refs automatically (if that makes sense) 🙂

@alexmojaki
Copy link
Owner

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.

@pawamoy
Copy link

pawamoy commented Apr 28, 2024

Thanks for your quick reply 🙂

Here's how I now use eval-type-backport: pawamoy/duty@e8ca7c1. Let me know if you'd like me to send a PR to show quick usage in the readme.

@JPHutchins
Copy link

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"}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants