diff --git a/dash/development/_py_components_generation.py b/dash/development/_py_components_generation.py index 61a183e787..66db6a12d6 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', diff --git a/tests/development/metadata_test.json b/tests/development/metadata_test.json index faa4bc734e..a65cad547a 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 c1a580fa6b..785feae6bf 100644 --- a/tests/development/metadata_test.py +++ b/tests/development/metadata_test.py @@ -21,6 +21,14 @@ class Table(Component): - 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 @@ class Table(Component): - 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 a19768a3dc..690e721d14 100644 --- a/tests/development/test_base_component.py +++ b/tests/development/test_base_component.py @@ -718,6 +718,7 @@ def test_call_signature(self): 'optionalUnion', 'optionalArrayOf', 'optionalObjectOf', + 'optionalObjectWithExactAndNestedDescription', 'optionalObjectWithShapeAndNestedDescription', 'optionalAny', 'customProp', @@ -737,7 +738,7 @@ def test_call_signature(self): 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 @@ def setUp(self): ['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 "