Repository: plotly/dash
Pull request #692 (2019-04-17 02:29:31)

support `exact` just like `shape`
in python component generation


Patch:
diff --git a/dash/development/_py_components_generation.py b/dash/development/_py_components_generation.py
index 61a183e78..66db6a12d 100644
--- a/dash/development/_py_components_generation.py
+++ b/dash/development/_py_components_generation.py
@@ -445,6 +445,24 @@ def create_prop_docstring(prop_name, type_object, required, description,
 
 def map_js_to_py_types_prop_types(type_object):
     """Mapping from the PropTypes js type object to the Python type"""
+
+    def shape_or_exact():
+        return 'dict containing keys {}.\n{}'.format(
+            ', '.join(
+                "'{}'".format(t) for t in list(type_object['value'].keys())
+            ),
+            'Those keys have the following types:\n{}'.format(
+                '\n'.join(
+                    create_prop_docstring(
+                        prop_name=prop_name,
+                        type_object=prop,
+                        required=prop['required'],
+                        description=prop.get('description', ''),
+                        indent_num=1
+                    ) for prop_name, prop in
+                    list(type_object['value'].items())))
+            )
+
     return dict(
         array=lambda: 'list',
         bool=lambda: 'boolean',
@@ -483,25 +501,15 @@ def map_js_to_py_types_prop_types(type_object):
                 js_to_py_type(type_object['value'])),
 
         # React's PropTypes.shape
-        shape=lambda: 'dict containing keys {}.\n{}'.format(
-            ', '.join(
-                "'{}'".format(t)
-                for t in list(type_object['value'].keys())),
-            'Those keys have the following types:\n{}'.format(
-                '\n'.join(
-                    create_prop_docstring(
-                        prop_name=prop_name,
-                        type_object=prop,
-                        required=prop['required'],
-                        description=prop.get('description', ''),
-                        indent_num=1
-                    ) for prop_name, prop in
-                    list(type_object['value'].items())))),
+        shape=shape_or_exact,
+        # React's PropTypes.exact
+        exact=shape_or_exact,
     )
 
 
 def map_js_to_py_types_flow_types(type_object):
     """Mapping from the Flow js types to the Python type"""
+
     return dict(
         array=lambda: 'list',
         boolean=lambda: 'boolean',


Test patch:
diff --git a/tests/development/metadata_test.json b/tests/development/metadata_test.json
index faa4bc734..a65cad547 100644
--- a/tests/development/metadata_test.json
+++ b/tests/development/metadata_test.json
@@ -137,6 +137,43 @@
       "required": false,
       "description": ""
     },
