Skip to content

Commit 300d9f5

Browse files
MarkDaoustcopybara-github
authored andcommitted
Protect against inconsistent path selection.
PiperOrigin-RevId: 442778183
1 parent 6b36a12 commit 300d9f5

File tree

2 files changed

+73
-18
lines changed

2 files changed

+73
-18
lines changed

tools/tensorflow_docs/api_generator/doc_generator_visitor.py

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import enum
2020
import inspect
2121

22-
from typing import Any, Dict, List, Optional, NamedTuple, Tuple
22+
from typing import Any, Dict, List, Optional, NamedTuple, Sequence, Tuple
2323

2424
from tensorflow_docs.api_generator import obj_type as obj_type_lib
2525

@@ -584,7 +584,7 @@ def from_path_tree(cls, path_tree: PathTree, score_name_fn) -> 'ApiTree':
584584

585585
parents = [node.parent for node in duplicate_nodes]
586586

587-
# Choose the master name with a lexical sort on the tuples returned by
587+
# Choose the priority name with a lexical sort on the tuples returned by
588588
# by _score_name.
589589
if not all(parent.path in self for parent in parents):
590590
# rewind
@@ -596,13 +596,34 @@ def from_path_tree(cls, path_tree: PathTree, score_name_fn) -> 'ApiTree':
596596
active_nodes.appendleft(parent)
597597
continue
598598
# If we've made it here, the immediate parents of each of the paths have
599-
# been processed, so now we can choose its master name.
599+
# been processed, so now we can choose its priority name.
600600
aliases = [node.path for node in duplicate_nodes]
601601

602-
master_path = min(aliases, key=score_name_fn)
602+
priority_path = self._choose_priority_path(aliases, score_name_fn)
603603

604-
self.insert(master_path, current_node.py_object, aliases)
604+
if priority_path is None:
605+
# How did this happen?
606+
# No parents in the public api -> you are not in the public API.
607+
continue
608+
609+
self.insert(priority_path, current_node.py_object, aliases)
605610

606611
active_nodes.extend(current_node.children.values())
607612

608613
return self
614+
615+
def _choose_priority_path(self, aliases: Sequence[ApiPath],
616+
score_name_fn) -> Optional[ApiPath]:
617+
# Only consider a path an option for the priority_path if its parent-path
618+
# is the priority_path for that object.
619+
priority_path_options = []
620+
for alias in aliases:
621+
parent_path = alias[:-1]
622+
623+
if self[parent_path].path == parent_path:
624+
priority_path_options.append(alias)
625+
626+
try:
627+
return min(priority_path_options, key=score_name_fn)
628+
except ValueError:
629+
return None

tools/tensorflow_docs/api_generator/doc_generator_visitor_test.py

Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
# ==============================================================================
1515
"""Tests for tools.docs.doc_generator_visitor."""
1616

17-
import argparse
1817
import inspect
1918
import io
2019
import os
@@ -262,8 +261,8 @@ class Parent(object):
262261
class PathTreeTest(absltest.TestCase):
263262

264263
def test_contains(self):
265-
tf = argparse.Namespace()
266-
tf.sub = argparse.Namespace()
264+
tf = types.ModuleType('tf')
265+
tf.sub = types.ModuleType('sub')
267266

268267
tree = doc_generator_visitor.PathTree()
269268
tree[('tf',)] = tf
@@ -273,8 +272,8 @@ def test_contains(self):
273272
self.assertIn(('tf', 'sub'), tree)
274273

275274
def test_node_insertion(self):
276-
tf = argparse.Namespace()
277-
tf.sub = argparse.Namespace()
275+
tf = types.ModuleType('tf')
276+
tf.sub = types.ModuleType('sub')
278277
tf.sub.object = object()
279278

280279
tree = doc_generator_visitor.PathTree()
@@ -290,10 +289,10 @@ def test_node_insertion(self):
290289
self.assertIs(node.children['thing'], tree[('tf', 'sub', 'thing')])
291290

292291
def test_duplicate(self):
293-
tf = argparse.Namespace()
294-
tf.sub = argparse.Namespace()
292+
tf = types.ModuleType('tf')
293+
tf.sub = types.ModuleType('sub')
295294
tf.sub.thing = object()
296-
tf.sub2 = argparse.Namespace()
295+
tf.sub2 = types.ModuleType('sub2')
297296
tf.sub2.thing = tf.sub.thing
298297

299298
tree = doc_generator_visitor.PathTree()
@@ -308,10 +307,10 @@ def test_duplicate(self):
308307
[tree[('tf', 'sub', 'thing')], tree[('tf', 'sub2', 'thing')]])
309308

310309
def test_duplicate_singleton(self):
311-
tf = argparse.Namespace()
312-
tf.sub = argparse.Namespace()
310+
tf = types.ModuleType('tf')
311+
tf.sub = types.ModuleType('sub')
313312
tf.sub.thing = 999
314-
tf.sub2 = argparse.Namespace()
313+
tf.sub2 = types.ModuleType('sub2')
315314
tf.sub2.thing = tf.sub.thing
316315

317316
tree = doc_generator_visitor.PathTree()
@@ -322,8 +321,7 @@ def test_duplicate_singleton(self):
322321
tree[('tf', 'sub2', 'thing')] = tf.sub2.thing
323322

324323
found = tree.nodes_for_obj(tf.sub.thing)
325-
self.assertIsNotNone(found)
326-
self.assertEmpty(found)
324+
self.assertEqual([], found)
327325

328326

329327
class ApiTreeTest(absltest.TestCase):
@@ -469,6 +467,42 @@ def test_api_tree_toc_integration(self):
469467

470468
self.assertEqual(expected, stream.getvalue())
471469

470+
def test_non_priority_name(self):
471+
472+
class Class1:
473+
pass
474+
475+
mod = types.ModuleType('mod')
476+
mod.a = types.ModuleType('sub')
477+
mod.a.Class1 = Class1
478+
mod.b = mod.a
479+
480+
path_tree = doc_generator_visitor.PathTree()
481+
path_tree[('mod',)] = mod
482+
path_tree[('mod', 'a')] = mod.a
483+
path_tree[('mod', 'a', 'Class1')] = mod.a.Class1
484+
path_tree[('mod', 'b')] = mod.b
485+
path_tree[('mod', 'b', 'Class1')] = mod.b.Class1
486+
487+
def inconsistent_name_score(path):
488+
# `mod.a` is prefered over `mod.b`, but `b.Class1` is prefered over
489+
# `a.Class1`!
490+
scores = {
491+
('mod',): 0,
492+
('mod', 'a'): 0, # prefer 'a'
493+
('mod', 'b'): 1,
494+
('mod', 'a', 'Class1'): 1,
495+
('mod', 'b', 'Class1'): 0, # prefer 'b.Class1'
496+
}
497+
return scores[path]
498+
499+
api_tree = doc_generator_visitor.ApiTree.from_path_tree(
500+
path_tree, inconsistent_name_score)
501+
node = api_tree.node_for_object(Class1)
502+
503+
# `Class1` can't choose `b.Class1` as its priority_path because
504+
# `a` is the priority_path for `sub`.
505+
self.assertEqual('mod.a.Class1', node.full_name)
472506

473507
if __name__ == '__main__':
474508
absltest.main()

0 commit comments

Comments
 (0)