1
1
import collections
2
2
import copy
3
+ import os
4
+ import inspect
3
5
4
6
5
7
def is_number (s ):
@@ -18,7 +20,56 @@ def _check_if_has_indexable_children(item):
18
20
raise KeyError
19
21
20
22
23
+ def _explicitize_args (func ):
24
+ # Python 2
25
+ if hasattr (func , 'func_code' ):
26
+ varnames = func .func_code .co_varnames
27
+ # Python 3
28
+ else :
29
+ varnames = func .__code__ .co_varnames
30
+
31
+ def wrapper (* args , ** kwargs ):
32
+ if '_explicit_args' in kwargs .keys ():
33
+ raise Exception ('Variable _explicit_args should not be set.' )
34
+ kwargs ['_explicit_args' ] = \
35
+ list (
36
+ set (
37
+ list (varnames [:len (args )]) + [k for k , _ in kwargs .items ()]
38
+ )
39
+ )
40
+ if 'self' in kwargs ['_explicit_args' ]:
41
+ kwargs ['_explicit_args' ].remove ('self' )
42
+ return func (* args , ** kwargs )
43
+
44
+ # If Python 3, we can set the function signature to be correct
45
+ if hasattr (inspect , 'signature' ):
46
+ # pylint: disable=no-member
47
+ new_sig = inspect .signature (wrapper ).replace (
48
+ parameters = inspect .signature (func ).parameters .values ()
49
+ )
50
+ wrapper .__signature__ = new_sig
51
+ return wrapper
52
+
53
+
21
54
class Component (collections .MutableMapping ):
55
+ class _UNDEFINED (object ):
56
+ def __repr__ (self ):
57
+ return 'undefined'
58
+
59
+ def __str__ (self ):
60
+ return 'undefined'
61
+
62
+ UNDEFINED = _UNDEFINED ()
63
+
64
+ class _REQUIRED (object ):
65
+ def __repr__ (self ):
66
+ return 'required'
67
+
68
+ def __str__ (self ):
69
+ return 'required'
70
+
71
+ REQUIRED = _REQUIRED ()
72
+
22
73
def __init__ (self , ** kwargs ):
23
74
# pylint: disable=super-init-not-called
24
75
for k , v in list (kwargs .items ()):
@@ -214,9 +265,9 @@ def __len__(self):
214
265
215
266
216
267
# pylint: disable=unused-argument
217
- def generate_class (typename , props , description , namespace ):
268
+ def generate_class_string (typename , props , description , namespace ):
218
269
"""
219
- Dynamically generate classes to have nicely formatted docstrings,
270
+ Dynamically generate class strings to have nicely formatted docstrings,
220
271
keyword arguments, and repr
221
272
222
273
Inspired by http://jameso.be/2013/08/06/namedtuple.html
@@ -230,6 +281,7 @@ def generate_class(typename, props, description, namespace):
230
281
231
282
Returns
232
283
-------
284
+ string
233
285
234
286
"""
235
287
# TODO _prop_names, _type, _namespace, available_events,
@@ -250,7 +302,8 @@ def generate_class(typename, props, description, namespace):
250
302
# not all component authors will supply those.
251
303
c = '''class {typename}(Component):
252
304
"""{docstring}"""
253
- def __init__(self, {default_argtext}):
305
+ @_explicitize_args
306
+ def __init__(self, {default_argtext}, **kwargs):
254
307
self._prop_names = {list_of_valid_keys}
255
308
self._type = '{typename}'
256
309
self._namespace = '{namespace}'
@@ -261,11 +314,15 @@ def __init__(self, {default_argtext}):
261
314
self.available_wildcard_properties =\
262
315
{list_of_valid_wildcard_attr_prefixes}
263
316
317
+ _explicit_args = kwargs.pop('_explicit_args')
318
+ _locals = locals()
319
+ _locals.update(kwargs) # For wildcard attrs
320
+ args = {{k: _locals[k] for k in _explicit_args if k != 'children'}}
321
+
264
322
for k in {required_args}:
265
- if k not in kwargs :
323
+ if k not in args :
266
324
raise TypeError(
267
325
'Required argument `' + k + '` was not specified.')
268
-
269
326
super({typename}, self).__init__({argtext})
270
327
271
328
def __repr__(self):
@@ -290,13 +347,13 @@ def __repr__(self):
290
347
return (
291
348
'{typename}(' +
292
349
repr(getattr(self, self._prop_names[0], None)) + ')')
293
- '''
350
+ '''
294
351
295
352
filtered_props = reorder_props (filter_props (props ))
296
353
# pylint: disable=unused-variable
297
354
list_of_valid_wildcard_attr_prefixes = repr (parse_wildcards (props ))
298
355
# pylint: disable=unused-variable
299
- list_of_valid_keys = repr (list (filtered_props .keys ()))
356
+ list_of_valid_keys = repr (list (map ( str , filtered_props .keys () )))
300
357
# pylint: disable=unused-variable
301
358
docstring = create_docstring (
302
359
component_name = typename ,
@@ -306,19 +363,82 @@ def __repr__(self):
306
363
307
364
# pylint: disable=unused-variable
308
365
events = '[' + ', ' .join (parse_events (props )) + ']'
366
+ prop_keys = list (props .keys ())
309
367
if 'children' in props :
310
- default_argtext = 'children=None, **kwargs'
368
+ prop_keys .remove ('children' )
369
+ default_argtext = "children=None, "
311
370
# pylint: disable=unused-variable
312
- argtext = 'children=children, **kwargs '
371
+ argtext = 'children=children, **args '
313
372
else :
314
- default_argtext = '**kwargs'
315
- argtext = '**kwargs'
373
+ default_argtext = ""
374
+ argtext = '**args'
375
+ default_argtext += ", " .join (
376
+ [('{:s}=Component.REQUIRED' .format (p )
377
+ if props [p ]['required' ] else
378
+ '{:s}=Component.UNDEFINED' .format (p ))
379
+ for p in prop_keys
380
+ if not p .endswith ("-*" ) and
381
+ p not in ['dashEvents' , 'fireEvent' , 'setProps' ]]
382
+ )
316
383
317
384
required_args = required_props (props )
385
+ return c .format (** locals ())
386
+
387
+
388
+ # pylint: disable=unused-argument
389
+ def generate_class_file (typename , props , description , namespace ):
390
+ """
391
+ Generate a python class file (.py) given a class string
318
392
319
- scope = {'Component' : Component }
393
+ Parameters
394
+ ----------
395
+ typename
396
+ props
397
+ description
398
+ namespace
399
+
400
+ Returns
401
+ -------
402
+
403
+ """
404
+ import_string = \
405
+ "# AUTO GENERATED FILE - DO NOT EDIT\n \n " + \
406
+ "from dash.development.base_component import " + \
407
+ "Component, _explicitize_args\n \n \n "
408
+ class_string = generate_class_string (
409
+ typename ,
410
+ props ,
411
+ description ,
412
+ namespace
413
+ )
414
+ file_name = "{:s}.py" .format (typename )
415
+
416
+ file_path = os .path .join (namespace , file_name )
417
+ with open (file_path , 'w' ) as f :
418
+ f .write (import_string )
419
+ f .write (class_string )
420
+
421
+
422
+ # pylint: disable=unused-argument
423
+ def generate_class (typename , props , description , namespace ):
424
+ """
425
+ Generate a python class object given a class string
426
+
427
+ Parameters
428
+ ----------
429
+ typename
430
+ props
431
+ description
432
+ namespace
433
+
434
+ Returns
435
+ -------
436
+
437
+ """
438
+ string = generate_class_string (typename , props , description , namespace )
439
+ scope = {'Component' : Component , '_explicitize_args' : _explicitize_args }
320
440
# pylint: disable=exec-used
321
- exec (c . format ( ** locals ()) , scope )
441
+ exec (string , scope )
322
442
result = scope [typename ]
323
443
return result
324
444
0 commit comments