Adding a `typing.evaluate_type()` function

We currently have two functions in the stdlib to evaluate type hints: typing.get_type_hints() and annotationlib.get_annotations().

These two functions expect a function, module or class object to be passed, and so it is currently not possible to evaluate a bare annotation expression. As I mentioned here, it would be useful for a couple reasons:

  • When evaluating annotations for a class/function (using the Format.VALUE format), it is not possible to track which annotated attribute/function parameter failed to have its annotation evaluated. For runtime type checkers, this can be useful to indicate to end users where they should look to fix the error.
  • Several runtime type checkers provide the ability to validate data against a bare annotation expression (trycast, Pydantic’s TypeAdapter).

I think it would be valuable to have a typing.evaluate_type() public function in the stdlib (or at least in typing-extensions):

def evaluate_type(
    t,
    globalns=None,
    localns=None,
    format=None,
    owner=None,
):
    return _eval_type(t, globalns, localns, type_params=(), format=format, owner=owner)

That would delegate to the existing private _eval_type() function (already relied on relatively heavily).

To be determined:

4 Likes

I wonder whether there should be a globalns and localns argument at all. When would you use them over owner?

I think this can still be useful for something like SQLAlchemy’s ORM where it’s common with back references that you are unable to import one or more of the related models at class definition time. The mapper resolves those forward references later, once all the models have been registered. So in that sense there’s not a clear single owner of those symbols. You would need to create one, so it seems easier to just generate a globalns/localns that contains all the symbols it needs to contain[1].

It’s certainly the less common use-case, but I don’t think we need to remove it altogether, constructing a dummy owner seems more cumbersome than manipulating two dictionaries.

We certainly should steer users towards considering owner for their use-case before reaching for globalns/localns.


  1. i.e. the ones it would contain based on where the annotation was defined and additionally all the models it knows about ↩︎

1 Like

Agree with what David said! Fwiw, in Pydantic, we need to be able to pass the globals and locals as we use slightly different logic to construct the globals and locals, that we can’t change for backwards compatibility.

We should probably emphasize on using owner as the preferred approach in docs (and maybe add overloads to this function + the existing evaluate_forward_ref() to make owner and globalns/localns mutually exclusive).