• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2015 The TensorFlow Authors. All Rights Reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14# ==============================================================================
15"""A `traverse` visitor for processing documentation."""
16
17from __future__ import absolute_import
18from __future__ import division
19from __future__ import print_function
20
21import six
22
23from tensorflow.python.util import tf_export
24from tensorflow.python.util import tf_inspect
25
26
27class DocGeneratorVisitor(object):
28  """A visitor that generates docs for a python object when __call__ed."""
29
30  def __init__(self, root_name=''):
31    """Make a visitor.
32
33    As this visitor is starting its traversal at a module or class, it will not
34    be told the name of that object during traversal. `root_name` is the name it
35    should use for that object, effectively prefixing all names with
36    "root_name.".
37
38    Args:
39      root_name: The name of the root module/class.
40    """
41    self.set_root_name(root_name)
42    self._index = {}
43    self._tree = {}
44    self._reverse_index = None
45    self._duplicates = None
46    self._duplicate_of = None
47
48  def set_root_name(self, root_name):
49    """Sets the root name for subsequent __call__s."""
50    self._root_name = root_name or ''
51    self._prefix = (root_name + '.') if root_name else ''
52
53  @property
54  def index(self):
55    """A map from fully qualified names to objects to be documented.
56
57    The index is filled when the visitor is passed to `traverse`.
58
59    Returns:
60      The index filled by traversal.
61    """
62    return self._index
63
64  @property
65  def tree(self):
66    """A map from fully qualified names to all its child names for traversal.
67
68    The full name to member names map is filled when the visitor is passed to
69    `traverse`.
70
71    Returns:
72      The full name to member name map filled by traversal.
73    """
74    return self._tree
75
76  @property
77  def reverse_index(self):
78    """A map from `id(object)` to the preferred fully qualified name.
79
80    This map only contains non-primitive objects (no numbers or strings) present
81    in `index` (for primitive objects, `id()` doesn't quite do the right thing).
82
83    It is computed when it, `duplicate_of`, or `duplicates` are first accessed.
84
85    Returns:
86      The `id(object)` to full name map.
87    """
88    self._maybe_find_duplicates()
89    return self._reverse_index
90
91  @property
92  def duplicate_of(self):
93    """A map from duplicate full names to a preferred fully qualified name.
94
95    This map only contains names that are not themself a preferred name.
96
97    It is computed when it, `reverse_index`, or `duplicates` are first accessed.
98
99    Returns:
100      The map from duplicate name to preferred name.
101    """
102    self._maybe_find_duplicates()
103    return self._duplicate_of
104
105  @property
106  def duplicates(self):
107    """A map from preferred full names to a list of all names for this symbol.
108
109    This function returns a map from preferred (master) name for a symbol to a
110    lexicographically sorted list of all aliases for that name (incl. the master
111    name). Symbols without duplicate names do not appear in this map.
112
113    It is computed when it, `reverse_index`, or `duplicate_of` are first
114    accessed.
115
116    Returns:
117      The map from master name to list of all duplicate names.
118    """
119    self._maybe_find_duplicates()
120    return self._duplicates
121
122  def _add_prefix(self, name):
123    """Adds the root name to a name."""
124    return self._prefix + name if name else self._root_name
125
126  def __call__(self, parent_name, parent, children):
127    """Visitor interface, see `tensorflow/tools/common:traverse` for details.
128
129    This method is called for each symbol found in a traversal using
130    `tensorflow/tools/common:traverse`. It should not be called directly in
131    user code.
132
133    Args:
134      parent_name: The fully qualified name of a symbol found during traversal.
135      parent: The Python object referenced by `parent_name`.
136      children: A list of `(name, py_object)` pairs enumerating, in alphabetical
137        order, the children (as determined by `tf_inspect.getmembers`) of
138          `parent`. `name` is the local name of `py_object` in `parent`.
139
140    Raises:
141      RuntimeError: If this visitor is called with a `parent` that is not a
142        class or module.
143    """
144    parent_name = self._add_prefix(parent_name)
145    self._index[parent_name] = parent
146    self._tree[parent_name] = []
147
148    if not (tf_inspect.ismodule(parent) or tf_inspect.isclass(parent)):
149      raise RuntimeError('Unexpected type in visitor -- %s: %r' % (parent_name,
150                                                                   parent))
151
152    for i, (name, child) in enumerate(list(children)):
153      # Don't document __metaclass__
154      if name in ['__metaclass__']:
155        del children[i]
156        continue
157
158      full_name = '.'.join([parent_name, name]) if parent_name else name
159      self._index[full_name] = child
160      self._tree[parent_name].append(name)
161
162  def _score_name(self, name):
163    """Return a tuple of scores indicating how to sort for the best name.
164
165    This function is meant to be used as the `key` to the `sorted` function.
166
167    This sorting in order:
168      Prefers names refering to the defining class, over a subclass.
169      Prefers names that are not in "contrib".
170      prefers submodules to the root namespace.
171      Prefers short names `tf.thing` over `tf.a.b.c.thing`
172      Sorts lexicographically on name parts.
173
174    Args:
175      name: the full name to score, for example `tf.estimator.Estimator`
176
177    Returns:
178      A tuple of scores. When sorted the preferred name will have the lowest
179      value.
180    """
181    parts = name.split('.')
182    short_name = parts[-1]
183
184    container = self._index['.'.join(parts[:-1])]
185
186    defining_class_score = 1
187    if tf_inspect.isclass(container):
188      if short_name in container.__dict__:
189        # prefer the defining class
190        defining_class_score = -1
191
192    contrib_score = -1
193    if 'contrib' in parts:
194      contrib_score = 1
195
196    while parts:
197      container = self._index['.'.join(parts)]
198      if tf_inspect.ismodule(container):
199        break
200      parts.pop()
201
202    module_length = len(parts)
203    if len(parts) == 2:
204      # `tf.submodule.thing` is better than `tf.thing`
205      module_length_score = -1
206    else:
207      # shorter is better
208      module_length_score = module_length
209
210    return (defining_class_score, contrib_score, module_length_score, name)
211
212  def _maybe_find_duplicates(self):
213    """Compute data structures containing information about duplicates.
214
215    Find duplicates in `index` and decide on one to be the "master" name.
216
217    Computes a reverse_index mapping each object id to its master name.
218
219    Also computes a map `duplicate_of` from aliases to their master name (the
220    master name itself has no entry in this map), and a map `duplicates` from
221    master names to a lexicographically sorted list of all aliases for that name
222    (incl. the master name).
223
224    All these are computed and set as fields if they haven't already.
225    """
226    if self._reverse_index is not None:
227      return
228
229    # Maps the id of a symbol to its fully qualified name. For symbols that have
230    # several aliases, this map contains the first one found.
231    # We use id(py_object) to get a hashable value for py_object. Note all
232    # objects in _index are in memory at the same time so this is safe.
233    reverse_index = {}
234
235    # Make a preliminary duplicates map. For all sets of duplicate names, it
236    # maps the first name found to a list of all duplicate names.
237    raw_duplicates = {}
238    for full_name, py_object in six.iteritems(self._index):
239      # We cannot use the duplicate mechanism for some constants, since e.g.,
240      # id(c1) == id(c2) with c1=1, c2=1. This is unproblematic since constants
241      # have no usable docstring and won't be documented automatically.
242      if (py_object is not None and
243          not isinstance(py_object, six.integer_types + six.string_types +
244                         (six.binary_type, six.text_type, float, complex, bool))
245          and py_object is not ()):  # pylint: disable=literal-comparison
246        object_id = id(py_object)
247        if object_id in reverse_index:
248          master_name = reverse_index[object_id]
249          if master_name in raw_duplicates:
250            raw_duplicates[master_name].append(full_name)
251          else:
252            raw_duplicates[master_name] = [master_name, full_name]
253        else:
254          reverse_index[object_id] = full_name
255    # Decide on master names, rewire duplicates and make a duplicate_of map
256    # mapping all non-master duplicates to the master name. The master symbol
257    # does not have an entry in this map.
258    duplicate_of = {}
259    # Duplicates maps the main symbols to the set of all duplicates of that
260    # symbol (incl. itself).
261    duplicates = {}
262    for names in raw_duplicates.values():
263      names = sorted(names)
264      master_name = (
265          tf_export.get_canonical_name_for_symbol(self._index[names[0]])
266          if names else None)
267      if master_name:
268        master_name = 'tf.%s' % master_name
269      else:
270        # Choose the master name with a lexical sort on the tuples returned by
271        # by _score_name.
272        master_name = min(names, key=self._score_name)
273
274      duplicates[master_name] = names
275      for name in names:
276        if name != master_name:
277          duplicate_of[name] = master_name
278
279      # Set the reverse index to the canonical name.
280      reverse_index[id(self._index[master_name])] = master_name
281
282    self._duplicate_of = duplicate_of
283    self._duplicates = duplicates
284    self._reverse_index = reverse_index
285