16
16
17
17
import collections
18
18
import dataclasses
19
+ import functools
19
20
import inspect
20
21
21
- from typing import Any , Dict , List , Optional , Mapping , Tuple
22
+ from typing import Any , Dict , List , Optional , Tuple
23
+
24
+ from tensorflow_docs .api_generator import obj_type as obj_type_lib
25
+
22
26
23
27
ApiPath = Tuple [str , ...]
24
28
@@ -75,6 +79,9 @@ def __repr__(self):
75
79
76
80
__str__ = __repr__
77
81
82
+ def __eq__ (self , other ):
83
+ raise ValueError ("Don't try to compare these" )
84
+
78
85
@property
79
86
def short_name (self ) -> str :
80
87
return self .path [- 1 ]
@@ -84,70 +91,38 @@ def full_name(self) -> str:
84
91
return '.' .join (self .path )
85
92
86
93
87
- class PathTree (Mapping [ApiPath , PathTreeNode ]):
94
+ class PathTree (Dict [ApiPath , PathTreeNode ]):
88
95
"""An index/tree of all object-paths in the API.
89
96
90
97
Items must be inserted in order, from root to leaf.
91
98
92
- Acts as a Dict[ApiPath, PathTreeNode].
93
99
94
100
Attributes:
95
101
root: The root `PathTreeNode`
96
102
"""
97
103
98
104
def __init__ (self ):
99
- root = PathTreeNode (path = (), py_object = None , parent = None )
100
- self . _index : Dict [ ApiPath , PathTreeNode ] = {(): root }
105
+ root = PathTreeNode (path = (), py_object = None , parent = None , children = {} )
106
+ super (). __setitem__ ((), root )
101
107
102
108
self .root : PathTreeNode = root
103
109
self ._nodes_for_id : Dict [int , List [PathTreeNode ]] = (
104
110
collections .defaultdict (list ))
105
111
106
- def keys (self ):
107
- """Returns the paths currently contained in the tree."""
108
- return self ._index .keys ()
109
-
110
- def __iter__ (self ):
111
- return iter (self ._index )
112
-
113
- def __len__ (self ):
114
- return len (self ._index )
115
-
116
- def values (self ):
117
- """Returns the path-nodes for each node currently in the tree."""
118
- return self ._index .values ()
119
-
120
- def items (self ):
121
- """Returns the (path, node) pairs for each node currently in the tree."""
122
- return self ._index .items ()
123
-
124
- def __contains__ (self , path : ApiPath ) -> bool :
125
- """Returns `True` if path exists in the tree.
126
-
127
- Args:
128
- path: A tuple of strings, the api path to the object.
129
-
130
- Returns:
131
- True if `path` exists in the tree.
132
- """
133
- return path in self ._index
134
-
135
- def __getitem__ (self , path : ApiPath ) -> PathTreeNode :
136
- """Fetch an item from the tree.
137
-
138
- Args:
139
- path: A tuple of strings, the api path to the object.
140
-
141
- Returns:
142
- A `PathTreeNode`.
112
+ def __eq__ (self , other ):
113
+ raise ValueError ("Don't try to compare these" )
143
114
144
- Raises:
145
- KeyError: If no node can be found at that path.
146
- """
147
- return self ._index [path ]
115
+ def iter_nodes (self ):
116
+ """Iterate over the nodes in BFS order."""
117
+ stack = collections .deque ([self .root ])
118
+ while stack :
119
+ children = list (stack .popleft ().children .values ())
120
+ yield from children
121
+ stack .extend (children )
148
122
149
- def get (self , path : ApiPath , default = None ):
150
- return self ._index .get (path , default )
123
+ def __contains__ (self , path : ApiPath ) -> bool : # pylint: disable=useless-super-delegation
124
+ # TODO(b/184563451): remove
125
+ return super ().__contains__ (path )
151
126
152
127
def __setitem__ (self , path : ApiPath , obj : Any ):
153
128
"""Add an object to the tree.
@@ -157,16 +132,17 @@ def __setitem__(self, path: ApiPath, obj: Any):
157
132
obj: The python object.
158
133
"""
159
134
parent_path = path [:- 1 ]
160
- parent = self . _index [parent_path ]
135
+ parent = self [parent_path ]
161
136
162
137
node = PathTreeNode (path = path , py_object = obj , parent = parent )
163
138
164
- self . _index [ path ] = node
139
+ super (). __setitem__ ( path , node )
165
140
if not maybe_singleton (obj ):
166
141
# We cannot use the duplicate mechanism for some constants, since e.g.,
167
142
# id(c1) == id(c2) with c1=1, c2=1. This isn't problematic since constants
168
143
# have no usable docstring and won't be documented automatically.
169
- self .nodes_for_obj (obj ).append (node )
144
+ nodes = self .nodes_for_obj (obj )
145
+ nodes .append (node )
170
146
parent .children [node .short_name ] = node
171
147
172
148
def nodes_for_obj (self , py_object ) -> List [PathTreeNode ]:
@@ -315,7 +291,7 @@ class or module.
315
291
316
292
return children
317
293
318
- def _score_name (self , name ):
294
+ def _score_name (self , name : str ):
319
295
"""Return a tuple of scores indicating how to sort for the best name.
320
296
321
297
This function is meant to be used as the `key` to the `sorted` function.
@@ -443,3 +419,144 @@ def build(self):
443
419
self ._duplicate_of = duplicate_of
444
420
self ._duplicates = duplicates
445
421
self ._reverse_index = reverse_index
422
+
423
+
424
+ @dataclasses .dataclass (repr = False )
425
+ class ApiTreeNode (PathTreeNode ):
426
+ aliases : List [ApiPath ] = dataclasses .field (default_factory = list )
427
+
428
+ @property
429
+ def obj_type (self ) -> obj_type_lib .ObjType :
430
+ return obj_type_lib .ObjType .get (self .py_object )
431
+
432
+
433
+ class ApiTree (Dict [ApiPath , ApiTreeNode ]):
434
+ """Public API index.
435
+
436
+ Items must be inserted in order from root to leaves.
437
+
438
+ Lookup a path-tuple to fetch a node:
439
+
440
+ ```
441
+ node = index[path]
442
+ ```
443
+
444
+ Use the `node_from_obj` method to lookup the node for a python object:
445
+
446
+ ```
447
+ node = index.node_from_obj(obj)
448
+ ```
449
+
450
+ Remember that `maybe_singelton` (numbers, strings, tuples) classes can't be
451
+ looked up this way.
452
+
453
+ To build a tree, nodes must be inserted in tree order starting from the root.
454
+
455
+
456
+ Attributes:
457
+ root: The root `ApiFileNode` of the tree.
458
+ """
459
+
460
+ def __init__ (self ):
461
+ root = ApiTreeNode (
462
+ path = (), py_object = None , parent = None , aliases = [()]) # type: ignore
463
+ self .root = root
464
+ super ().__setitem__ ((), root )
465
+ self ._nodes = []
466
+ self ._node_for_object = {}
467
+
468
+ def __eq__ (self , other ):
469
+ raise ValueError ("Don't try to compare these" )
470
+
471
+ def node_for_object (self , obj : Any ) -> Optional [ApiTreeNode ]:
472
+ if maybe_singleton (obj ):
473
+ return None
474
+ return self ._node_for_object .get (id (obj ), None )
475
+
476
+ def __contains__ (self , path : ApiPath ) -> bool : # pylint: disable=useless-super-delegation
477
+ # TODO(b/184563451): remove
478
+ return super ().__contains__ (path )
479
+
480
+ def iter_nodes (self ):
481
+ """Iterate over the nodes in BFS order."""
482
+ stack = collections .deque ([self .root ])
483
+ while stack :
484
+ children = list (stack .popleft ().children .values ())
485
+ yield from children
486
+ stack .extend (children )
487
+
488
+ def __setitem__ (self , * args , ** kwargs ):
489
+ raise TypeError ('Use .insert instead of setitem []' )
490
+
491
+ def insert (self , path : ApiPath , py_object : Any , aliases : List [ApiPath ]):
492
+ """Add an object to the index."""
493
+ assert path not in self , 'A path was inserted twice.'
494
+
495
+ parent_path = path [:- 1 ]
496
+ parent = self [parent_path ]
497
+
498
+ node = ApiTreeNode (
499
+ path = path , py_object = py_object , aliases = aliases , parent = parent )
500
+
501
+ super ().__setitem__ (path , node )
502
+ self ._nodes .append (node )
503
+ for alias in aliases :
504
+ if alias == path :
505
+ continue
506
+ assert alias not in self
507
+ super ().__setitem__ (alias , node )
508
+
509
+ self ._node_for_object [id (node .py_object )] = node
510
+
511
+ parent .children [node .short_name ] = node
512
+
513
+ @classmethod
514
+ def from_path_tree (cls , path_tree : PathTree , score_name_fn ) -> 'ApiTree' :
515
+ """Create an ApiTree from an PathTree.
516
+
517
+ Args:
518
+ path_tree: The `PathTree` to convert.
519
+ score_name_fn: The name scoring function.
520
+
521
+ Returns:
522
+ an `ApiIndex`, created from `path_tree`.
523
+ """
524
+ self = cls ()
525
+
526
+ active_nodes = collections .deque (path_tree .root .children .values ())
527
+ while active_nodes :
528
+ current_node = active_nodes .popleft ()
529
+ if current_node .path in self :
530
+ continue
531
+
532
+ duplicate_nodes = set (
533
+ # Singelton objects will return [].
534
+ path_tree .nodes_for_obj (current_node .py_object ))
535
+ # Add the current node in case it's a singelton.
536
+ duplicate_nodes .add (current_node )
537
+
538
+ parents = [node .parent for node in duplicate_nodes ]
539
+
540
+ # Choose the master name with a lexical sort on the tuples returned by
541
+ # by _score_name.
542
+ if not all (parent .path in self for parent in parents ):
543
+ # rewind
544
+ active_nodes .appendleft (current_node )
545
+ # do each duplicate's immediate parents first.
546
+ for parent in parents :
547
+ if parent .path in self :
548
+ continue
549
+ active_nodes .appendleft (parent )
550
+ continue
551
+ # If we've made it here, the immediate parents of each of the paths have
552
+ # been processed, so now we can choose its master name.
553
+ aliases = [node .path for node in duplicate_nodes ]
554
+
555
+ master_path = min (['.' .join (a ) for a in aliases ], key = score_name_fn )
556
+ master_path = tuple (master_path .split ('.' ))
557
+
558
+ self .insert (master_path , current_node .py_object , aliases )
559
+
560
+ active_nodes .extend (current_node .children .values ())
561
+
562
+ return self
0 commit comments