1# Copyright 2018 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"""Documentation control decorators.""" 16 17from __future__ import absolute_import 18from __future__ import division 19from __future__ import print_function 20 21_DO_NOT_DOC = "_tf_docs_do_not_document" 22 23 24def do_not_generate_docs(obj): 25 """A decorator: Do not generate docs for this object. 26 27 For example the following classes: 28 29 ``` 30 class Parent(object): 31 def method1(self): 32 pass 33 def method2(self): 34 pass 35 36 class Child(Parent): 37 def method1(self): 38 pass 39 def method2(self): 40 pass 41 ``` 42 43 Produce the following api_docs: 44 45 ``` 46 /Parent.md 47 # method1 48 # method2 49 /Child.md 50 # method1 51 # method2 52 ``` 53 54 This decorator allows you to skip classes or methods: 55 56 ``` 57 @do_not_generate_docs 58 class Parent(object): 59 def method1(self): 60 pass 61 def method2(self): 62 pass 63 64 class Child(Parent): 65 @do_not_generate_docs 66 def method1(self): 67 pass 68 def method2(self): 69 pass 70 ``` 71 72 This will only produce the following docs: 73 74 ``` 75 /Child.md 76 # method2 77 ``` 78 79 Note: This is implemented by adding a hidden attribute on the object, so it 80 cannot be used on objects which do not allow new attributes to be added. So 81 this decorator must go *below* `@property`, `@classmethod`, 82 or `@staticmethod`: 83 84 ``` 85 class Example(object): 86 @property 87 @do_not_generate_docs 88 def x(self): 89 return self._x 90 ``` 91 92 Args: 93 obj: The object to hide from the generated docs. 94 95 Returns: 96 obj 97 """ 98 setattr(obj, _DO_NOT_DOC, None) 99 return obj 100 101 102_DO_NOT_DOC_INHERITABLE = "_tf_docs_do_not_doc_inheritable" 103 104 105def do_not_doc_inheritable(obj): 106 """A decorator: Do not generate docs for this method. 107 108 This version of the decorator is "inherited" by subclasses. No docs will be 109 generated for the decorated method in any subclass. Even if the sub-class 110 overrides the method. 111 112 For example, to ensure that `method1` is **never documented** use this 113 decorator on the base-class: 114 115 ``` 116 class Parent(object): 117 @do_not_doc_inheritable 118 def method1(self): 119 pass 120 def method2(self): 121 pass 122 123 class Child(Parent): 124 def method1(self): 125 pass 126 def method2(self): 127 pass 128 ``` 129 This will produce the following docs: 130 131 ``` 132 /Parent.md 133 # method2 134 /Child.md 135 # method2 136 ``` 137 138 When generating docs for a class's arributes, the `__mro__` is searched and 139 the attribute will be skipped if this decorator is detected on the attribute 140 on any class in the `__mro__`. 141 142 Note: This is implemented by adding a hidden attribute on the object, so it 143 cannot be used on objects which do not allow new attributes to be added. So 144 this decorator must go *below* `@property`, `@classmethod`, 145 or `@staticmethod`: 146 147 ``` 148 class Example(object): 149 @property 150 @do_not_doc_inheritable 151 def x(self): 152 return self._x 153 ``` 154 155 Args: 156 obj: The class-attribute to hide from the generated docs. 157 158 Returns: 159 obj 160 """ 161 setattr(obj, _DO_NOT_DOC_INHERITABLE, None) 162 return obj 163 164 165_FOR_SUBCLASS_IMPLEMENTERS = "_tf_docs_tools_for_subclass_implementers" 166 167 168def for_subclass_implementers(obj): 169 """A decorator: Only generate docs for this method in the defining class. 170 171 Also group this method's docs with and `@abstractmethod` in the class's docs. 172 173 No docs will generated for this class attribute in sub-classes. 174 175 The canonical use case for this is `tf.keras.layers.Layer.call`: It's a 176 public method, essential for anyone implementing a subclass, but it should 177 never be called directly. 178 179 Works on method, or other class-attributes. 180 181 When generating docs for a class's arributes, the `__mro__` is searched and 182 the attribute will be skipped if this decorator is detected on the attribute 183 on any **parent** class in the `__mro__`. 184 185 For example: 186 187 ``` 188 class Parent(object): 189 @for_subclass_implementers 190 def method1(self): 191 pass 192 def method2(self): 193 pass 194 195 class Child1(Parent): 196 def method1(self): 197 pass 198 def method2(self): 199 pass 200 201 class Child2(Parent): 202 def method1(self): 203 pass 204 def method2(self): 205 pass 206 ``` 207 208 This will produce the following docs: 209 210 ``` 211 /Parent.md 212 # method1 213 # method2 214 /Child1.md 215 # method2 216 /Child2.md 217 # method2 218 ``` 219 220 Note: This is implemented by adding a hidden attribute on the object, so it 221 cannot be used on objects which do not allow new attributes to be added. So 222 this decorator must go *below* `@property`, `@classmethod`, 223 or `@staticmethod`: 224 225 ``` 226 class Example(object): 227 @property 228 @for_subclass_implementers 229 def x(self): 230 return self._x 231 ``` 232 233 Args: 234 obj: The class-attribute to hide from the generated docs. 235 236 Returns: 237 obj 238 """ 239 setattr(obj, _FOR_SUBCLASS_IMPLEMENTERS, None) 240 return obj 241 242 243def should_skip(obj): 244 """Returns true if docs generation should be skipped for this object. 245 246 checks for the `do_not_generate_docs` or `do_not_doc_inheritable` decorators. 247 248 Args: 249 obj: The object to document, or skip. 250 251 Returns: 252 True if the object should be skipped 253 """ 254 # Unwrap fget if the object is a property 255 if isinstance(obj, property): 256 obj = obj.fget 257 258 return hasattr(obj, _DO_NOT_DOC) or hasattr(obj, _DO_NOT_DOC_INHERITABLE) 259 260 261def should_skip_class_attr(cls, name): 262 """Returns true if docs should be skipped for this class attribute. 263 264 Args: 265 cls: The class the attribute belongs to. 266 name: The name of the attribute. 267 268 Returns: 269 True if the attribute should be skipped. 270 """ 271 # Get the object with standard lookup, from the nearest 272 # defining parent. 273 try: 274 obj = getattr(cls, name) 275 except AttributeError: 276 # Avoid error caused by enum metaclasses in python3 277 if name in ("name", "value"): 278 return True 279 raise 280 281 # Unwrap fget if the object is a property 282 if isinstance(obj, property): 283 obj = obj.fget 284 285 # Skip if the object is decorated with `do_not_generate_docs` or 286 # `do_not_doc_inheritable` 287 if should_skip(obj): 288 return True 289 290 # Use __dict__ lookup to get the version defined in *this* class. 291 obj = cls.__dict__.get(name, None) 292 if isinstance(obj, property): 293 obj = obj.fget 294 if obj is not None: 295 # If not none, the object is defined in *this* class. 296 # Do not skip if decorated with `for_subclass_implementers`. 297 if hasattr(obj, _FOR_SUBCLASS_IMPLEMENTERS): 298 return False 299 300 # for each parent class 301 for parent in cls.__mro__[1:]: 302 obj = getattr(parent, name, None) 303 304 if obj is None: 305 continue 306 307 if isinstance(obj, property): 308 obj = obj.fget 309 310 # Skip if the parent's definition is decorated with `do_not_doc_inheritable` 311 # or `for_subclass_implementers` 312 if hasattr(obj, _DO_NOT_DOC_INHERITABLE): 313 return True 314 315 if hasattr(obj, _FOR_SUBCLASS_IMPLEMENTERS): 316 return True 317 318 # No blockng decorators --> don't skip 319 return False 320