1818"""
1919
2020from argparse import ArgumentParser , RawDescriptionHelpFormatter
21+ from enum import Enum
2122import importlib
2223import inspect
24+ from itertools import chain
2325import json
2426import os
2527import re
3032sys .path .insert (0 , os .path .abspath (".." ))
3133
3234import labelbox
33- import labelbox .utils
35+ from labelbox .utils import snake_case
3436from labelbox .exceptions import LabelboxError
37+ from labelbox .orm .db_object import Deletable , BulkDeletable , Updateable
3538
3639
3740GENERAL_CLASSES = [labelbox .Client ]
@@ -70,16 +73,24 @@ def qual_class_name(cls):
7073 return cls .__module__ + "." + cls .__name__
7174
7275
73- def header (level , text ):
76+ def header (level , text , header_id = None ):
7477 """ Wraps `text` into a <h> (header) tag ov the given level.
7578 Automatically increases the level by 2 to be inline with HelpDocs
7679 standards (h1 -> h3).
7780
7881 Example:
7982 >>> header(2, "My Chapter")
8083 >>> "<h4>My Chapter</h4>
84+
85+ Args:
86+ level (int): Level of header.
87+ text (str): Header text.
88+ header_id (str or None): The ID of the header. If None it's
89+ generated from text by converting to snake_case and
90+ replacing all whitespace with "_".
8191 """
82- header_id = labelbox .utils .snake_case (text ).replace (" " , "_" )
92+ if header_id == None :
93+ header_id = snake_case (text ).replace (" " , "_" )
8394 # Convert to level + 2 for HelpDocs standard.
8495 return tag (text , "h" + str (level + 2 ), {"id" : header_id })
8596
@@ -127,8 +138,7 @@ def class_link(cls, text):
127138 >>> class_link(Project, "blah")
128139 >>> <a href="#class_labelbox_schema_project">blah</a>
129140 """
130- header_id = "class_" + labelbox .utils .snake_case (qual_class_name (cls ).
131- replace ("." , "_" ))
141+ header_id = "class_" + snake_case (qual_class_name (cls ).replace ("." , "_" ))
132142 return header_link (text , header_id )
133143
134144
@@ -265,18 +275,33 @@ def generate_functions(cls, predicate):
265275 Textual documentation of functions belonging to the given
266276 class that satisfy the given predicate.
267277 """
268- text = []
269- for name , attr in cls .__dict__ .items ():
270- if predicate (attr ):
271- # static and class methods gave the __func__ attribute
272- # with the original definition that we need.
273- attr = getattr (attr , "__func__" , attr )
274- if not name .startswith ("_" ) or (cls == labelbox .Client and
275- name == "__init__" ):
276- text .append (paragraph (generate_signature (attr )))
277- text .append (preprocess_docstring (attr .__doc__ ))
278+ def name_predicate (attr ):
279+ return not name .startswith ("_" ) or (cls == labelbox .Client and
280+ name == "__init__" )
281+
282+ # Get all class atrributes plus selected superclass attributes.
283+ attributes = chain (
284+ cls .__dict__ .values (),
285+ (getattr (cls , name ) for name in ("delete" , "update" )
286+ if name in dir (cls ) and name not in cls .__dict__ ))
287+
288+ # Remove attributes not satisfying the predicate
289+ attributes = filter (predicate , attributes )
278290
279- return "" .join (text )
291+ # Extract function from staticmethod and classmethod wrappers
292+ attributes = map (lambda attr : getattr (attr , "__func__" , attr ), attributes )
293+
294+ # Apply name filter
295+ attributes = filter (lambda attr : not attr .__name__ .startswith ("_" ) or \
296+ (cls == labelbox .Client and attr .__name__ == "__init__" ),
297+ attributes )
298+
299+ # Sort on name
300+ attributes = sorted (attributes , key = lambda attr : attr .__name__ )
301+
302+ return "" .join (paragraph (generate_signature (function )) +
303+ preprocess_docstring (function .__doc__ )
304+ for function in attributes )
280305
281306
282307def generate_signature (method ):
@@ -327,6 +352,20 @@ def generate_relationships(cls):
327352 return unordered_list (relationships )
328353
329354
355+ def generate_constants (cls ):
356+ values = []
357+ for name , value in cls .__dict__ .items ():
358+ if name .isupper () and isinstance (value , (str , int , float , bool )):
359+ values .append ("%s %s" % (name , em ("(" + type (value ).__name__ + ")" )))
360+
361+ for name , value in cls .__dict__ .items ():
362+ if isinstance (value , type ) and issubclass (value , Enum ):
363+ enumeration_items = unordered_list ([item .name for item in value ])
364+ values .append ("Enumeration %s%s" % (name , enumeration_items ))
365+
366+ return unordered_list (values )
367+
368+
330369def generate_class (cls , schema_class ):
331370 """ Generates HelpDocs style documentation for the given class.
332371 Args:
@@ -337,8 +376,24 @@ def generate_class(cls, schema_class):
337376 methods and fields and relationships if `schema_class`.
338377 """
339378 text = []
340- text .append (header (2 , "Class " + cls .__module__ + "." + cls .__name__ ))
379+
380+ title = "Class " + cls .__module__ + "." + cls .__name__
381+ title_id = re .sub (r"\s+" , "_" , snake_case (title ).lower ())
382+ if schema_class :
383+ superclasses = [plugin .__name__ for plugin
384+ in (Updateable , Deletable , BulkDeletable )
385+ if issubclass (cls , plugin )]
386+ if superclasses :
387+ title += " (%s)" % ", " .join (superclasses )
388+ text .append (header (2 , title , title_id ))
389+
341390 text .append (preprocess_docstring (cls .__doc__ ))
391+
392+ constants = generate_constants (cls )
393+ if constants :
394+ text .append (header (3 , "Constants" ))
395+ text .append (constants )
396+
342397 if schema_class :
343398 text .append (header (3 , "Fields" ))
344399 text .append (generate_fields (cls ))
0 commit comments