Repository: plotly/dash

Issue:
Generate_class issue with plotly dahs 0.23.1 and dangerously_set_inner_html
After updating to 0.23.1, the following code importing "dangerously_set_inner_html" does not work anymore. It seems that some field may be missing / not set correctly and that this is field is used in an exec statement. The same import is working with previous release.

Error:
```
In [1]: import dash_dangerously_set_inner_html
Traceback (most recent call last):

  File "/miniconda/envs/.../lib/python3.4/site-packages/IPython/core/interactiveshell.py", line 2963, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)

  File "<ipython-input-1-8721236f3c51>", line 1, in <module>
    import dash_dangerously_set_inner_html

  File "/miniconda/envs/////lib/python3.4/site-packages/dash_dangerously_set_inner_html/__init__.py", line 10, in <module>
    'dash_dangerously_set_inner_html'

  File "/miniconda/envs/.../lib/python3.4/site-packages/dash/development/component_loader.py", line 51, in load_components
    namespace

  File "/miniconda/envs/...lib/python3.4/site-packages/dash/development/base_component.py", line 441, in generate_class
    exec(string, scope)

  File "<string>", line 27
    def __init__(self, children=None, , **kwargs):
```



Discussion:
I think `dash_dangerously_set_inner_html` has to be updated. You can downgrade to dash `0.22.0` in the meantime.
Hm, did the latest IDE stuff break the existing components? If so, we should make a new release that is backwards compatible, i.e. doesn't break the existing components that are out there.
Yes, I think there is some errors in how the classes generates the constructor arguments you can see the invalid part 
`def __init__(self, children=None, , **kwargs):` have an extra comma. I also have problems with `dash_flow_example` #319.
@rmarren1 - Could you check this out too?
I found the issue with `dash_flow_example`

https://github.com/plotly/dash/blob/1f27bac61fc30f0480f585bebbc43c861cb5adb3/dash/development/base_component.py#L376-L381

Should change the `endswith('-*')` with `endswith('*')`, cause the dash_flow_example has `data_*`.
For the dangerously set inner html, we have to remove the `,` on this line:
https://github.com/plotly/dash/blob/1f27bac61fc30f0480f585bebbc43c861cb5adb3/dash/development/base_component.py#L369

There is already a ',' before the kwargs at

https://github.com/plotly/dash/blob/1f27bac61fc30f0480f585bebbc43c861cb5adb3/dash/development/base_component.py#L306
I got this bit working:

```python
    if 'children' in props:
        prop_keys.remove('children')
        default_argtext = "children=None "
        # pylint: disable=unused-variable
        argtext = 'children=children, **args'
    else:
        default_argtext = ""
        argtext = '**args'
    default_argtext = ", ".join([default_argtext] if default_argtext else [] +
        [('{:s}=Component.REQUIRED'.format(p)
          if props[p]['required'] else
          '{:s}=Component.UNDEFINED'.format(p))
         for p in prop_keys
         if p and not p.endswith("*") and
         p not in ['dashEvents', 'fireEvent', 'setProps']]
    )
```
But it's not pretty.
I'll get a patch for this issue soon, it's looks to be breaking on the edge case where a component has no props.
I left some comments in #319, it looks to be a pre-existing issue that wasn't raised until 0.23.1

Resolving patch:
diff --git a/dash/development/base_component.py b/dash/development/base_component.py
--- a/dash/development/base_component.py
+++ b/dash/development/base_component.py
@@ -2,6 +2,7 @@
 import copy
 import os
 import inspect
+import keyword
 
 
 def is_number(s):
