Skip to content

Commit a541d7e

Browse files
MarkDaoustcopybara-github
authored andcommitted
Add an ApiTree data-structure.
doc_generator_visitor -> build the ApiTree from the PathTree. toc -> add code to generate the toc from the ApiTree (not used yet). PiperOrigin-RevId: 438850818
1 parent a3fbfca commit a541d7e

File tree

6 files changed

+539
-84
lines changed

6 files changed

+539
-84
lines changed

tools/tensorflow_docs/api_generator/config.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ class ParserConfig(object):
1818
"""Stores all indexes required to parse the docs."""
1919

2020
def __init__(self, reference_resolver, duplicates, duplicate_of, tree, index,
21-
reverse_index, base_dir, code_url_prefix):
21+
reverse_index, path_tree, base_dir, code_url_prefix):
2222
"""Object with the common config for docs_for_object() calls.
2323
2424
Args:
@@ -42,11 +42,10 @@ def __init__(self, reference_resolver, duplicates, duplicate_of, tree, index,
4242
self.tree = tree
4343
self.reverse_index = reverse_index
4444
self.index = index
45+
self.path_tree = path_tree
4546
self.base_dir = base_dir
4647
self.code_url_prefix = code_url_prefix
4748

4849
def py_name_to_object(self, full_name):
4950
"""Return the Python object for a Python symbol name."""
5051
return self.index[full_name]
51-
52-

tools/tensorflow_docs/api_generator/doc_generator_visitor.py

Lines changed: 169 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,13 @@
1616

1717
import collections
1818
import dataclasses
19+
import functools
1920
import inspect
2021

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+
2226

2327
ApiPath = Tuple[str, ...]
2428

@@ -75,6 +79,9 @@ def __repr__(self):
7579

7680
__str__ = __repr__
7781

82+
def __eq__(self, other):
83+
raise ValueError("Don't try to compare these")
84+
7885
@property
7986
def short_name(self) -> str:
8087
return self.path[-1]
@@ -84,70 +91,38 @@ def full_name(self) -> str:
8491
return '.'.join(self.path)
8592

8693

87-
class PathTree(Mapping[ApiPath, PathTreeNode]):
94+
class PathTree(Dict[ApiPath, PathTreeNode]):
8895
"""An index/tree of all object-paths in the API.
8996
9097
Items must be inserted in order, from root to leaf.
9198
92-
Acts as a Dict[ApiPath, PathTreeNode].
9399
94100
Attributes:
95101
root: The root `PathTreeNode`
96102
"""
97103

98104
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)
101107

102108
self.root: PathTreeNode = root
103109
self._nodes_for_id: Dict[int, List[PathTreeNode]] = (
104110
collections.defaultdict(list))
105111

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")
143114

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)
148122

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)
151126

152127
def __setitem__(self, path: ApiPath, obj: Any):
153128
"""Add an object to the tree.
@@ -157,16 +132,17 @@ def __setitem__(self, path: ApiPath, obj: Any):
157132
obj: The python object.
158133
"""
159134
parent_path = path[:-1]
160-
parent = self._index[parent_path]
135+
parent = self[parent_path]
161136

162137
node = PathTreeNode(path=path, py_object=obj, parent=parent)
163138

164-
self._index[path] = node
139+
super().__setitem__(path, node)
165140
if not maybe_singleton(obj):
166141
# We cannot use the duplicate mechanism for some constants, since e.g.,
167142
# id(c1) == id(c2) with c1=1, c2=1. This isn't problematic since constants
168143
# 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)
170146
parent.children[node.short_name] = node
171147

172148
def nodes_for_obj(self, py_object) -> List[PathTreeNode]:
@@ -315,7 +291,7 @@ class or module.
315291

316292
return children
317293

318-
def _score_name(self, name):
294+
def _score_name(self, name: str):
319295
"""Return a tuple of scores indicating how to sort for the best name.
320296
321297
This function is meant to be used as the `key` to the `sorted` function.
@@ -443,3 +419,144 @@ def build(self):
443419
self._duplicate_of = duplicate_of
444420
self._duplicates = duplicates
445421
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

Comments
 (0)