Skip to content

Commit 5e89dd4

Browse files
Update docstrings to address #1205
1 parent 4ce87ee commit 5e89dd4

File tree

1 file changed

+100
-73
lines changed

1 file changed

+100
-73
lines changed

dash/development/_py_components_generation.py

Lines changed: 100 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from collections import OrderedDict
22
import copy
33
import os
4+
from textwrap import fill, indent
45

56
from dash.development.base_component import _explicitize_args
67
from dash.exceptions import NonExistentEventException
@@ -12,16 +13,13 @@
1213
def generate_class_string(typename, props, description, namespace):
1314
"""Dynamically generate class strings to have nicely formatted docstrings,
1415
keyword arguments, and repr.
15-
1616
Inspired by http://jameso.be/2013/08/06/namedtuple.html
17-
1817
Parameters
1918
----------
2019
typename
2120
props
2221
description
2322
namespace
24-
2523
Returns
2624
-------
2725
string
@@ -53,12 +51,10 @@ def __init__(self, {default_argtext}):
5351
self.available_properties = {list_of_valid_keys}
5452
self.available_wildcard_properties =\
5553
{list_of_valid_wildcard_attr_prefixes}
56-
5754
_explicit_args = kwargs.pop('_explicit_args')
5855
_locals = locals()
5956
_locals.update(kwargs) # For wildcard attrs
6057
args = {{k: _locals[k] for k in _explicit_args if k != 'children'}}
61-
6258
for k in {required_props}:
6359
if k not in args:
6460
raise TypeError(
@@ -112,14 +108,12 @@ def __init__(self, {default_argtext}):
112108

113109
def generate_class_file(typename, props, description, namespace):
114110
"""Generate a Python class file (.py) given a class string.
115-
116111
Parameters
117112
----------
118113
typename
119114
props
120115
description
121116
namespace
122-
123117
Returns
124118
-------
125119
"""
@@ -170,14 +164,12 @@ def generate_classes_files(project_shortname, metadata, *component_generators):
170164

171165
def generate_class(typename, props, description, namespace):
172166
"""Generate a Python class object given a class string.
173-
174167
Parameters
175168
----------
176169
typename
177170
props
178171
description
179172
namespace
180-
181173
Returns
182174
-------
183175
"""
@@ -191,11 +183,9 @@ def generate_class(typename, props, description, namespace):
191183

192184
def required_props(props):
193185
"""Pull names of required props from the props object.
194-
195186
Parameters
196187
----------
197188
props: dict
198-
199189
Returns
200190
-------
201191
list
@@ -206,7 +196,6 @@ def required_props(props):
206196

207197
def create_docstring(component_name, props, description):
208198
"""Create the Dash component docstring.
209-
210199
Parameters
211200
----------
212201
component_name: str
@@ -215,7 +204,6 @@ def create_docstring(component_name, props, description):
215204
Dictionary with {propName: propMetadata} structure
216205
description: str
217206
Component description
218-
219207
Returns
220208
-------
221209
str
@@ -248,12 +236,10 @@ def create_docstring(component_name, props, description):
248236
def prohibit_events(props):
249237
"""Events have been removed. Raise an error if we see dashEvents or
250238
fireEvents.
251-
252239
Parameters
253240
----------
254241
props: dict
255242
Dictionary with {propName: propMetadata} structure
256-
257243
Raises
258244
-------
259245
?
@@ -267,12 +253,10 @@ def prohibit_events(props):
267253

