1
1
from collections import OrderedDict
2
2
import copy
3
3
import os
4
+ from textwrap import fill , indent
4
5
5
6
from dash .development .base_component import _explicitize_args
6
7
from dash .exceptions import NonExistentEventException
12
13
def generate_class_string (typename , props , description , namespace ):
13
14
"""Dynamically generate class strings to have nicely formatted docstrings,
14
15
keyword arguments, and repr.
15
-
16
16
Inspired by http://jameso.be/2013/08/06/namedtuple.html
17
-
18
17
Parameters
19
18
----------
20
19
typename
21
20
props
22
21
description
23
22
namespace
24
-
25
23
Returns
26
24
-------
27
25
string
@@ -53,12 +51,10 @@ def __init__(self, {default_argtext}):
53
51
self.available_properties = {list_of_valid_keys}
54
52
self.available_wildcard_properties =\
55
53
{list_of_valid_wildcard_attr_prefixes}
56
-
57
54
_explicit_args = kwargs.pop('_explicit_args')
58
55
_locals = locals()
59
56
_locals.update(kwargs) # For wildcard attrs
60
57
args = {{k: _locals[k] for k in _explicit_args if k != 'children'}}
61
-
62
58
for k in {required_props}:
63
59
if k not in args:
64
60
raise TypeError(
@@ -112,14 +108,12 @@ def __init__(self, {default_argtext}):
112
108
113
109
def generate_class_file (typename , props , description , namespace ):
114
110
"""Generate a Python class file (.py) given a class string.
115
-
116
111
Parameters
117
112
----------
118
113
typename
119
114
props
120
115
description
121
116
namespace
122
-
123
117
Returns
124
118
-------
125
119
"""
@@ -170,14 +164,12 @@ def generate_classes_files(project_shortname, metadata, *component_generators):
170
164
171
165
def generate_class (typename , props , description , namespace ):
172
166
"""Generate a Python class object given a class string.
173
-
174
167
Parameters
175
168
----------
176
169
typename
177
170
props
178
171
description
179
172
namespace
180
-
181
173
Returns
182
174
-------
183
175
"""
@@ -191,11 +183,9 @@ def generate_class(typename, props, description, namespace):
191
183
192
184
def required_props (props ):
193
185
"""Pull names of required props from the props object.
194
-
195
186
Parameters
196
187
----------
197
188
props: dict
198
-
199
189
Returns
200
190
-------
201
191
list
@@ -206,7 +196,6 @@ def required_props(props):
206
196
207
197
def create_docstring (component_name , props , description ):
208
198
"""Create the Dash component docstring.
209
-
210
199
Parameters
211
200
----------
212
201
component_name: str
@@ -215,7 +204,6 @@ def create_docstring(component_name, props, description):
215
204
Dictionary with {propName: propMetadata} structure
216
205
description: str
217
206
Component description
218
-
219
207
Returns
220
208
-------
221
209
str
@@ -248,12 +236,10 @@ def create_docstring(component_name, props, description):
248
236
def prohibit_events (props ):
249
237
"""Events have been removed. Raise an error if we see dashEvents or
250
238
fireEvents.
251
-
252
239
Parameters
253
240
----------
254
241
props: dict
255
242
Dictionary with {propName: propMetadata} structure
256
-
257
243
Raises
258
244
-------
259
245
?
@@ -267,12 +253,10 @@ def prohibit_events(props):
267
253
268
254
def parse_wildcards (props ):
269
255
"""Pull out the wildcard attributes from the Component props.
270
-
271
256
Parameters
272
257
----------
273
258
props: dict
274
259
Dictionary with {propName: propMetadata} structure
275
-
276
260
Returns
277
261
-------
278
262
list
@@ -287,43 +271,37 @@ def parse_wildcards(props):
287
271
288
272
def reorder_props (props ):
289
273
"""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
292
275
Parameters
293
276
----------
294
277
props: dict
295
278
Dictionary with {propName: propMetadata} structure
296
-
297
279
Returns
298
280
-------
299
281
dict
300
282
Dictionary with {propName: propMetadata} structure
301
283
"""
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 ()))
307
284
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 ())))
309
291
310
292
311
293
def filter_props (props ):
312
294
"""Filter props from the Component arguments to exclude:
313
-
314
295
- Those without a "type" or a "flowType" field
315
296
- Those with arg.type.name in {'func', 'symbol', 'instanceOf'}
316
-
317
297
Parameters
318
298
----------
319
299
props: dict
320
300
Dictionary with {propName: propMetadata} structure
321
-
322
301
Returns
323
302
-------
324
303
dict
325
304
Filtered dictionary with {propName: propMetadata} structure
326
-
327
305
Examples
328
306
--------
329
307
```python
@@ -380,7 +358,18 @@ def filter_props(props):
380
358
return filtered_props
381
359
382
360
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
+
383
371
# pylint: disable=too-many-arguments
372
+ # pylint: disable=too-many-locals
384
373
def create_prop_docstring (
385
374
prop_name ,
386
375
type_object ,
@@ -391,7 +380,6 @@ def create_prop_docstring(
391
380
is_flow_type = False ,
392
381
):
393
382
"""Create the Dash component prop docstring.
394
-
395
383
Parameters
396
384
----------
397
385
prop_name: str
@@ -411,71 +399,109 @@ def create_prop_docstring(
411
399
(creates 2 spaces for every indent)
412
400
is_flow_type: bool
413
401
Does the prop use Flow types? Otherwise, uses PropTypes
414
-
415
402
Returns
416
403
-------
417
404
str
418
405
Dash component prop docstring
419
406
"""
420
407
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
422
409
)
423
410
indent_spacing = " " * indent_num
424
411
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 )
432
414
433
415
is_required = "optional"
434
416
if required :
435
417
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 )
438
435
439
436
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
+
440
463
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 (
444
467
indent_spacing = indent_spacing ,
445
468
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 ,
449
474
is_required = is_required ,
450
475
)
451
476
)
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
+ )
458
487
)
459
488
460
489
461
- def map_js_to_py_types_prop_types (type_object ):
490
+ def map_js_to_py_types_prop_types (type_object , indent_num ):
462
491
"""Mapping from the PropTypes js type object to the Python type."""
463
492
464
493
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 ,
478
503
)
504
+ for prop_name , prop in sorted (list (type_object ["value" ].items ()))
479
505
),
480
506
)
481
507
@@ -505,7 +531,7 @@ def shape_or_exact():
505
531
"list"
506
532
+ (
507
533
" of {}" .format (
508
- js_to_py_type (type_object ["value" ]) + "s"
534
+ js_to_py_type (type_object ["value" ])
509
535
if js_to_py_type (type_object ["value" ]).split (" " )[0 ] != "dict"
510
536
else js_to_py_type (type_object ["value" ]).replace ("dict" , "dicts" , 1 )
511
537
)
@@ -551,7 +577,7 @@ def map_js_to_py_types_flow_types(type_object):
551
577
else ""
552
578
),
553
579
# React's PropTypes.shape
554
- signature = lambda indent_num : "dict containing keys {}.\n {}" .format (
580
+ signature = lambda indent_num : "dict with keys: {}.\n {}" .format (
555
581
", " .join (
556
582
"'{}'" .format (d ["key" ]) for d in type_object ["signature" ]["properties" ]
557
583
),
@@ -576,7 +602,6 @@ def map_js_to_py_types_flow_types(type_object):
576
602
577
603
def js_to_py_type (type_object , is_flow_type = False , indent_num = 0 ):
578
604
"""Convert JS types to Python types for the component definition.
579
-
580
605
Parameters
581
606
----------
582
607
type_object: dict
@@ -585,17 +610,19 @@ def js_to_py_type(type_object, is_flow_type=False, indent_num=0):
585
610
Does the prop use Flow types? Otherwise, uses PropTypes
586
611
indent_num: int
587
612
Number of indents to use for the docstring for the prop
588
-
589
613
Returns
590
614
-------
591
615
str
592
616
Python type string
593
617
"""
618
+
594
619
js_type_name = type_object ["name" ]
595
620
js_to_py_types = (
596
621
map_js_to_py_types_flow_types (type_object = type_object )
597
622
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
+ )
599
626
)
600
627
601
628
if (
0 commit comments