+    "optionalObjectWithExactAndNestedDescription": {
+      "type": {
+        "name": "exact",
+        "value": {
+          "color": {
+            "name": "string",
+            "required": false
+          },
+          "fontSize": {
+            "name": "number",
+            "required": false
+          },
+          "figure": {
+            "name": "shape",
+            "value": {
+              "data": {
+                "name": "arrayOf",
+                "value": {
+                  "name": "object"
+                },
+                "description": "data is a collection of traces",
+                "required": false
+              },
+              "layout": {
+                "name": "object",
+                "description": "layout describes the rest of the figure",
+                "required": false
+              }
+            },
+            "description": "Figure is a plotly graph object",
+            "required": false
+          }
+        }
+      },
+      "required": false,
+      "description": ""
+    },
     "optionalObjectWithShapeAndNestedDescription": {
       "type": {
         "name": "shape",
diff --git a/tests/development/metadata_test.py b/tests/development/metadata_test.py
index c1a580fa6..785feae6b 100644
--- a/tests/development/metadata_test.py
+++ b/tests/development/metadata_test.py
@@ -21,6 +21,14 @@ Keyword arguments:
 - optionalUnion (string | number; optional)
 - optionalArrayOf (list; optional)
 - optionalObjectOf (dict with strings as keys and values of type number; optional)
+- optionalObjectWithExactAndNestedDescription (optional): . optionalObjectWithExactAndNestedDescription has the following type: dict containing keys 'color', 'fontSize', 'figure'.
+Those keys have the following types:
+  - color (string; optional)
+  - fontSize (number; optional)
+  - figure (optional): Figure is a plotly graph object. figure has the following type: dict containing keys 'data', 'layout'.
+Those keys have the following types:
+  - data (list; optional): data is a collection of traces
+  - layout (dict; optional): layout describes the rest of the figure
 - optionalObjectWithShapeAndNestedDescription (optional): . optionalObjectWithShapeAndNestedDescription has the following type: dict containing keys 'color', 'fontSize', 'figure'.
 Those keys have the following types:
   - color (string; optional)
@@ -37,12 +45,12 @@ Those keys have the following types:
 - in (string; optional)
 - id (string; optional)"""
     @_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-*', 'in', 'id']
+    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, optionalObjectWithExactAndNestedDescription=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', 'optionalObjectWithExactAndNestedDescription', 'optionalObjectWithShapeAndNestedDescription', 'optionalAny', 'customProp', 'customArrayProp', 'data-*', 'aria-*', 'in', 'id']
         self._type = 'Table'
         self._namespace = 'TableComponents'
         self._valid_wildcard_attributes =            ['data-', 'aria-']
-        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_properties = ['children', 'optionalArray', 'optionalBool', 'optionalNumber', 'optionalObject', 'optionalString', 'optionalNode', 'optionalElement', 'optionalEnum', 'optionalUnion', 'optionalArrayOf', 'optionalObjectOf', 'optionalObjectWithExactAndNestedDescription', '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
index a19768a3d..690e721d1 100644
--- a/tests/development/test_base_component.py
+++ b/tests/development/test_base_component.py
@@ -718,6 +718,7 @@ class TestGenerateClass(unittest.TestCase):
              'optionalUnion',
              'optionalArrayOf',
              'optionalObjectOf',
+             'optionalObjectWithExactAndNestedDescription',
              'optionalObjectWithShapeAndNestedDescription',
              'optionalAny',
              'customProp',
@@ -737,7 +738,7 @@ class TestGenerateClass(unittest.TestCase):
         if hasattr(inspect, 'signature'):
             self.assertEqual(
                 [str(x) for x in inspect.getargspec(__init__func).defaults],
-                ['None'] + ['undefined'] * 19
+                ['None'] + ['undefined'] * 20
             )
 
     def test_required_props(self):
@@ -794,6 +795,19 @@ class TestMetaDataConversions(unittest.TestCase):
             ['optionalObjectOf',
              'dict with strings as keys and values of type number'],
 
+            ['optionalObjectWithExactAndNestedDescription', '\n'.join([
+
+                "dict containing keys 'color', 'fontSize', 'figure'.",
+                "Those keys have the following types:",
+                "  - color (string; optional)",
+                "  - fontSize (number; optional)",
+                "  - figure (optional): Figure is a plotly graph object. figure has the following type: dict containing keys 'data', 'layout'.",  # noqa: E501
+                "Those keys have the following types:",
+                "  - data (list; optional): data is a collection of traces",
+                "  - layout (dict; optional): layout describes the rest of the figure"  # noqa: E501
+
+            ])],
+
             ['optionalObjectWithShapeAndNestedDescription', '\n'.join([
 
                 "dict containing keys 'color', 'fontSize', 'figure'.",
@@ -868,6 +882,25 @@ def assert_docstring(assertEqual, docstring):
             "- optionalObjectOf (dict with strings as keys and values "
             "of type number; optional)",
 
+            "- optionalObjectWithExactAndNestedDescription (optional): . "
+            "optionalObjectWithExactAndNestedDescription has the "
+            "following type: dict containing keys "
+            "'color', 'fontSize', 'figure'.",
+
+            "Those keys have the following types:",
+            "  - color (string; optional)",
+            "  - fontSize (number; optional)",
+
+            "  - figure (optional): Figure is a plotly graph object. "
+            "figure has the following type: dict containing "
+            "keys 'data', 'layout'.",
+
+            "Those keys have the following types:",
+            "  - data (list; optional): data is a collection of traces",
+
+            "  - layout (dict; optional): layout describes "
+            "the rest of the figure",
+
             "- optionalObjectWithShapeAndNestedDescription (optional): . "
             "optionalObjectWithShapeAndNestedDescription has the "
             "following type: dict containing keys "