diff --git a/CHANGELOG.md b/CHANGELOG.md index 6cba6cbe3a..7591df1008 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ All notable changes to `dash` will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org/). +## UNRELEASED + +### Changed + +- [#1531](https://github.com/plotly/dash/pull/1531). Updates the format of the docstrings to make them easier to read in + the reference pages of Dash Docs and in the console. This also addresses [#1205](https://github.com/plotly/dash/issues/1205) + + ## [1.19.0] - 2021-01-19 ## Dash and Dash Renderer diff --git a/dash/development/_py_components_generation.py b/dash/development/_py_components_generation.py index 24928d1fcb..a9ade10760 100644 --- a/dash/development/_py_components_generation.py +++ b/dash/development/_py_components_generation.py @@ -1,6 +1,7 @@ from collections import OrderedDict import copy import os +from textwrap import fill from dash.development.base_component import _explicitize_args from dash.exceptions import NonExistentEventException @@ -12,16 +13,13 @@ def generate_class_string(typename, props, description, namespace): """Dynamically generate class strings to have nicely formatted docstrings, keyword arguments, and repr. - Inspired by http://jameso.be/2013/08/06/namedtuple.html - Parameters ---------- typename props description namespace - Returns ------- string @@ -53,12 +51,10 @@ def __init__(self, {default_argtext}): self.available_properties = {list_of_valid_keys} self.available_wildcard_properties =\ {list_of_valid_wildcard_attr_prefixes} - _explicit_args = kwargs.pop('_explicit_args') _locals = locals() _locals.update(kwargs) # For wildcard attrs args = {{k: _locals[k] for k in _explicit_args if k != 'children'}} - for k in {required_props}: if k not in args: raise TypeError( @@ -112,14 +108,12 @@ def __init__(self, {default_argtext}): def generate_class_file(typename, props, description, namespace): """Generate a Python class file (.py) given a class string. - Parameters ---------- typename props description namespace - Returns ------- """ @@ -170,14 +164,12 @@ def generate_classes_files(project_shortname, metadata, *component_generators): def generate_class(typename, props, description, namespace): """Generate a Python class object given a class string. - Parameters ---------- typename props description namespace - Returns ------- """ @@ -191,11 +183,9 @@ def generate_class(typename, props, description, namespace): def required_props(props): """Pull names of required props from the props object. - Parameters ---------- props: dict - Returns ------- list @@ -206,7 +196,6 @@ def required_props(props): def create_docstring(component_name, props, description): """Create the Dash component docstring. - Parameters ---------- component_name: str @@ -215,7 +204,6 @@ def create_docstring(component_name, props, description): Dictionary with {propName: propMetadata} structure description: str Component description - Returns ------- str @@ -248,12 +236,10 @@ def create_docstring(component_name, props, description): def prohibit_events(props): """Events have been removed. Raise an error if we see dashEvents or fireEvents. - Parameters ---------- props: dict Dictionary with {propName: propMetadata} structure - Raises ------- ? @@ -267,12 +253,10 @@ def prohibit_events(props): def parse_wildcards(props): """Pull out the wildcard attributes from the Component props. - Parameters ---------- props: dict Dictionary with {propName: propMetadata} structure - Returns ------- list @@ -287,43 +271,37 @@ def parse_wildcards(props): def reorder_props(props): """If "children" is in props, then move it to the front to respect dash - convention. - + convention, then 'id', then the remaining props sorted by prop name Parameters ---------- props: dict Dictionary with {propName: propMetadata} structure - Returns ------- dict Dictionary with {propName: propMetadata} structure """ - if "children" in props: - # Constructing an OrderedDict with duplicate keys, you get the order - # from the first one but the value from the last. - # Doing this to avoid mutating props, which can cause confusion. - props = OrderedDict([("children", "")] + list(props.items())) - return props + # Constructing an OrderedDict with duplicate keys, you get the order + # from the first one but the value from the last. + # Doing this to avoid mutating props, which can cause confusion. + props1 = [("children", "")] if "children" in props else [] + props2 = [("id", "")] if "id" in props else [] + return OrderedDict(props1 + props2 + sorted(list(props.items()))) def filter_props(props): """Filter props from the Component arguments to exclude: - - Those without a "type" or a "flowType" field - Those with arg.type.name in {'func', 'symbol', 'instanceOf'} - Parameters ---------- props: dict Dictionary with {propName: propMetadata} structure - Returns ------- dict Filtered dictionary with {propName: propMetadata} structure - Examples -------- ```python @@ -380,7 +358,18 @@ def filter_props(props): return filtered_props +def fix_keywords(txt): + """ + replaces javascript keywords true, false, null with Python keywords + """ + fix_word = {"true": "True", "false": "False", "null": "None"} + for js_keyword, python_keyword in fix_word.items(): + txt = txt.replace(js_keyword, python_keyword) + return txt + + # pylint: disable=too-many-arguments +# pylint: disable=too-many-locals def create_prop_docstring( prop_name, type_object, @@ -391,7 +380,6 @@ def create_prop_docstring( is_flow_type=False, ): """Create the Dash component prop docstring. - Parameters ---------- prop_name: str @@ -411,71 +399,110 @@ def create_prop_docstring( (creates 2 spaces for every indent) is_flow_type: bool Does the prop use Flow types? Otherwise, uses PropTypes - Returns ------- str Dash component prop docstring """ py_type_name = js_to_py_type( - type_object=type_object, is_flow_type=is_flow_type, indent_num=indent_num + 1 + type_object=type_object, is_flow_type=is_flow_type, indent_num=indent_num ) indent_spacing = " " * indent_num - if default is None: - default = "" - else: - default = default["value"] - - if default in ["true", "false"]: - default = default.title() + default = default["value"] if default else "" + default = fix_keywords(default) is_required = "optional" if required: is_required = "required" - elif default and default not in ["null", "{}", "[]"]: - is_required = "default {}".format(default.replace("\n", "\n" + indent_spacing)) + elif default and default not in ["None", "{}", "[]"]: + is_required = "default {}".format(default.replace("\n", "")) + + # formats description + period = "." if description else "" + description = description.strip().strip(".").replace('"', r"\"") + period + desc_indent = indent_spacing + " " + description = fill( + description, + initial_indent=desc_indent, + subsequent_indent=desc_indent, + break_long_words=False, + break_on_hyphens=False, + ) + description = "\n{}".format(description) if description else "" + colon = ":" if description else "" + description = fix_keywords(description) if "\n" in py_type_name: + # corrects the type + dict_or_list = "list of dicts" if py_type_name.startswith("list") else "dict" + + # format and rewrite the intro to the nested dicts + intro1, intro2, dict_descr = py_type_name.partition("with keys:") + intro = "".join(["`{}`".format(prop_name), " is a ", intro1, intro2]) + intro = fill( + intro, + initial_indent=desc_indent, + subsequent_indent=desc_indent, + break_long_words=False, + break_on_hyphens=False, + ) + + # captures optional nested dict description and puts the "or" condition on a new line + if "| dict with keys:" in dict_descr: + dict_part1, dict_part2 = dict_descr.split(" |", 1) + dict_part2 = "".join([desc_indent, "Or", dict_part2]) + dict_descr = "{}\n\n {}".format(dict_part1, dict_part2) + + # ensures indent is correct if there is a second nested list of dicts + current_indent = dict_descr.lstrip("\n").find("-") + if current_indent == len(indent_spacing): + dict_descr = "".join( + "\n\n " + line for line in dict_descr.splitlines() if line != "" + ) + return ( - "{indent_spacing}- {name} (dict; {is_required}): " - "{description}{period}" - "{name} has the following type: {type}".format( + "\n{indent_spacing}- {name} ({dict_or_list}; {is_required}){colon}" + "{description}" + "\n\n{intro}{dict_descr}".format( indent_spacing=indent_spacing, name=prop_name, - type=py_type_name, - description=description.strip().strip("."), - period=". " if description else "", + colon=colon, + description=description, + intro=intro, + dict_descr=dict_descr, + dict_or_list=dict_or_list, is_required=is_required, ) ) - return "{indent_spacing}- {name} ({type}{is_required}){description}".format( - indent_spacing=indent_spacing, - name=prop_name, - type="{}; ".format(py_type_name) if py_type_name else "", - description=(": {}".format(description) if description != "" else ""), - is_required=is_required, + return ( + "\n{indent_spacing}- {name} ({type}{is_required}){colon}" + "{description}".format( + indent_spacing=indent_spacing, + name=prop_name, + type="{}; ".format(py_type_name) if py_type_name else "", + colon=colon, + description=description, + is_required=is_required, + ) ) -def map_js_to_py_types_prop_types(type_object): +def map_js_to_py_types_prop_types(type_object, indent_num): """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", ""), - default=prop.get("defaultValue"), - indent_num=1, - ) - for prop_name, prop in list(type_object["value"].items()) + return "dict with keys:\n{}".format( + "\n".join( + create_prop_docstring( + prop_name=prop_name, + type_object=prop, + required=prop["required"], + description=prop.get("description", ""), + default=prop.get("defaultValue"), + indent_num=indent_num + 2, ) + for prop_name, prop in sorted(list(type_object["value"].items())) ), ) @@ -526,7 +553,6 @@ def 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", @@ -551,24 +577,18 @@ def map_js_to_py_types_flow_types(type_object): else "" ), # React's PropTypes.shape - signature=lambda indent_num: "dict containing keys {}.\n{}".format( - ", ".join( - "'{}'".format(d["key"]) for d in type_object["signature"]["properties"] - ), - "{}Those keys have the following types:\n{}".format( - " " * indent_num, - "\n".join( - create_prop_docstring( - prop_name=prop["key"], - type_object=prop["value"], - required=prop["value"]["required"], - description=prop["value"].get("description", ""), - default=prop.get("defaultValue"), - indent_num=indent_num, - is_flow_type=True, - ) - for prop in type_object["signature"]["properties"] - ), + signature=lambda indent_num: "dict with keys:\n{}".format( + "\n".join( + create_prop_docstring( + prop_name=prop["key"], + type_object=prop["value"], + required=prop["value"]["required"], + description=prop["value"].get("description", ""), + default=prop.get("defaultValue"), + indent_num=indent_num + 2, + is_flow_type=True, + ) + for prop in type_object["signature"]["properties"] ), ), ) @@ -576,7 +596,6 @@ def map_js_to_py_types_flow_types(type_object): def js_to_py_type(type_object, is_flow_type=False, indent_num=0): """Convert JS types to Python types for the component definition. - Parameters ---------- type_object: dict @@ -585,17 +604,19 @@ def js_to_py_type(type_object, is_flow_type=False, indent_num=0): Does the prop use Flow types? Otherwise, uses PropTypes indent_num: int Number of indents to use for the docstring for the prop - Returns ------- str Python type string """ + js_type_name = type_object["name"] js_to_py_types = ( map_js_to_py_types_flow_types(type_object=type_object) if is_flow_type - else map_js_to_py_types_prop_types(type_object=type_object) + else map_js_to_py_types_prop_types( + type_object=type_object, indent_num=indent_num + ) ) if ( diff --git a/tests/unit/development/__init__.py b/tests/unit/development/__init__.py index b8d2f0b34c..9ec687e80d 100644 --- a/tests/unit/development/__init__.py +++ b/tests/unit/development/__init__.py @@ -13,51 +13,81 @@ def has_trailing_space(s): "It's multiple lines long.", "", "Keyword arguments:", + "", "- children (a list of or a singular dash component, string or number; optional)", - "- optionalArray (list; optional): Description of optionalArray", + "", + "- id (string; optional)", + "", + "- aria-* (string; optional)", + "", + "- customArrayProp (list; optional)", + "", + "- customProp (optional)", + "", + "- data-* (string; optional)", + "", + "- in (string; optional)", + "", + "- optionalAny (boolean | number | string | dict | list; optional)", + "", + "- optionalArray (list; optional):", + " Description of optionalArray.", + "", + "- optionalArrayOf (list of numbers; optional)", + "", "- optionalBool (boolean; optional)", + "", + "- optionalElement (dash component; optional)", + "", + "- optionalEnum (a value equal to: 'News'," " 'Photos'; optional)", + "", + "- optionalNode (a list of or a singular dash component, string or number; optional)", + "", "- optionalNumber (number; default 42)", + "", "- optionalObject (dict; optional)", + "", + "- optionalObjectOf (dict with strings as keys and values of type number; optional)", + "", + "- optionalObjectWithExactAndNestedDescription (dict; optional)", + "", + " `optionalObjectWithExactAndNestedDescription` is a dict with keys:", + "", + " - color (string; optional)", + "", + " - figure (dict; optional):", + " Figure is a plotly graph object.", + "", + " `figure` is a dict with keys:", + "", + " - data (list of dicts; optional):", + " data is a collection of traces.", + "", + " - layout (dict; optional):", + " layout describes the rest of the figure.", + "", + " - fontSize (number; optional)", + "", + "- optionalObjectWithShapeAndNestedDescription (dict; optional)", + "", + " `optionalObjectWithShapeAndNestedDescription` is a dict with keys:", + "", + " - color (string; optional)", + "", + " - figure (dict; optional):", + " Figure is a plotly graph object.", + "", + " `figure` is a dict with keys:", + "", + " - data (list of dicts; optional):", + " data is a collection of traces.", + "", + " - layout (dict; optional):", + " layout describes the rest of the figure.", + "", + " - fontSize (number; optional)", + "", "- optionalString (string; default 'hello world')", - "- optionalNode (a list of or a singular dash component, " - "string or number; optional)", - "- optionalElement (dash component; optional)", - "- optionalEnum (a value equal to: 'News', 'Photos'; optional)", + "", "- optionalUnion (string | number; optional)", - "- optionalArrayOf (list of numbers; optional)", - "- optionalObjectOf (dict with strings as keys and values " - "of type number; optional)", - "- optionalObjectWithExactAndNestedDescription (dict; 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 (dict; 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 of dicts; optional): data is a collection of traces", - " - layout (dict; optional): layout describes " "the rest of the figure", - "- optionalObjectWithShapeAndNestedDescription (dict; optional): " - "optionalObjectWithShapeAndNestedDescription has the " - "following type: dict containing keys " - "'color', 'fontSize', 'figure'.", - "Those keys have the following types:", - " - color (string; optional)", - " - fontSize (number; optional)", - " - figure (dict; 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 of dicts; optional): data is a collection of traces", - " - layout (dict; optional): layout describes " "the rest of the figure", - "- optionalAny (boolean | number | string | dict | " "list; optional)", - "- customProp (optional)", - "- customArrayProp (list; optional)", - "- data-* (string; optional)", - "- aria-* (string; optional)", - "- in (string; optional)", - "- id (string; optional)", ] diff --git a/tests/unit/development/metadata_test.py b/tests/unit/development/metadata_test.py index 042b443851..c7440d2090 100644 --- a/tests/unit/development/metadata_test.py +++ b/tests/unit/development/metadata_test.py @@ -9,57 +9,97 @@ class Table(Component): It's multiple lines long. Keyword arguments: + - children (a list of or a singular dash component, string or number; optional) -- optionalArray (list; optional): Description of optionalArray + +- id (string; optional) + +- aria-* (string; optional) + +- customArrayProp (list; optional) + +- customProp (optional) + +- data-* (string; optional) + +- in (string; optional) + +- optionalAny (boolean | number | string | dict | list; optional) + +- optionalArray (list; optional): + Description of optionalArray. + +- optionalArrayOf (list of numbers; optional) + - optionalBool (boolean; optional) -- optionalNumber (number; default 42) -- optionalObject (dict; optional) -- optionalString (string; default 'hello world') -- optionalNode (a list of or a singular dash component, string or number; optional) + - optionalElement (dash component; optional) + - optionalEnum (a value equal to: 'News', 'Photos'; optional) -- optionalUnion (string | number; optional) -- optionalArrayOf (list of numbers; optional) + +- optionalNode (a list of or a singular dash component, string or number; optional) + +- optionalNumber (number; default 42) + +- optionalObject (dict; optional) + - optionalObjectOf (dict with strings as keys and values of type number; optional) -- optionalObjectWithExactAndNestedDescription (dict; 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 (dict; 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 of dicts; optional): data is a collection of traces - - layout (dict; optional): layout describes the rest of the figure -- optionalObjectWithShapeAndNestedDescription (dict; optional): optionalObjectWithShapeAndNestedDescription has the following type: dict containing keys 'color', 'fontSize', 'figure'. -Those keys have the following types: - - color (string; optional) - - fontSize (number; optional) - - figure (dict; 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 of dicts; optional): data is a collection of traces - - layout (dict; optional): layout describes the rest of the figure -- optionalAny (boolean | number | string | dict | list; optional) -- customProp (optional) -- customArrayProp (list; optional) -- data-* (string; optional) -- aria-* (string; optional) -- in (string; optional) -- id (string; optional)""" + +- optionalObjectWithExactAndNestedDescription (dict; optional) + + `optionalObjectWithExactAndNestedDescription` is a dict with keys: + + - color (string; optional) + + - figure (dict; optional): + Figure is a plotly graph object. + + `figure` is a dict with keys: + + - data (list of dicts; optional): + data is a collection of traces. + + - layout (dict; optional): + layout describes the rest of the figure. + + - fontSize (number; optional) + +- optionalObjectWithShapeAndNestedDescription (dict; optional) + + `optionalObjectWithShapeAndNestedDescription` is a dict with keys: + + - color (string; optional) + + - figure (dict; optional): + Figure is a plotly graph object. + + `figure` is a dict with keys: + + - data (list of dicts; optional): + data is a collection of traces. + + - layout (dict; optional): + layout describes the rest of the figure. + + - fontSize (number; optional) + +- optionalString (string; default 'hello world') + +- optionalUnion (string | number; 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, 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._prop_names = ['children', 'id', 'aria-*', 'customArrayProp', 'customProp', 'data-*', 'in', 'optionalAny', 'optionalArray', 'optionalArrayOf', 'optionalBool', 'optionalElement', 'optionalEnum', 'optionalNode', 'optionalNumber', 'optionalObject', 'optionalObjectOf', 'optionalObjectWithExactAndNestedDescription', 'optionalObjectWithShapeAndNestedDescription', 'optionalString', 'optionalUnion'] 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', 'optionalObjectWithExactAndNestedDescription', 'optionalObjectWithShapeAndNestedDescription', 'optionalAny', 'customProp', 'customArrayProp', 'data-*', 'aria-*', 'in', 'id'] + self.available_properties = ['children', 'id', 'aria-*', 'customArrayProp', 'customProp', 'data-*', 'in', 'optionalAny', 'optionalArray', 'optionalArrayOf', 'optionalBool', 'optionalElement', 'optionalEnum', 'optionalNode', 'optionalNumber', 'optionalObject', 'optionalObjectOf', 'optionalObjectWithExactAndNestedDescription', 'optionalObjectWithShapeAndNestedDescription', 'optionalString', 'optionalUnion'] self.available_wildcard_properties = ['data-', 'aria-'] - _explicit_args = kwargs.pop('_explicit_args') _locals = locals() _locals.update(kwargs) # For wildcard attrs args = {k: _locals[k] for k in _explicit_args if k != 'children'} - for k in []: if k not in args: raise TypeError( 'Required argument `' + k + '` was not specified.') - super(Table, self).__init__(children=children, **args) \ No newline at end of file + super(Table, self).__init__(children=children, **args) diff --git a/tests/unit/development/test_flow_metadata_conversions.py b/tests/unit/development/test_flow_metadata_conversions.py index 822df417e9..cf2b10d9e7 100644 --- a/tests/unit/development/test_flow_metadata_conversions.py +++ b/tests/unit/development/test_flow_metadata_conversions.py @@ -1,3 +1,4 @@ +# flake8: ignore=E501 import json import os from collections import OrderedDict @@ -27,17 +28,18 @@ "optionalSignature(shape)", "\n".join( [ - "dict containing keys 'checked', 'children', 'customData', 'disabled', 'label', 'primaryText', 'secondaryText', 'style', 'value'.", - "Those keys have the following types:", - "- checked (boolean; optional)", - "- children (a list of or a singular dash component, string or number; optional)", - "- customData (bool | number | str | dict | list; required): A test description", - "- disabled (boolean; optional)", - "- label (string; optional)", - "- primaryText (string; required): Another test description", - "- secondaryText (string; optional)", - "- style (dict; optional)", - "- value (bool | number | str | dict | list; required)", + "dict with keys:\n", + " - checked (boolean; optional)\n", + " - children (a list of or a singular dash component, string or number; optional)\n", + " - customData (bool | number | str | dict | list; required):\n" + " A test description.\n", + " - disabled (boolean; optional)\n", + " - label (string; optional)\n", + " - primaryText (string; required):\n" + " Another test description.\n", + " - secondaryText (string; optional)\n", + " - style (dict; optional)\n", + " - value (bool | number | str | dict | list; required)", ] ), ], @@ -45,20 +47,19 @@ "requiredNested", "\n".join( [ - "dict containing keys 'customData', 'value'.", - "Those keys have the following types:", - "- customData (dict; required): customData has the following type: dict containing keys 'checked', 'children', 'customData', 'disabled', 'label', 'primaryText', 'secondaryText', 'style', 'value'.", - " Those keys have the following types:", - " - checked (boolean; optional)", - " - children (a list of or a singular dash component, string or number; optional)", - " - customData (bool | number | str | dict | list; required)", - " - disabled (boolean; optional)", - " - label (string; optional)", - " - primaryText (string; required)", - " - secondaryText (string; optional)", - " - style (dict; optional)", - " - value (bool | number | str | dict | list; required)", - "- value (bool | number | str | dict | list; required)", + "dict with keys:\n", + " - customData (dict; required)\n\n" + " `customData` is a dict with keys:\n", + " - checked (boolean; optional)\n", + " - children (a list of or a singular dash component, string or number; optional)\n", + " - customData (bool | number | str | dict | list; required)\n", + " - disabled (boolean; optional)\n", + " - label (string; optional)\n", + " - primaryText (string; required)\n", + " - secondaryText (string; optional)\n", + " - style (dict; optional)\n", + " - value (bool | number | str | dict | list; required)\n", + " - value (bool | number | str | dict | list; required)", ] ), ], @@ -71,46 +72,78 @@ "It's multiple lines long.", "", "Keyword arguments:", - "- requiredString (string; required): A required string", - "- optionalString (string; default ''): A string that isn't required.", - "- optionalBoolean (boolean; default False): A boolean test", - "- optionalNode (a list of or a singular dash component, string or number; optional): " - "A node test", - "- optionalArray (list; optional): An array test with a particularly ", - "long description that covers several lines. It includes the newline character ", - "and should span 3 lines in total.", - "- requiredUnion (string | number; required)", - "- optionalSignature(shape) (dict; optional): This is a test of an object's shape. " - "optionalSignature(shape) has the following type: dict containing keys 'checked', " - "'children', 'customData', 'disabled', 'label', 'primaryText', 'secondaryText', " - "'style', 'value'.", - " Those keys have the following types:", - " - checked (boolean; optional)", - " - children (a list of or a singular dash component, string or number; optional)", - " - customData (bool | number | str | dict | list; required): A test description", - " - disabled (boolean; optional)", - " - label (string; optional)", - " - primaryText (string; required): Another test description", - " - secondaryText (string; optional)", - " - style (dict; optional)", - " - value (bool | number | str | dict | list; required)", - "- requiredNested (dict; required): requiredNested has the following type: dict containing " - "keys 'customData', 'value'.", - " Those keys have the following types:", - " - customData (dict; required): customData has the following type: dict containing " - "keys 'checked', 'children', 'customData', 'disabled', 'label', 'primaryText', " - "'secondaryText', 'style', 'value'.", - " Those keys have the following types:", + "", + "- optionalArray (list; optional):", + " An array test with a particularly long description that covers", + " several lines. It includes the newline character and should span", + " 3 lines in total.", + "", + "- optionalBoolean (boolean; default False):", + " A boolean test.", + "", + "- optionalNode (a list of or a singular dash component, string or number; optional):", + " A node test.", + "", + "- optionalSignature(shape) (dict; optional):", + " This is a test of an object's shape.", + "", + " `optionalSignature(shape)` is a dict with keys:", + "", " - checked (boolean; optional)", + "", " - children (a list of or a singular dash component, string or number; optional)", - " - customData (bool | number | str | dict | list; required)", + "", + " - customData (bool | number | str | dict | list; required):", + " A test description.", + "", " - disabled (boolean; optional)", + "", " - label (string; optional)", - " - primaryText (string; required)", + "", + " - primaryText (string; required):", + " Another test description.", + "", " - secondaryText (string; optional)", + "", " - style (dict; optional)", + "", " - value (bool | number | str | dict | list; required)", - " - value (bool | number | str | dict | list; required)", + "", + "- optionalString (string; default ''):", + " A string that isn't required.", + "", + "- requiredNested (dict; required)", + "", + " `requiredNested` is a dict with keys:", + "", + " - customData (dict; required)", + "", + " `customData` is a dict with keys:", + "", + " - checked (boolean; optional)", + "", + " - children (a list of or a singular dash component, string or number; optional)", + "", + " - customData (bool | number | str | dict | list; required)", + "", + " - disabled (boolean; optional)", + "", + " - label (string; optional)", + "", + " - primaryText (string; required)", + "", + " - secondaryText (string; optional)", + "", + " - style (dict; optional)", + "", + " - value (bool | number | str | dict | list; required)", + "", + " - value (bool | number | str | dict | list; required)", + "", + "- requiredString (string; required):", + " A required string.", + "", + "- requiredUnion (string | number; required)", ] @@ -129,13 +162,13 @@ def test_docstring(load_test_flow_metadata_json): load_test_flow_metadata_json["props"], load_test_flow_metadata_json["description"], ) + print(docstring.splitlines()) prohibit_events(load_test_flow_metadata_json["props"]), assert not list(unified_diff(expected_doc, docstring.splitlines())) def test_docgen_to_python_args(load_test_flow_metadata_json): props = load_test_flow_metadata_json["props"] - for prop_name, prop in list(props.items()): assert ( js_to_py_type(prop["flowType"], is_flow_type=True) diff --git a/tests/unit/development/test_generate_class.py b/tests/unit/development/test_generate_class.py index 49ba7d5684..2c7c0de494 100644 --- a/tests/unit/development/test_generate_class.py +++ b/tests/unit/development/test_generate_class.py @@ -83,7 +83,7 @@ def test_repr_multiple_arguments(component_class): # Note how the order in which keyword arguments are supplied is # not always equal to the order in the repr of the component c = component_class(id="my id", optionalArray=[1, 2, 3]) - assert repr(c) == "Table(optionalArray=[1, 2, 3], id='my id')" + assert repr(c) == "Table(id='my id', optionalArray=[1, 2, 3])" def test_repr_nested_arguments(component_class): diff --git a/tests/unit/development/test_generate_class_file.py b/tests/unit/development/test_generate_class_file.py index eecc2ba34e..b3ba34109b 100644 --- a/tests/unit/development/test_generate_class_file.py +++ b/tests/unit/development/test_generate_class_file.py @@ -67,7 +67,6 @@ def test_class_string(expected_class_string, component_class_string): expected_class_string.splitlines(), component_class_string.splitlines() ) ) - assert not has_trailing_space(component_class_string) diff --git a/tests/unit/development/test_metadata_conversions.py b/tests/unit/development/test_metadata_conversions.py index 008efebe66..23203821bf 100644 --- a/tests/unit/development/test_metadata_conversions.py +++ b/tests/unit/development/test_metadata_conversions.py @@ -29,15 +29,18 @@ "optionalObjectWithExactAndNestedDescription", "\n".join( [ - "dict containing keys 'color', 'fontSize', 'figure'.", - "Those keys have the following types:", - " - color (string; optional)", - " - fontSize (number; optional)", - " - figure (dict; optional): Figure is a plotly graph object. figure has the following type: dict containing keys 'data', 'layout'.", + "dict with keys:\n", + " - color (string; optional)\n", + " - figure (dict; optional):", + " Figure is a plotly graph object.\n", + " `figure` is a dict with keys:\n", # noqa: E501 - "Those keys have the following types:", - " - data (list of dicts; optional): data is a collection of traces", - " - layout (dict; optional): layout describes the rest of the figure", # noqa: E501 + " - data (list of dicts; optional):", + " data is a collection of traces.\n", + " - layout (dict; optional):", + " layout describes the rest of the figure.\n", + # noqa: E501 + " - fontSize (number; optional)", ] ), ], @@ -45,16 +48,19 @@ "optionalObjectWithShapeAndNestedDescription", "\n".join( [ - "dict containing keys 'color', 'fontSize', 'figure'.", - "Those keys have the following types:", - " - color (string; optional)", - " - fontSize (number; optional)", - " - figure (dict; optional): Figure is a plotly graph object. figure has the following type: dict containing keys 'data', 'layout'.", + "dict with keys:\n", + " - color (string; optional)\n", + " - figure (dict; optional):", + " Figure is a plotly graph object.\n", + " `figure` is a dict with keys:\n", # noqa: E501 - "Those keys have the following types:", - " - data (list of dicts; optional): data is a collection of traces", - " - layout (dict; optional): layout describes the rest of the figure", # noqa: E501 - ] + " - data (list of dicts; optional):", + " data is a collection of traces.\n", + " - layout (dict; optional):", + " layout describes the rest of the figure.\n", + # noqa: E501 + " - fontSize (number; optional)", + ], ), ], ["optionalAny", "boolean | number | string | dict | list"],