11import collections
22import copy
3+ import os
4+ import inspect
35
46
57def is_number (s ):
@@ -18,7 +20,56 @@ def _check_if_has_indexable_children(item):
1820 raise KeyError
1921
2022
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+
2154class 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+
2273 def __init__ (self , ** kwargs ):
2374 # pylint: disable=super-init-not-called
2475 for k , v in list (kwargs .items ()):
@@ -214,9 +265,9 @@ def __len__(self):
214265
215266
216267# pylint: disable=unused-argument
217- def generate_class (typename , props , description , namespace ):
268+ def generate_class_string (typename , props , description , namespace ):
218269 """
219- Dynamically generate classes to have nicely formatted docstrings,
270+ Dynamically generate class strings to have nicely formatted docstrings,
220271 keyword arguments, and repr
221272
222273 Inspired by http://jameso.be/2013/08/06/namedtuple.html
@@ -230,6 +281,7 @@ def generate_class(typename, props, description, namespace):
230281
231282 Returns
232283 -------
284+ string
233285
234286 """
235287 # TODO _prop_names, _type, _namespace, available_events,
@@ -250,7 +302,8 @@ def generate_class(typename, props, description, namespace):
250302 # not all component authors will supply those.
251303 c = '''class {typename}(Component):
252304 """{docstring}"""
253- def __init__(self, {default_argtext}):
305+ @_explicitize_args
306+ def __init__(self, {default_argtext}, **kwargs):
254307 self._prop_names = {list_of_valid_keys}
255308 self._type = '{typename}'
256309 self._namespace = '{namespace}'
@@ -261,11 +314,15 @@ def __init__(self, {default_argtext}):
261314 self.available_wildcard_properties =\
262315 {list_of_valid_wildcard_attr_prefixes}
263316
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+
264322 for k in {required_args}:
265- if k not in kwargs :
323+ if k not in args :
266324 raise TypeError(
267325 'Required argument `' + k + '` was not specified.')
268-
269326 super({typename}, self).__init__({argtext})
270327
271328 def __repr__(self):
@@ -290,13 +347,13 @@ def __repr__(self):
290347 return (
291348 '{typename}(' +
292349 repr(getattr(self, self._prop_names[0], None)) + ')')
293- '''
350+ '''
294351
295352 filtered_props = reorder_props (filter_props (props ))
296353 # pylint: disable=unused-variable
297354 list_of_valid_wildcard_attr_prefixes = repr (parse_wildcards (props ))
298355 # 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 () )))
300357 # pylint: disable=unused-variable
301358 docstring = create_docstring (
302359 component_name = typename ,
@@ -306,19 +363,82 @@ def __repr__(self):
306363
307364 # pylint: disable=unused-variable
308365 events = '[' + ', ' .join (parse_events (props )) + ']'
366+ prop_keys = list (props .keys ())
309367 if 'children' in props :
310- default_argtext = 'children=None, **kwargs'
368+ prop_keys .remove ('children' )
369+ default_argtext = "children=None, "
311370 # pylint: disable=unused-variable
312- argtext = 'children=children, **kwargs '
371+ argtext = 'children=children, **args '
313372 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+ )
316383
317384 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
318392
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 }
320440 # pylint: disable=exec-used
321- exec (c . format ( ** locals ()) , scope )
441+ exec (string , scope )
322442 result = scope [typename ]
323443 return result
324444
0 commit comments