268254
def parse_wildcards(props):
269255
"""Pull out the wildcard attributes from the Component props.
270-
271256
Parameters
272257
----------
273258
props: dict
274259
Dictionary with {propName: propMetadata} structure
275-
276260
Returns
277261
-------
278262
list
@@ -287,43 +271,37 @@ def parse_wildcards(props):
287271

288272
def reorder_props(props):
289273
"""If "children" is in props, then move it to the front to respect dash
290-
convention.
291-
274+
convention, then 'id', then the remaining props sorted by prop name
292275
Parameters
293276
----------
294277
props: dict
295278
Dictionary with {propName: propMetadata} structure
296-
297279
Returns
298280
-------
299281
dict
300282
Dictionary with {propName: propMetadata} structure
301283
"""
302-
if "children" in props:
303-
# Constructing an OrderedDict with duplicate keys, you get the order
304-
# from the first one but the value from the last.
305-
# Doing this to avoid mutating props, which can cause confusion.
306-
props = OrderedDict([("children", "")] + list(props.items()))
307284

308-
return props
285+
# Constructing an OrderedDict with duplicate keys, you get the order
286+
# from the first one but the value from the last.
287+
# Doing this to avoid mutating props, which can cause confusion.
288+
props1 = [("children", "")] if "children" in props else []
289+
props2 = [("id", "")] if "id" in props else []
290+
return OrderedDict(props1 + props2 + sorted(list(props.items())))
309291

310292

311293
def filter_props(props):
312294
"""Filter props from the Component arguments to exclude:
313-
314295
- Those without a "type" or a "flowType" field
315296
- Those with arg.type.name in {'func', 'symbol', 'instanceOf'}
316-
317297
Parameters
318298
----------
319299
props: dict
320300
Dictionary with {propName: propMetadata} structure
321-
322301
Returns
323302
-------
324303
dict
325304
Filtered dictionary with {propName: propMetadata} structure
326-
327305
Examples
328306
--------
329307
```python
@@ -380,7 +358,18 @@ def filter_props(props):
380358
return filtered_props
381359

382360

361+
def fix_keywords(txt):
362+
"""
363+
replaces javascript keywords true, false, null with Python keywords
364+
"""
365+
fix_word = {"true": "True", "false": "False", "null": "None"}
366+
for js_keyword, python_keyword in fix_word.items():
367+
txt = txt.replace(js_keyword, python_keyword)
368+
return txt
369+
370+
383371
# pylint: disable=too-many-arguments
372+
# pylint: disable=too-many-locals
384373
def create_prop_docstring(
385374
prop_name,
386375
type_object,
@@ -391,7 +380,6 @@ def create_prop_docstring(
391380
is_flow_type=False,
392381
):
393382
"""Create the Dash component prop docstring.
394-
395383
Parameters
396384
----------
397385
prop_name: str
@@ -411,71 +399,109 @@ def create_prop_docstring(
411399
(creates 2 spaces for every indent)
412400
is_flow_type: bool
413401
Does the prop use Flow types? Otherwise, uses PropTypes
414-
415402
Returns
416403
-------
417404
str
418405
Dash component prop docstring
419406
"""
420407
py_type_name = js_to_py_type(
421-
type_object=type_object, is_flow_type=is_flow_type, indent_num=indent_num + 1
408+
type_object=type_object, is_flow_type=is_flow_type, indent_num=indent_num
422409
)
423410
indent_spacing = " " * indent_num
424411

425-
if default is None:
426-
default = ""
427-
else:
428-
default = default["value"]
429-
430-
if default in ["true", "false"]:
431-
default = default.title()
412+
default = default["value"] if default else ""
413+
default = fix_keywords(default)
432414

433415
is_required = "optional"
434416
if required:
435417
is_required = "required"
436-
elif default and default not in ["null", "{}", "[]"]:
437-
is_required = "default {}".format(default.replace("\n", "\n" + indent_spacing))
418+
elif default and default not in ["None", "{}", "[]"]:
419+
is_required = "default {}".format(default.replace("\n", ""))
420+
421+
# formats description
422+
period = "." if description else ""
423+
description = description.strip().strip(".") + period
424+
desc_indent = indent_spacing + " "
425+
description = fill(
426+
description,
427+
initial_indent=desc_indent,
428+
subsequent_indent=desc_indent,
429+
break_long_words=False,
430+
break_on_hyphens=False,
431+
)
432+
description = "\n{} ".format(description) if description else ""
433+
colon = ":" if description else ""
434+
description = fix_keywords(description)
438435

439436
if "\n" in py_type_name:
437+
# corrects the type
438+
dict_or_list = "list of dicts" if py_type_name.startswith("list") else "dict"
439+
440+
# format and rewrite the intro to the nested dicts
441+
intro1, intro2, dict_descr = py_type_name.partition("with keys:")
442+
intro = "".join([f"`{prop_name}`", " is a ", intro1, intro2])
443+
intro = fill(
444+
intro,
445+
initial_indent=desc_indent,
446+
subsequent_indent=desc_indent,
447+
break_long_words=False,
448+
break_on_hyphens=False,
449+
)
450+
451+
# captures optional nested dict description and puts the "or" condition on a new line
452+
if "| dict with keys:" in dict_descr:
453+
dict_part1, dict_part2 = dict_descr.split("|", 1)
454+
dict_part1 = dict_part1 + " "
455+
dict_part2 = "".join([desc_indent, "Or", dict_part2])
456+
dict_descr = "{} \n\n {}".format(dict_part1, dict_part2)
457+
458+
# ensures indent is correct
459+
current_indent = dict_descr.lstrip("\n").find("-")
460+
if current_indent == len(indent_spacing):
461+
dict_descr = indent(dict_descr, " ")
462+
440463
return (
441-
"{indent_spacing}- {name} (dict; {is_required}): "
442-
"{description}{period}"
443-
"{name} has the following type: {type}".format(
464+
"\n{indent_spacing}- {name} ({dict_or_list}; {is_required}){colon}"
465+
"{description} "
466+
"\n\n{intro} {dict_descr}".format(
444467
indent_spacing=indent_spacing,
445468
name=prop_name,
446-
type=py_type_name,
447-
description=description.strip().strip("."),
448-
period=". " if description else "",
469+
colon=colon,
470+
description=description,
471+
intro=intro,
472+
dict_descr=dict_descr,
473+
dict_or_list=dict_or_list,
449474
is_required=is_required,
450475
)
451476
)
452-
return "{indent_spacing}- {name} ({type}{is_required}){description}".format(
453-
indent_spacing=indent_spacing,
454-
name=prop_name,
455-
type="{}; ".format(py_type_name) if py_type_name else "",
456-
description=(": {}".format(description) if description != "" else ""),
457-
is_required=is_required,
477+
return (
478+
"\n{indent_spacing}- {name} ({type}{is_required}){colon}"
479+
"{description}".format(
480+
indent_spacing=indent_spacing,
481+
name=prop_name,
482+
type="{}; ".format(py_type_name) if py_type_name else "",
483+
colon=colon,
484+
description=description,
485+
is_required=is_required,
486+
)
458487
)
459488

460489

461-
def map_js_to_py_types_prop_types(type_object):
490+
def map_js_to_py_types_prop_types(type_object, indent_num):
462491
"""Mapping from the PropTypes js type object to the Python type."""
463492

464493
def shape_or_exact():
465-
return "dict containing keys {}.\n{}".format(
466-
", ".join("'{}'".format(t) for t in list(type_object["value"].keys())),
467-
"Those keys have the following types:\n{}".format(
468-
"\n".join(
469-
create_prop_docstring(
470-
prop_name=prop_name,
471-
type_object=prop,
472-
required=prop["required"],
473-
description=prop.get("description", ""),
474-
default=prop.get("defaultValue"),
475-
indent_num=1,
476-
)
477-
for prop_name, prop in list(type_object["value"].items())
494+
return "dict with keys: \n{}".format(
495+
" \n".join(
496+
create_prop_docstring(
497+
prop_name=prop_name,
498+
type_object=prop,
499+
required=prop["required"],
500+
description=prop.get("description", ""),
501+
default=prop.get("defaultValue"),
502+
indent_num=indent_num + 2,
478503
)
504+
for prop_name, prop in sorted(list(type_object["value"].items()))
479505
),
480506
)
481507

@@ -505,7 +531,7 @@ def shape_or_exact():
505531
"list"
506532
+ (
507533
" of {}".format(
508-
js_to_py_type(type_object["value"]) + "s"
534+
js_to_py_type(type_object["value"])
509535
if js_to_py_type(type_object["value"]).split(" ")[0] != "dict"
510536
else js_to_py_type(type_object["value"]).replace("dict", "dicts", 1)
511537
)
@@ -551,7 +577,7 @@ def map_js_to_py_types_flow_types(type_object):
551577
else ""
552578
),
553579
# React's PropTypes.shape
554-
signature=lambda indent_num: "dict containing keys {}.\n{}".format(
580+
signature=lambda indent_num: "dict with keys: {}.\n{}".format(
555581
", ".join(
556582
"'{}'".format(d["key"]) for d in type_object["signature"]["properties"]
557583
),
@@ -576,7 +602,6 @@ def map_js_to_py_types_flow_types(type_object):
576602

577603
def js_to_py_type(type_object, is_flow_type=False, indent_num=0):
578604
"""Convert JS types to Python types for the component definition.
579-
580605
Parameters
581606
----------
582607
type_object: dict
@@ -585,17 +610,19 @@ def js_to_py_type(type_object, is_flow_type=False, indent_num=0):
585610
Does the prop use Flow types? Otherwise, uses PropTypes
586611
indent_num: int
587612
Number of indents to use for the docstring for the prop
588-
589613
Returns
590614
-------
591615
str
592616
Python type string
593617
"""
618+
594619
js_type_name = type_object["name"]
595620
js_to_py_types = (
596621
map_js_to_py_types_flow_types(type_object=type_object)
597622
if is_flow_type
598-
else map_js_to_py_types_prop_types(type_object=type_object)
623+
else map_js_to_py_types_prop_types(
624+
type_object=type_object, indent_num=indent_num
625+
)
599626
)
600627

601628
if (

0 commit comments

Comments
 (0)