@@ -1293,6 +1293,16 @@ def _formatannotation(annot):
1293
1293
'pdoc.MyType'
1294
1294
>>> _formatannotation(Optional[Tuple[Optional[int], None]])
1295
1295
'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]'
1296
1306
"""
1297
1307
class force_repr (str ):
1298
1308
__repr__ = str .__str__
@@ -1302,12 +1312,16 @@ def maybe_replace_reprs(a):
1302
1312
if a is type (None ): # noqa: E721
1303
1313
return force_repr ('None' )
1304
1314
# 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 )
1311
1325
# typing.NewType('T', foo) -> T
1312
1326
module = getattr (a , '__module__' , '' )
1313
1327
if module == 'typing' and getattr (a , '__qualname__' , '' ).startswith ('NewType.' ):
@@ -1316,11 +1330,25 @@ def maybe_replace_reprs(a):
1316
1330
if module .startswith ('nptyping.' ):
1317
1331
return force_repr (repr (a ))
1318
1332
# 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.' ))
1324
1352
return a
1325
1353
1326
1354
return str (inspect .formatannotation (maybe_replace_reprs (annot )))
0 commit comments