@@ -1293,6 +1293,16 @@ def _formatannotation(annot):
12931293 'pdoc.MyType'
12941294 >>> _formatannotation(Optional[Tuple[Optional[int], None]])
12951295 'Optional[Tuple[Optional[int], None]]'
1296+ >>> _formatannotation(Optional[Union[int, float, None]])
1297+ 'Optional[int | float]'
1298+ >>> _formatannotation(Union[int, float])
1299+ 'int | float'
1300+ >>> from typing import Callable
1301+ >>> _formatannotation(Callable[[Optional[int]], float])
1302+ 'Callable[[Optional[int]], float]'
1303+ >>> from collections.abc import Callable
1304+ >>> _formatannotation(Callable[[Optional[int]], float])
1305+ 'Callable[[Optional[int]], float]'
12961306 """
12971307 class force_repr (str ):
12981308 __repr__ = str .__str__
@@ -1302,12 +1312,16 @@ def maybe_replace_reprs(a):
13021312 if a is type (None ): # noqa: E721
13031313 return force_repr ('None' )
13041314 # Union[T, None] -> Optional[T]
1305- if (getattr (a , '__origin__' , None ) is typing .Union and
1306- len (a .__args__ ) == 2 and
1307- type (None ) in a .__args__ ):
1308- t = inspect .formatannotation (
1309- maybe_replace_reprs (next (filter (None , a .__args__ ))))
1310- return force_repr (f'Optional[{ t } ]' )
1315+ if getattr (a , '__origin__' , None ) is typing .Union :
1316+ union_args = a .__args__
1317+ is_optional = type (None ) in union_args
1318+ if is_optional :
1319+ union_args = (x for x in union_args if x is not type (None ))
1320+ t = ' | ' .join (inspect .formatannotation (maybe_replace_reprs (x ))
1321+ for x in union_args )
1322+ if is_optional :
1323+ t = f'Optional[{ t } ]'
1324+ return force_repr (t )
13111325 # typing.NewType('T', foo) -> T
13121326 module = getattr (a , '__module__' , '' )
13131327 if module == 'typing' and getattr (a , '__qualname__' , '' ).startswith ('NewType.' ):
@@ -1316,11 +1330,25 @@ def maybe_replace_reprs(a):
13161330 if module .startswith ('nptyping.' ):
13171331 return force_repr (repr (a ))
13181332 # Recurse into typing.Callable/etc. args
1319- if hasattr (a , 'copy_with' ) and hasattr (a , '__args__' ):
1320- if a is typing .Callable :
1321- # Bug on Python < 3.9, https://bugs.python.org/issue42195
1322- return a
1323- a = a .copy_with (tuple ([maybe_replace_reprs (arg ) for arg in a .__args__ ]))
1333+ if hasattr (a , '__args__' ):
1334+ if hasattr (a , 'copy_with' ):
1335+ if a is typing .Callable :
1336+ # Bug on Python < 3.9, https://bugs.python.org/issue42195
1337+ return a
1338+ a = a .copy_with (tuple ([maybe_replace_reprs (arg ) for arg in a .__args__ ]))
1339+ elif hasattr (a , '__origin__' ):
1340+ args = tuple (map (maybe_replace_reprs , a .__args__ ))
1341+ try :
1342+ a = a .__origin__ [args ]
1343+ except TypeError :
1344+ # collections.abc.Callable takes "([in], out)"
1345+ a = a .__origin__ [(args [:- 1 ], args [- 1 ])]
1346+ # Recurse into lists
1347+ if isinstance (a , (list , tuple )):
1348+ return type (a )(map (maybe_replace_reprs , a ))
1349+ # Shorten standard collections: collections.abc.Callable -> Callable
1350+ if module == 'collections.abc' :
1351+ return force_repr (repr (a ).removeprefix ('collections.abc.' ))
13241352 return a
13251353
13261354 return str (inspect .formatannotation (maybe_replace_reprs (annot )))
0 commit comments