aboutsummaryrefslogtreecommitdiffhomepage
path: root/tensorflow/tools/docs/doc_controls.py
blob: 5e526443cc977ed6f2b68b43b385f57992560d4d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
# Copyright 2018 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Documentation control decorators."""

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

_DO_NOT_DOC = "_tf_docs_do_not_document"


def do_not_generate_docs(obj):
  """A decorator: Do not generate docs for this object.

  For example the following classes:

  ```
  class Parent(object):
    def method1(self):
      pass
    def method2(self):
      pass

  class Child(Parent):
    def method1(self):
      pass
    def method2(self):
      pass
  ```

  Produce the following api_docs:

  ```
  /Parent.md
    # method1
    # method2
  /Child.md
    # method1
    # method2
  ```

  This decorator allows you to skip classes or methods:

  ```
  @do_not_generate_docs
  class Parent(object):
    def method1(self):
      pass
    def method2(self):
      pass

  class Child(Parent):
    @do_not_generate_docs
    def method1(self):
      pass
    def method2(self):
      pass
  ```

  This will only produce the following docs:

  ```
  /Child.md
    # method2
  ```

  Note: This is implemented by adding a hidden attribute on the object, so it
  cannot be used on objects which do not allow new attributes to be added. So
  this decorator must go *below* `@property`, `@classmethod`,
  or `@staticmethod`:

  ```
  class Example(object):
    @property
    @do_not_generate_docs
    def x(self):
      return self._x
  ```

  Args:
    obj: The object to hide from the generated docs.

  Returns:
    obj
  """
  setattr(obj, _DO_NOT_DOC, None)
  return obj


_DO_NOT_DOC_INHERITABLE = "_tf_docs_do_not_doc_inheritable"


def do_not_doc_inheritable(obj):
  """A decorator: Do not generate docs for this method.

  This version of the decorator is "inherited" by subclasses. No docs will be
  generated for the decorated method in any subclass. Even if the sub-class
  overrides the method.

  For example, to ensure that `method1` is **never documented** use this
  decorator on the base-class:

  ```
  class Parent(object):
    @do_not_doc_inheritable
    def method1(self):
      pass
    def method2(self):
      pass

  class Child(Parent):
    def method1(self):
      pass
    def method2(self):
      pass
  ```
  This will produce the following docs:

  ```
  /Parent.md
    # method2
  /Child.md
    # method2
  ```

  When generating docs for a class's arributes, the `__mro__` is searched and
  the attribute will be skipped if this decorator is detected on the attribute
  on any class in the `__mro__`.

  Note: This is implemented by adding a hidden attribute on the object, so it
  cannot be used on objects which do not allow new attributes to be added. So
  this decorator must go *below* `@property`, `@classmethod`,
  or `@staticmethod`:

  ```
  class Example(object):
    @property
    @do_not_doc_inheritable
    def x(self):
      return self._x
  ```

  Args:
    obj: The class-attribute to hide from the generated docs.

  Returns:
    obj
  """
  setattr(obj, _DO_NOT_DOC_INHERITABLE, None)
  return obj


_FOR_SUBCLASS_IMPLEMENTERS = "_tf_docs_tools_for_subclass_implementers"


def for_subclass_implementers(obj):
  """A decorator: Only generate docs for this method in the defining class.

  Also group this method's docs with and `@abstractmethod` in the class's docs.

  No docs will generated for this class attribute in sub-classes.

  The canonical use case for this is `tf.keras.layers.Layer.call`: It's a
  public method, essential for anyone implementing a subclass, but it should
  never be called directly.

  Works on method, or other class-attributes.

  When generating docs for a class's arributes, the `__mro__` is searched and
  the attribute will be skipped if this decorator is detected on the attribute
  on any **parent** class in the `__mro__`.

  For example:

  ```
  class Parent(object):
    @for_subclass_implementers
    def method1(self):
      pass
    def method2(self):
      pass

  class Child1(Parent):
    def method1(self):
      pass
    def method2(self):
      pass

  class Child2(Parent):
    def method1(self):
      pass
    def method2(self):
      pass
  ```

  This will produce the following docs:

  ```
  /Parent.md
    # method1
    # method2
  /Child1.md
    # method2
  /Child2.md
    # method2
  ```

  Note: This is implemented by adding a hidden attribute on the object, so it
  cannot be used on objects which do not allow new attributes to be added. So
  this decorator must go *below* `@property`, `@classmethod`,
  or `@staticmethod`:

  ```
  class Example(object):
    @property
    @for_subclass_implementers
    def x(self):
      return self._x
  ```

  Args:
    obj: The class-attribute to hide from the generated docs.

  Returns:
    obj
  """
  setattr(obj, _FOR_SUBCLASS_IMPLEMENTERS, None)
  return obj


def should_skip(obj):
  """Returns true if docs generation should be skipped for this object.

  checks for the `do_not_generate_docs` or `do_not_doc_inheritable` decorators.

  Args:
    obj: The object to document, or skip.

  Returns:
    True if the object should be skipped
  """
  # Unwrap fget if the object is a property
  if isinstance(obj, property):
    obj = obj.fget

  return hasattr(obj, _DO_NOT_DOC) or hasattr(obj, _DO_NOT_DOC_INHERITABLE)


def should_skip_class_attr(cls, name):
  """Returns true if docs should be skipped for this class attribute.

  Args:
    cls: The class the attribute belongs to.
    name: The name of the attribute.

  Returns:
    True if the attribute should be skipped.
  """
  # Get the object with standard lookup, from the nearest
  # defining parent.
  try:
    obj = getattr(cls, name)
  except AttributeError:
    # Avoid error caused by enum metaclasses in python3
    if name in ("name", "value"):
      return True
    raise

  # Unwrap fget if the object is a property
  if isinstance(obj, property):
    obj = obj.fget

  # Skip if the object is decorated with `do_not_generate_docs` or
  # `do_not_doc_inheritable`
  if should_skip(obj):
    return True

  # Use __dict__ lookup to get the version defined in *this* class.
  obj = cls.__dict__.get(name, None)
  if isinstance(obj, property):
    obj = obj.fget
  if obj is not None:
    # If not none, the object is defined in *this* class.
    # Do not skip if decorated with `for_subclass_implementers`.
    if hasattr(obj, _FOR_SUBCLASS_IMPLEMENTERS):
      return False

  # for each parent class
  for parent in cls.__mro__[1:]:
    obj = getattr(parent, name, None)

    if obj is None:
      continue

    if isinstance(obj, property):
      obj = obj.fget

    # Skip if the parent's definition is decorated with `do_not_doc_inheritable`
    # or `for_subclass_implementers`
    if hasattr(obj, _DO_NOT_DOC_INHERITABLE):
      return True

    if hasattr(obj, _FOR_SUBCLASS_IMPLEMENTERS):
      return True

  # No blockng decorators --> don't skip
  return False