@@ -71,8 +71,9 @@ class or function within a module or module in a package. If the
7171import textwrap
7272import time
7373import tokenize
74+ import typing
7475import warnings
75- from annotationlib import Format
76+ from annotationlib import Format , call_evaluate_function
7677from collections import deque
7778from reprlib import Repr
7879from traceback import format_exception_only
@@ -195,7 +196,12 @@ def isdata(object):
195196 """Check if an object is of a type that probably means it's data."""
196197 return not (inspect .ismodule (object ) or inspect .isclass (object ) or
197198 inspect .isroutine (object ) or inspect .isframe (object ) or
198- inspect .istraceback (object ) or inspect .iscode (object ))
199+ inspect .istraceback (object ) or inspect .iscode (object ) or
200+ istypealias (object ))
201+
202+ def istypealias (object ):
203+ """Check if an object is a type alias."""
204+ return isinstance (object , typing .TypeAliasType )
199205
200206def replace (text , * pairs ):
201207 """Do a series of global replacements on a string."""
@@ -466,6 +472,58 @@ def safeimport(path, forceload=0, cache={}):
466472 raise ErrorDuringImport (path , err )
467473 return module
468474
475+ def _typealias_source_value (object ):
476+ """Recover an alias expression when string-format evaluation fails."""
477+ import ast
478+
479+ try :
480+ source = textwrap .dedent (inspect .getsource (object .evaluate_value ))
481+ tree = ast .parse (source )
482+ except (OSError , TypeError , SyntaxError ):
483+ return None
484+ aliases = [node for node in ast .walk (tree )
485+ if isinstance (node , ast .TypeAlias )
486+ and node .name .id == object .__name__ ]
487+ if len (aliases ) != 1 :
488+ return None
489+ return ast .get_source_segment (source , aliases [0 ].value )
490+
491+
492+ def _typealias_declaration (object ):
493+ params = []
494+ for param in object .__type_params__ :
495+ if isinstance (param , typing .TypeVarTuple ):
496+ prefix = '*'
497+ elif isinstance (param , typing .ParamSpec ):
498+ prefix = '**'
499+ else :
500+ prefix = ''
501+ declaration = prefix + param .__name__
502+ for separator , attribute in (
503+ (': ' , 'evaluate_constraints' ),
504+ (': ' , 'evaluate_bound' ),
505+ (' = ' , 'evaluate_default' ),
506+ ):
507+ evaluate = getattr (param , attribute , None )
508+ if evaluate is not None :
509+ try :
510+ value = call_evaluate_function (evaluate , Format .STRING )
511+ except Exception :
512+ continue
513+ declaration += separator + str (value )
514+ params .append (declaration )
515+ name = object .__name__
516+ if params :
517+ name += '[%s]' % ', ' .join (params )
518+ try :
519+ value = call_evaluate_function (object .evaluate_value , Format .STRING )
520+ except Exception as exc :
521+ value = _typealias_source_value (object )
522+ if value is None :
523+ value = f'<unable to render: { exc !r} >'
524+ return name , str (value )
525+
526+
469527# ---------------------------------------------------- formatter base class
470528
471529class Doc :
@@ -486,6 +544,7 @@ def document(self, object, name=None, *args):
486544 if inspect .ismodule (object ): return self .docmodule (* args )
487545 if inspect .isclass (object ): return self .docclass (* args )
488546 if inspect .isroutine (object ): return self .docroutine (* args )
547+ if istypealias (object ): return self .doctypealias (* args )
489548 except AttributeError :
490549 pass
491550 if inspect .isdatadescriptor (object ): return self .docdata (* args )
@@ -497,7 +556,7 @@ def fail(self, object, name=None, *args):
497556 name and ' ' + repr (name ), type (object ).__name__ )
498557 raise TypeError (message )
499558
500- docmodule = docclass = docroutine = docother = docproperty = docdata = fail
559+ docmodule = docclass = docroutine = doctypealias = docother = docproperty = docdata = fail
501560
502561 def getdocloc (self , object , basedir = None ):
503562 """Return the location of module docs or None"""
@@ -844,6 +903,14 @@ def docmodule(self, object, name=None, mod=None, *ignored):
844903 funcs .append ((key , value ))
845904 fdict [key ] = '#-' + key
846905 if inspect .isfunction (value ): fdict [value ] = fdict [key ]
906+ typealiases = []
907+ for key , value in inspect .getmembers (
908+ object , istypealias ):
909+ if (all is not None or
910+ (inspect .getmodule (value ) or object ) is object ):
911+ if visiblename (key , all , object ):
912+ typealiases .append ((key , value ))
913+ cdict [key ] = cdict [value ] = '#' + key
847914 data = []
848915 for key , value in inspect .getmembers (object , isdata ):
849916 if visiblename (key , all , object ):
@@ -875,6 +942,12 @@ def docmodule(self, object, name=None, mod=None, *ignored):
875942 contents .append (self .document (value , key , name , fdict , cdict ))
876943 result = result + self .bigsection (
877944 'Classes' , 'index' , ' ' .join (contents ))
945+ if typealiases :
946+ contents = []
947+ for key , value in typealiases :
948+ contents .append (self .document (value , key , name , fdict , cdict ))
949+ result = result + self .bigsection (
950+ 'Type Aliases' , 'type-aliases' , ' ' .join (contents ))
878951 if funcs :
879952 contents = []
880953 for key , value in funcs :
@@ -1162,6 +1235,35 @@ def docroutine(self, object, name=None, mod=None,
11621235 doc = doc and '<dd class="docstring">%s</dd>' % doc
11631236 return '<dl class="doc"><dt>%s</dt>%s</dl>\n ' % (decl , doc )
11641237
1238+ def doctypealias (self , object , name = None , mod = None , funcs = {}, classes = {},
1239+ * ignored ):
1240+ """Produce HTML documentation for a type alias."""
1241+ realname , value = _typealias_declaration (object )
1242+ name = name or object .__name__
1243+ if name == object .__name__ :
1244+ declaration = '<strong>%s</strong>' % self .escape (realname )
1245+ else :
1246+ declaration = '<strong>%s</strong> = type alias %s' % (
1247+ self .escape (name ), self .escape (realname ))
1248+ title = 'type <a id="%s">%s</a> = %s' % (
1249+ self .escape (name ), declaration , self .escape (value ))
1250+ contents = ['<h4>Lazy value access:</h4>\n ' ]
1251+ for attr in ('__value__' , 'evaluate_value' ):
1252+ member = vars (typing .TypeAliasType )[attr ]
1253+ if (not inspect .isdatadescriptor (member ) or
1254+ not (description := inspect .getdoc (member ))):
1255+ continue
1256+ contents .append (
1257+ '<dl class="doc"><dt><strong>%s</strong></dt>'
1258+ '<dd class="docstring">%s</dd></dl>\n ' %
1259+ (attr , description ))
1260+ contents .append (
1261+ '<p>See <a href="typing.html#TypeAliasType">'
1262+ 'typing.TypeAliasType</a> for the full type alias interface.</p>\n ' )
1263+ doc = self .markup (
1264+ getdoc (object ), self .preformat , funcs , classes )
1265+ return self .section (title , 'title' , '' .join (contents ), prelude = doc )
1266+
11651267 def docdata (self , object , name = None , mod = None , cl = None , * ignored ):
11661268 """Produce html documentation for a data descriptor."""
11671269 results = []
@@ -1319,6 +1421,13 @@ def docmodule(self, object, name=None, mod=None, *ignored):
13191421 or (inspect .getmodule (value ) or object ) is object ):
13201422 if visiblename (key , all , object ):
13211423 funcs .append ((key , value ))
1424+ typealiases = []
1425+ for key , value in inspect .getmembers (
1426+ object , istypealias ):
1427+ if (all is not None or
1428+ (inspect .getmodule (value ) or object ) is object ):
1429+ if visiblename (key , all , object ):
1430+ typealiases .append ((key , value ))
13221431 data = []
13231432 for key , value in inspect .getmembers (object , isdata ):
13241433 if visiblename (key , all , object ):
@@ -1356,6 +1465,13 @@ def docmodule(self, object, name=None, mod=None, *ignored):
13561465 contents .append (self .document (value , key , name ))
13571466 result = result + self .section ('CLASSES' , '\n ' .join (contents ))
13581467
1468+ if typealiases :
1469+ contents = []
1470+ for key , value in typealiases :
1471+ contents .append (self .document (value , key , name ))
1472+ result = result + self .section (
1473+ 'TYPE ALIASES' , '\n ' .join (contents ))
1474+
13591475 if funcs :
13601476 contents = []
13611477 for key , value in funcs :
@@ -1536,6 +1652,29 @@ def spilldata(msg, attrs, predicate):
15361652 return title + '\n '
15371653 return title + '\n ' + self .indent (contents .rstrip (), ' | ' ) + '\n '
15381654
1655+ def doctypealias (self , object , name = None , mod = None , * ignored ):
1656+ """Produce text documentation for a type alias."""
1657+ realname , value = _typealias_declaration (object )
1658+ name = name or object .__name__
1659+ contents = []
1660+ if doc := getdoc (object ):
1661+ contents .extend ((self ._format_doc (doc ), '' ))
1662+ contents .extend (('Lazy value access:' , '' ))
1663+ for attr in ('__value__' , 'evaluate_value' ):
1664+ member = vars (typing .TypeAliasType )[attr ]
1665+ if (not inspect .isdatadescriptor (member ) or
1666+ not (description := inspect .getdoc (member ))):
1667+ continue
1668+ contents .extend ((self .bold (attr ), self .indent (description ), '' ))
1669+ contents .append (
1670+ 'See help(typing.TypeAliasType) for the full type alias interface.' )
1671+ if name == object .__name__ :
1672+ title = self .bold (realname )
1673+ else :
1674+ title = self .bold (name ) + ' = type alias ' + realname
1675+ title = 'type ' + title + ' = ' + value
1676+ return title + '\n ' + self .indent ('\n ' .join (contents ).rstrip ()) + '\n '
1677+
15391678 def formatvalue (self , object ):
15401679 """Format an argument default value as text."""
15411680 return '=' + self .repr (object )
@@ -1693,6 +1832,8 @@ def describe(thing):
16931832 return 'method descriptor ' + thing .__name__
16941833 except AttributeError :
16951834 pass
1835+ if istypealias (thing ):
1836+ return 'type alias ' + thing .__name__
16961837 return type (thing ).__name__
16971838
16981839def locate (path , forceload = 0 ):
@@ -1751,6 +1892,7 @@ def render_doc(thing, title='Python Library Documentation: %s', forceload=0,
17511892 inspect .isclass (object ) or
17521893 inspect .isroutine (object ) or
17531894 inspect .isdatadescriptor (object ) or
1895+ istypealias (object ) or
17541896 _getdoc (object )):
17551897 # If the passed object is a piece of data or an instance,
17561898 # document its available methods instead of its value.
0 commit comments