Skip to content

Commit 84ee2d1

Browse files
Improve API docs generation
1 parent 0fa39ed commit 84ee2d1

File tree

3 files changed

+83
-19
lines changed

3 files changed

+83
-19
lines changed

labelbox/orm/query.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -580,7 +580,7 @@ def create_webhook(topics, url, secret, project):
580580
return query_str, {}
581581

582582

583-
def edit_webhook(webhook, topics, url, status):
583+
def update_webhook(webhook, topics, url, status):
584584
topics_str = "" if topics is None else "topics: {set: [%s]}" % " ".join(topics)
585585
url_str = "" if url is None else "url: \"%s\"" % url
586586
status_str = "" if status is None else "status: %s" % status

labelbox/schema.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,9 @@ def create_benchmark(self):
420420
return Benchmark(self.client, res)
421421

422422
class Review(DbObject, Deletable, Updateable):
423+
""" Reviewing labeled data is a collaborative quality assurance technique.
424+
A Review object indicates the quality of the assigned Label. The aggregated
425+
review numbers can be obtained on a Project object. """
423426

424427
class NetScore(Enum):
425428
Negative = auto()
@@ -632,9 +635,15 @@ def create(client, topics, url, secret, project):
632635
project = Relationship.ToOne("Project")
633636

634637
def update(self, topics=None, url=None, status=None):
638+
""" Updates this Webhook.
639+
Args:
640+
topics (list of str): The new topics value, optional.
641+
url (str): The new URL value, optional.
642+
status (str): The new status value, optional.
643+
"""
635644
# Webhook has a custom `update` function due to custom types
636645
# in `status` and `topics` fields.
637-
query_str, params = query.edit_webhook(self, topics, url, status)
646+
query_str, params = query.update_webhook(self, topics, url, status)
638647
res = self.client.execute(query_str, params)
639648
res = res["data"]["updateWebhook"]
640649
self._set_field_values(res)

tools/db_object_doc_gen.py

Lines changed: 72 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@
1818
"""
1919

2020
from argparse import ArgumentParser, RawDescriptionHelpFormatter
21+
from enum import Enum
2122
import importlib
2223
import inspect
24+
from itertools import chain
2325
import json
2426
import os
2527
import re
@@ -30,8 +32,9 @@
3032
sys.path.insert(0, os.path.abspath(".."))
3133

3234
import labelbox
33-
import labelbox.utils
35+
from labelbox.utils import snake_case
3436
from labelbox.exceptions import LabelboxError
37+
from labelbox.orm.db_object import Deletable, BulkDeletable, Updateable
3538

3639

3740
GENERAL_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

282307
def 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+
330369
def 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

Comments
 (0)