16
16
"""A `traverse` visitor for processing documentation."""
17
17
18
18
import collections
19
+ import dataclasses
19
20
import inspect
20
21
21
- from typing import Any , Dict , List , Optional , Tuple
22
+ from typing import Any , Dict , List , Optional , Mapping , Tuple
22
23
23
24
ApiPath = Tuple [str , ...]
24
25
@@ -48,25 +49,32 @@ def maybe_singleton(py_object: Any) -> bool:
48
49
return is_immutable_type or (isinstance (py_object , tuple ) and py_object == ()) # pylint: disable=g-explicit-bool-comparison
49
50
50
51
51
- class ApiTreeNode (object ):
52
- """Represents a single API end-point.
52
+ @dataclasses .dataclass
53
+ class PathTreeNode (object ):
54
+ """Represents a path to an object in the API, an object can have many paths.
53
55
54
56
Attributes:
55
57
path: A tuple of strings containing the path to the object from the root
56
58
like `('tf', 'losses', 'hinge')`
57
- obj : The python object.
58
- children: A dictionary from short name to `ApiTreeNode `, including the
59
- children nodes .
60
- parent: The parent node .
59
+ py_object : The python object.
60
+ children: A dictionary from short name to `PathTreeNode `, of this node's
61
+ children.
62
+ parent: This node's parent. This is a tree, there can only be one .
61
63
short_name: The last path component
62
64
full_name: All path components joined with "."
63
65
"""
66
+ path : ApiPath
67
+ py_object : Any
68
+ parent : Optional ['PathTreeNode' ]
69
+ children : Dict [str , 'PathTreeNode' ] = dataclasses .field (default_factory = dict )
64
70
65
- def __init__ (self , path : ApiPath , obj : Any , parent : Optional ['ApiTreeNode' ]):
66
- self .path = path
67
- self .py_object = obj
68
- self .children : Dict [str , 'ApiTreeNode' ] = {}
69
- self .parent = parent
71
+ def __hash__ (self ):
72
+ return id (self )
73
+
74
+ def __repr__ (self ):
75
+ return f'{ type (self ).__name__ } ({ self .full_name } )'
76
+
77
+ __str__ = __repr__
70
78
71
79
@property
72
80
def short_name (self ) -> str :
@@ -77,24 +85,42 @@ def full_name(self) -> str:
77
85
return '.' .join (self .path )
78
86
79
87
80
- class ApiTree ( object ):
81
- """Represents all api end-points as a tree .
88
+ class PathTree ( Mapping [ ApiPath , PathTreeNode ] ):
89
+ """An index/tree of all object-paths in the API .
82
90
83
91
Items must be inserted in order, from root to leaf.
84
92
93
+ Acts as a Dict[ApiPath, PathTreeNode].
94
+
85
95
Attributes:
86
- index: A dict, mapping from path tuples to `ApiTreeNode`.
87
- aliases: A dict, mapping from object ids to a list of all `ApiTreeNode` that
88
- refer to the object.
89
- root: The root `ApiTreeNode`
96
+ root: The root `PathTreeNode`
90
97
"""
91
98
92
99
def __init__ (self ):
93
- root = ApiTreeNode (path = (), obj = None , parent = None )
94
- self .index : Dict [ApiPath , ApiTreeNode ] = {(): root }
95
- self .aliases : Dict [ApiPath ,
96
- List [ApiTreeNode ]] = collections .defaultdict (list )
97
- self .root : ApiTreeNode = root
100
+ root = PathTreeNode (path = (), py_object = None , parent = None )
101
+ self ._index : Dict [ApiPath , PathTreeNode ] = {(): root }
102
+
103
+ self .root : PathTreeNode = root
104
+ self ._nodes_for_id : Dict [int , List [PathTreeNode ]] = (
105
+ collections .defaultdict (list ))
106
+
107
+ def keys (self ):
108
+ """Returns the paths currently contained in the tree."""
109
+ return self ._index .keys ()
110
+
111
+ def __iter__ (self ):
112
+ return iter (self ._index )
113
+
114
+ def __len__ (self ):
115
+ return len (self ._index )
116
+
117
+ def values (self ):
118
+ """Returns the path-nodes for each node currently in the tree."""
119
+ return self ._index .values ()
120
+
121
+ def items (self ):
122
+ """Returns the (path, node) pairs for each node currently in the tree."""
123
+ return self ._index .items ()
98
124
99
125
def __contains__ (self , path : ApiPath ) -> bool :
100
126
"""Returns `True` if path exists in the tree.
@@ -105,21 +131,24 @@ def __contains__(self, path: ApiPath) -> bool:
105
131
Returns:
106
132
True if `path` exists in the tree.
107
133
"""
108
- return path in self .index
134
+ return path in self ._index
109
135
110
- def __getitem__ (self , path : ApiPath ) -> ApiTreeNode :
136
+ def __getitem__ (self , path : ApiPath ) -> PathTreeNode :
111
137
"""Fetch an item from the tree.
112
138
113
139
Args:
114
140
path: A tuple of strings, the api path to the object.
115
141
116
142
Returns:
117
- An `ApiTreeNode `.
143
+ A `PathTreeNode `.
118
144
119
145
Raises:
120
146
KeyError: If no node can be found at that path.
121
147
"""
122
- return self .index [path ]
148
+ return self ._index [path ]
149
+
150
+ def get (self , path : ApiPath , default = None ):
151
+ return self ._index .get (path , default )
123
152
124
153
def __setitem__ (self , path : ApiPath , obj : Any ):
125
154
"""Add an object to the tree.
@@ -129,18 +158,21 @@ def __setitem__(self, path: ApiPath, obj: Any):
129
158
obj: The python object.
130
159
"""
131
160
parent_path = path [:- 1 ]
132
- parent = self .index [parent_path ]
161
+ parent = self ._index [parent_path ]
133
162
134
- node = ApiTreeNode (path = path , obj = obj , parent = parent )
163
+ node = PathTreeNode (path = path , py_object = obj , parent = parent )
135
164
136
- self .index [path ] = node
165
+ self ._index [path ] = node
137
166
if not maybe_singleton (obj ):
138
167
# We cannot use the duplicate mechanism for some constants, since e.g.,
139
168
# id(c1) == id(c2) with c1=1, c2=1. This isn't problematic since constants
140
169
# have no usable docstring and won't be documented automatically.
141
- self .aliases [ id (obj )] .append (node ) # pytype: disable=unsupported-operands # attribute-variable-annotations
170
+ self .nodes_for_obj (obj ).append (node )
142
171
parent .children [node .short_name ] = node
143
172
173
+ def nodes_for_obj (self , py_object ) -> List [PathTreeNode ]:
174
+ return self ._nodes_for_id [id (py_object )]
175
+
144
176
145
177
class DocGeneratorVisitor (object ):
146
178
"""A visitor that generates docs for a python object when __call__ed."""
@@ -174,7 +206,7 @@ def __init__(self):
174
206
self ._duplicates : Dict [str , List [str ]] = None
175
207
self ._duplicate_of : Dict [str , str ] = None
176
208
177
- self ._api_tree = ApiTree ()
209
+ self ._path_tree = PathTree ()
178
210
179
211
@property
180
212
def index (self ):
@@ -270,15 +302,16 @@ class or module.
270
302
parent_name = '.' .join (parent_path )
271
303
self ._index [parent_name ] = parent
272
304
self ._tree [parent_name ] = []
273
- if parent_path not in self ._api_tree :
274
- self ._api_tree [parent_path ] = parent
305
+ if parent_path not in self ._path_tree :
306
+ self ._path_tree [parent_path ] = parent
275
307
276
308
if not (inspect .ismodule (parent ) or inspect .isclass (parent )):
277
- raise RuntimeError ('Unexpected type in visitor -- '
278
- f'{ parent_name } : { parent !r} ' )
309
+ raise TypeError ('Unexpected type in visitor -- '
310
+ f'{ parent_name } : { parent !r} ' )
279
311
280
312
for name , child in children :
281
- self ._api_tree [parent_path + (name ,)] = child
313
+ child_path = parent_path + (name ,)
314
+ self ._path_tree [child_path ] = child
282
315
283
316
full_name = '.' .join ([parent_name , name ]) if parent_name else name
284
317
self ._index [full_name ] = child
@@ -379,7 +412,7 @@ def _maybe_find_duplicates(self):
379
412
# symbol (incl. itself).
380
413
duplicates = {}
381
414
382
- for path , node in self ._api_tree . index .items ():
415
+ for path , node in self ._path_tree .items ():
383
416
if not path :
384
417
continue
385
418
full_name = node .full_name
@@ -388,7 +421,8 @@ def _maybe_find_duplicates(self):
388
421
if full_name in duplicates :
389
422
continue
390
423
391
- aliases = self ._api_tree .aliases [object_id ]
424
+ aliases = self ._path_tree .nodes_for_obj (py_object )
425
+ # maybe_singleton types can't be looked up by object.
392
426
if not aliases :
393
427
aliases = [node ]
394
428
0 commit comments