@@ -303,7 +304,7 @@ def generate_class_string(typename, props, description, namespace):
     c = '''class {typename}(Component):
     """{docstring}"""
     @_explicitize_args
-    def __init__(self, {default_argtext}, **kwargs):
+    def __init__(self, {default_argtext}):
         self._prop_names = {list_of_valid_keys}
         self._type = '{typename}'
         self._namespace = '{namespace}'
@@ -378,7 +379,8 @@ def __repr__(self):
           '{:s}=Component.UNDEFINED'.format(p))
          for p in prop_keys
          if not p.endswith("-*") and
-         p not in ['dashEvents', 'fireEvent', 'setProps']]
+         p not in keyword.kwlist and
+         p not in ['dashEvents', 'fireEvent', 'setProps']] + ['**kwargs']
     )
 
     required_args = required_props(props)
diff --git a/dash/version.py b/dash/version.py
--- a/dash/version.py
+++ b/dash/version.py
@@ -1 +1 @@
-__version__ = '0.24.0'
+__version__ = '0.24.1'


Test patch:
diff --git a/tests/development/metadata_test.json b/tests/development/metadata_test.json
--- a/tests/development/metadata_test.json
+++ b/tests/development/metadata_test.json
@@ -221,6 +221,13 @@
       "required": false,
       "description": ""
     },
+    "in": {
+      "type": {
+        "name": "string"
+      },
+      "required": false,
+      "description": ""
+    },
     "id": {
       "type": {
         "name": "string"
diff --git a/tests/development/metadata_test.py b/tests/development/metadata_test.py
--- a/tests/development/metadata_test.py
+++ b/tests/development/metadata_test.py
@@ -34,17 +34,18 @@ class Table(Component):
 - customArrayProp (list; optional)
 - data-* (string; optional)
 - aria-* (string; optional)
+- in (string; optional)
 - id (string; optional)
 
 Available events: 'restyle', 'relayout', 'click'"""
     @_explicitize_args
     def __init__(self, children=None, optionalArray=Component.UNDEFINED, optionalBool=Component.UNDEFINED, optionalFunc=Component.UNDEFINED, optionalNumber=Component.UNDEFINED, optionalObject=Component.UNDEFINED, optionalString=Component.UNDEFINED, optionalSymbol=Component.UNDEFINED, optionalNode=Component.UNDEFINED, optionalElement=Component.UNDEFINED, optionalMessage=Component.UNDEFINED, optionalEnum=Component.UNDEFINED, optionalUnion=Component.UNDEFINED, optionalArrayOf=Component.UNDEFINED, optionalObjectOf=Component.UNDEFINED, optionalObjectWithShapeAndNestedDescription=Component.UNDEFINED, optionalAny=Component.UNDEFINED, customProp=Component.UNDEFINED, customArrayProp=Component.UNDEFINED, id=Component.UNDEFINED, **kwargs):
-        self._prop_names = ['children', 'optionalArray', 'optionalBool', 'optionalNumber', 'optionalObject', 'optionalString', 'optionalNode', 'optionalElement', 'optionalEnum', 'optionalUnion', 'optionalArrayOf', 'optionalObjectOf', 'optionalObjectWithShapeAndNestedDescription', 'optionalAny', 'customProp', 'customArrayProp', 'data-*', 'aria-*', 'id']
+        self._prop_names = ['children', 'optionalArray', 'optionalBool', 'optionalNumber', 'optionalObject', 'optionalString', 'optionalNode', 'optionalElement', 'optionalEnum', 'optionalUnion', 'optionalArrayOf', 'optionalObjectOf', 'optionalObjectWithShapeAndNestedDescription', 'optionalAny', 'customProp', 'customArrayProp', 'data-*', 'aria-*', 'in', 'id']
         self._type = 'Table'
         self._namespace = 'TableComponents'
         self._valid_wildcard_attributes =            ['data-', 'aria-']
         self.available_events = ['restyle', 'relayout', 'click']
-        self.available_properties = ['children', 'optionalArray', 'optionalBool', 'optionalNumber', 'optionalObject', 'optionalString', 'optionalNode', 'optionalElement', 'optionalEnum', 'optionalUnion', 'optionalArrayOf', 'optionalObjectOf', 'optionalObjectWithShapeAndNestedDescription', 'optionalAny', 'customProp', 'customArrayProp', 'data-*', 'aria-*', 'id']
+        self.available_properties = ['children', 'optionalArray', 'optionalBool', 'optionalNumber', 'optionalObject', 'optionalString', 'optionalNode', 'optionalElement', 'optionalEnum', 'optionalUnion', 'optionalArrayOf', 'optionalObjectOf', 'optionalObjectWithShapeAndNestedDescription', 'optionalAny', 'customProp', 'customArrayProp', 'data-*', 'aria-*', 'in', 'id']
         self.available_wildcard_properties =            ['data-', 'aria-']
 
         _explicit_args = kwargs.pop('_explicit_args')
diff --git a/tests/development/test_base_component.py b/tests/development/test_base_component.py
--- a/tests/development/test_base_component.py
+++ b/tests/development/test_base_component.py
@@ -808,6 +808,8 @@ def setUp(self):
 
             ['aria-*', 'string'],
 
+            ['in', 'string'],
+
             ['id', 'string'],
 
             ['dashEvents', "a value equal to: 'restyle', 'relayout', 'click'"]
@@ -885,6 +887,7 @@ def assert_docstring(assertEqual, docstring):
             "- customArrayProp (list; optional)",
             '- data-* (string; optional)',
             '- aria-* (string; optional)',
+            '- in (string; optional)',
             '- id (string; optional)',
             '',
             "Available events: 'restyle', 'relayout', 'click'",
diff --git a/tests/test_integration.py b/tests/test_integration.py
--- a/tests/test_integration.py
+++ b/tests/test_integration.py
@@ -6,6 +6,7 @@
 import dash_html_components as html
 import dash_core_components as dcc
 import dash_flow_example
+import dash_dangerously_set_inner_html
 
 import dash
 import time
@@ -235,6 +236,17 @@ def test_wildcard_data_attributes(self):
 
         assert_clean_console(self)
 
+    def test_no_props_component(self):
+        app = dash.Dash()
+        app.layout = html.Div([
+            dash_dangerously_set_inner_html.DangerouslySetInnerHTML('''
+                <h1>No Props Component</h1>
+            ''')
+        ])
+        self.startServer(app)
+        assert_clean_console(self)
+        self.percy_snapshot(name='no-props-component')
+
     def test_flow_component(self):
         app = dash.Dash()