Search code examples
pythonregexpython-sphinxpython-typing

Remove Annotations from typing.Annotated


Let's say I have some type aliases, maybe

Point3D = Annotated[tuple[float, float, float], "A 3D Point"]
Points = Annotated[list[Point3D, list[float]], "A collection of points"]

If I try to print Points, I get

typing.Annotated[list[typing.Annotated[tuple[float, float, float], 'A 3D Point'] | list[float]], 'A collection of points']

I want to only get the list[tuple[float, float, float]] part. I tried using typing.get_args(Points)[0] but that just gives this:

list[typing.Annotated[tuple[float, float, float], 'A 3D Point'] | list[float]]

Where there is still the unwanted 'A 3D Point'. How can I achieve this? I tried replacing it with the ", '.*?' regex, but that didn't work, and I'm not experienced enough with regex to be able to figure out why.

Note: I can't just change all the Annotated types to normal type hints because I still need that annotation content to be displayed elsewhere.


Solution

  • The solution is simple: Walk the tree (at runtime) and convert all Annotated nodes to their bare-type counterpart. No regex needed.

    from typing import Annotated, get_args, get_origin
    
    def convert_annotated_to_bare_types(type_object: type):
      # For a given `X[Y, Z, ...]`:
      # * `get_origin` returns `X`
      # * `get_args` return `(Y, Z, ...)`
      # For non-supported type objects, the return values are
      # `None` and `()` correspondingly.
      origin, args = get_origin(type_object), get_args(type_object)
        
      if origin is None:
        # No origin -> Not a generic/no arguments.
        return type_object
        
      if origin is Annotated:
        # Annotated -> Convert the first argument recursively.
        bare_type = get_args(type_object)[0]
        return convert_annotated_to_bare_types(bare_type)
    
      # Otherwise, it is a generic. Convert all arguments recursively.
      converted_args = [
        convert_annotated_to_bare_types(arg) for arg in args
      ]
      
      return origin[*converted_args]
    

    It works exactly like you would expect, even if list is not supposed to take 2 type arguments:

    >>> convert_annotated_to_bare_types(Points)
    list[tuple[float, float, float], list[float]]