0

For use with pydantic, I want to create recursive type aliases at runtime. "Normal" type aliases are possible like this:

from typing import TypeAliasType

alias = TypeAliasType("alias", str)
foo: alias = "bar"

But now I want to create a recursive type alias like this one:

from collections.abc import Sequence

type recursive = int | Sequence[recursive]

This includes a forward reference, which I found impossible to emulate at runtime. There is ForwardRef, but besides being discouraged it did not work for me:

from typing import ForwardRef, TypeAliasType
from collections.abc import Sequence

# This is what I want to emulate at runtime
type recursive = int | Sequence[recursive]
print(recursive.__value__) # int | collections.abc.Sequence[recursive]

# This is my (failing) attempt to emulate it at runtime
dynamic_list_of_types = float | int
ref = ForwardRef("recursive")
recursive = TypeAliasType("recursive", dynamic_list_of_types | Sequence[ref])
print(recursive.__value__)  # float | int | collections.abc.Sequence[ForwardRef('recursive')]

How can I declare such a recursive type alias during runtime?

I am aware of how to do it statically as in this question. Because the list of allowed types (the dynamic_list_of_types in my example above) is not fixed, but can be expanded dynamically, this is not possible here.

More context: I have a pydantic model inside some package that offers support for plugins. This model has some fields like mylist: Sequence[MyTypeAlias], where MyTypeAlias describes a union of allowed types. The plugins are now supposed to expand this list of allowed types with their own models. The solution I am aiming for is to collect this list and replace all occurences of MyTypeAlias with the updated union. This works well, except for the recursive types mentioned here. Other options to manually mark all such occurences (which are many) would decrease readability in the models of the main package, so I chose this approach.

11
  • See the question I linked to - in your case type recursive = Union[int | Iterable['recursive']] - also, that's not really a type alias but just a type, but I don't think that matters for the answer. If you really need it to be an alias, than analogous recursive: TypeAlias = Union[int | Iterable['recursive']] Commented Sep 3 at 22:53
  • @Grismar I am aware of this question, but neither the question nor any of the answers define their TypeAlias (or their type) dynamically at runtime, but use something like my second example. At runtime, using a string placeholder in the definition of TypeAliasType does not resolve to a type, because it is not recognized as a forward reference. So the answer you linked does not help unfortunately. I will link it though. I am not quite sure how to rephrase my question so my point comes across more clearly, but am open for suggestions. In any case, I would ask you to reopen it. Commented Sep 4 at 9:23
  • What do you mean, "replace all occurrences"? You can only have a recursive type if it's named; anonymizing the type would remove any recursiveness. Commented Sep 4 at 15:58
  • 1
    I don't think this question is clear. Trying your example, >>> print(recursive.__value__) already prints int | collections.abc.Sequence[recursive] in a Python 3.13 REPL, indicating that type recursive = int | Sequence[recursive] already works at runtime for defining recursive type aliases. Are you trying to ask instead: "How do I create recursive type aliases without the PEP 695 type statement?" Commented Sep 23 at 0:26
  • 1
    After thinking about this a bit more, I'm afraid I still don't quite understand the question, because it's not clear where you're getting dynamic_list_of_types. Maybe this helps: type recursive = produce_types() | Sequence[recursive] does work if you define produce_types as def produce_types(): return int | str. Would this be dynamic enough for your purposes? Commented Sep 29 at 21:01

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.