Browse Source

Improve content of generated pyi for the Page Builder API (#1653)

- Change Page Builder API pyi generation script
- Updated actions
- Details in element examples
Fabien Lelaquais 9 tháng trước cách đây
mục cha
commit
39f5a454c8

+ 1 - 1
.github/actions/gui-test/pyi/action.yml

@@ -8,7 +8,7 @@ runs:
       run: pipenv run pip install mypy black isort
     - name: Generate pyi
       shell: bash
-      run: cp tools/gui/generate_pyi.py pyi_temp.py && pipenv run python pyi_temp.py && rm pyi_temp.py
+      run: pipenv run python tools/gui/generate_pyi.py
     - name: Cleanup any untracked files
       shell: bash
       run: git clean -f

+ 1 - 1
.github/workflows/build-and-release-single-package.yml

@@ -128,7 +128,7 @@ jobs:
       - name: Generate GUI pyi file
         if: github.event.inputs.target_package == 'gui'
         run: |
-          cp tools/gui/generate_pyi.py pyi_temp.py && pipenv run python pyi_temp.py && rm pyi_temp.py
+          pipenv run python tools/gui/generate_pyi.py
 
       - name: Build frontends
         if: github.event.inputs.target_package == 'gui'

+ 1 - 1
.github/workflows/build-and-release.yml

@@ -126,7 +126,7 @@ jobs:
       - name: Generate GUI pyi file
         if: matrix.package == 'gui'
         run: |
-          cp tools/gui/generate_pyi.py pyi_temp.py && pipenv run python pyi_temp.py && rm pyi_temp.py
+          pipenv run python tools/gui/generate_pyi.py
 
       - name: Build frontends
         if: matrix.package == 'gui'

+ 2 - 1
doc/gui/examples/controls/chat-discuss.py

@@ -19,12 +19,13 @@
 # incognito windows so a given user's context is not reused.
 # -----------------------------------------------------------------------------------------
 from os import path
+from typing import Union
 
 from taipy.gui import Gui, Icon
 from taipy.gui.gui_actions import navigate, notify
 
 username = ""
-users: list[str | Icon] = []
+users: list[Union[str, Icon]] = []
 messages: list[tuple[str, str, str]] = []
 
 Gui.add_shared_variables("messages", "users")

+ 15 - 15
doc/gui/examples/controls/metric-color-map.py

@@ -15,23 +15,23 @@
 # -----------------------------------------------------------------------------------------
 from taipy.gui import Gui
 
-# color_map = {
-#     # 0-20 - Let Taipy decide
-#     # 20-40 - red
-#     20: "red",
-#     # 40-60 - Let Taipy decide
-#     40: None,
-#     # 60-80 - blue
-#     60: "blue",
-#     # 80-100 - Let Taipy decide
-#     80: None
-# }
-
-value = 50
-color_map = {20: "red", 40: None, 60: "blue", 80: None}
+# Color wavelength
+color_wl = 530
+# Color ranges by wavelength
+color_map = {
+    200: None,
+    380: "violet",
+    435: "blue",
+    500: "cyan",
+    520: "green",
+    565: "yellow",
+    590: "orange",
+    625: "red",
+    740: None,
+}
 
 page = """
-<|{value}|metric|color_map={color_map}|>
+<|{color_wl}|metric|color_map={color_map}|format=%d nm|min=200|max=800|bar_color=gray|>
 """
 
 Gui(page).run()

+ 1 - 1
doc/gui/examples/controls/metric-layout.py

@@ -15,7 +15,7 @@
 # -----------------------------------------------------------------------------------------
 from taipy.gui import Gui
 
-value = 50
+value = 45
 # The layout object reference can be found in Plotly's documentation:
 #         https://plotly.com/python/reference/layout/
 layout = {

+ 4 - 5
doc/gui/examples/controls/metric-simple.py

@@ -15,13 +15,12 @@
 # -----------------------------------------------------------------------------------------
 from taipy.gui import Gui
 
-value = 50
-max_value = 150
-delta_value = 20
-threshold = 100
+value = 72
+delta = 15
+threshold = 60
 
 page = """
-<|{value}|metric|max={max_value}|delta={delta_value}|threshold={threshold}|>
+<|{value}|metric|delta={delta}|threshold={threshold}|>
 """
 
 Gui(page).run()

+ 59 - 63
taipy/gui/viselements.json

@@ -44,14 +44,14 @@
                     {
                         "name": "label",
                         "default_property": true,
-                        "type": "dynamic(str|Icon)",
+                        "type": "dynamic(Union[str,Icon])",
                         "default_value": "\"\"",
                         "doc": "The label displayed in the button."
                     },
                     {
                         "name": "on_action",
                         "type": "Callback",
-                        "doc": "The name of a function that is triggered when the button is pressed.<br/>The parameters of that function are all optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>id (str|None): the identifier of the button.</li>\n<li>payload (dict): a dictionary that contains the key \"action\" set to the name of the action that triggered this callback.</li>\n</ul>",
+                        "doc": "The name of a function that is triggered when the button is pressed.<br/>The parameters of that function are all optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>id (str): the identifier of the button it it has one.</li>\n<li>payload (dict): a dictionary that contains the key \"action\" set to the name of the action that triggered this callback.</li>\n</ul>",
                         "signature": [
                             [
                                 "state",
@@ -142,24 +142,24 @@
                     },
                     {
                         "name": "step",
-                        "type": "dynamic(int|float)",
+                        "type": "dynamic(Union[int,float])",
                         "default_value": "1",
                         "doc": "The amount by which the value is incremented or decremented when the user clicks one of the arrow buttons."
                     },
                     {
                         "name": "step_multiplier",
-                        "type": "dynamic(int|float)",
+                        "type": "dynamic(Union[int,float])",
                         "default_value": "10",
                         "doc": "A factor that multiplies <i>step</i> when the user presses the Shift key while clicking one of the arrow buttons."
                     },
                     {
                         "name": "min",
-                        "type": "dynamic(int|float)",
+                        "type": "dynamic(Union[int,float])",
                         "doc": "The minimum value to accept for this input."
                     },
                     {
                         "name": "max",
-                        "type": "dynamic(int|float)",
+                        "type": "dynamic(Union[int,float])",
                         "doc": "The maximum value to accept for this input."
                     }
                 ]
@@ -176,26 +176,26 @@
                     {
                         "name": "value",
                         "default_property": true,
-                        "type": "dynamic(int|float|int[]|float[]|str|str[])",
+                        "type": "dynamic(Union[int,float,str,list[int],list[float],list[str]])",
                         "doc": "The value that is set for this slider.<br/>If this slider is based on a <i>lov</i> then this property can be set to the lov element.<br/>This value can also hold an array of numbers to indicate that the slider reflects a range (within the [<i>min</i>,<i>max</i>] domain) defined by several knobs that the user can set independently.<br/>If this slider is based on a <i>lov</i> then this property can be set to an array of lov elements. The slider is then represented with several knobs, one for each lov value."
                     },
                     {
                         "name": "min",
-                        "type": "int|float",
+                        "type": "Union[int,float]",
                         "default_value": "0",
                         "doc": "The minimum value.<br/>This is ignored when <i>lov</i> is defined."
                     },
                     {
                         "name": "max",
-                        "type": "int|float",
+                        "type": "Union[int,float]",
                         "default_value": "100",
                         "doc": "The maximum value.<br/>This is ignored when <i>lov</i> is defined."
                     },
                     {
                         "name": "step",
-                        "type": "int|float",
+                        "type": "Union[int,float]",
                         "default_value": "1",
-                        "doc": "The step value: the gap between two consecutive values the slider set. It is a good practice to have (<i>max</i>-<i>min</i>) being divisible by <i>step</i>.<br/>This property is ignored when <i>lov</i> is defined."
+                        "doc": "The step value, which is the gap between two consecutive values the slider set. It is a good practice to have (<i>max</i>-<i>min</i>) being divisible by <i>step</i>.<br/>This property is ignored when <i>lov</i> is defined."
                     },
                     {
                         "name": "text_anchor",
@@ -205,7 +205,7 @@
                     },
                     {
                         "name": "labels",
-                        "type": "bool|dict",
+                        "type": "Union[bool,dict[str,str]]",
                         "doc": "The labels for specific points of the slider.<br/>If set to True, this slider uses the labels of the <i>lov</i> if there are any.<br/>If set to a dictionary, the slider uses the dictionary keys as a <i>lov</i> key or index, and the associated value as the label."
                     },
                     {
@@ -501,7 +501,7 @@
                     {
                         "name": "on_range_change",
                         "type": "Callback",
-                        "doc": "The callback function that is invoked when the visible part of the x axis changes.<br/>The function receives three parameters:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>id (str|None): the identifier of the chart control.</li>\n<li>payload (dict[str, any]): the full details on this callback's invocation, as emitted by <a href=\"https://plotly.com/javascript/plotlyjs-events/#update-data\">Plotly</a>.</li>\n</ul>",
+                        "doc": "The callback function that is invoked when the visible part of the x axis changes.<br/>The function receives three parameters:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>id (str): the identifier of the chart control if it has one.</li>\n<li>payload (dict[str, any]): the full details on this callback's invocation, as emitted by <a href=\"https://plotly.com/javascript/plotlyjs-events/#update-data\">Plotly</a>.</li>\n</ul>",
                         "signature": [
                             [
                                 "state",
@@ -519,7 +519,7 @@
                     },
                     {
                         "name": "columns",
-                        "type": "str|list[str]|dict[str, dict[str, str]]",
+                        "type": "Union[str,list[str],dict[str,dict[str,str]]]",
                         "default_value": "<i>All columns</i>",
                         "doc": "The list of column names\n<ul>\n<li>str: ;-separated list of column names</li>\n<li>list[str]: list of names</li>\n<li>dict: {\"column_name\": {format: \"format\", index: 1}} if index is specified, it represents the display order of the columns.\nIf not, the list order defines the index</li>\n</ul>"
                     },
@@ -535,7 +535,7 @@
                     },
                     {
                         "name": "selected",
-                        "type": "indexed(dynamic(list[int]|str))",
+                        "type": "indexed(dynamic(Union[list[int],str]))",
                         "doc": "The list of the selected point indices  ."
                     },
                     {
@@ -555,7 +555,7 @@
                     },
                     {
                         "name": "line",
-                        "type": "indexed(str|dict[str, any])",
+                        "type": "indexed(Union[str,dict[str,any]])",
                         "doc": "The configuration of the line used for the indicated trace.<br/>See <a href=\"https://plotly.com/javascript/reference/scatter/#scatter-line\">line</a> for details.<br/>If the value is a string, it must be a dash type or pattern (see <a href=\"https://plotly.com/python/reference/scatter/#scatter-line-dash\">dash style of lines</a> for details)."
                     },
                     {
@@ -600,13 +600,13 @@
                     },
                     {
                         "name": "width",
-                        "type": "str|int|float",
+                        "type": "Union[str,int,float]",
                         "default_value": "\"100%\"",
                         "doc": "The width of this chart, in CSS units."
                     },
                     {
                         "name": "height",
-                        "type": "str|int|float",
+                        "type": "Union[str,int,float]",
                         "doc": "The height of this chart, in CSS units."
                     },
                     {
@@ -689,18 +689,18 @@
                     },
                     {
                         "name": "selected",
-                        "type": "dynamic(list[int]|str)",
+                        "type": "dynamic(Union[list[int],str])",
                         "doc": "The list of the indices of the rows to be displayed as selected."
                     },
                     {
                         "name": "page_size_options",
-                        "type": "list[int]|str",
-                        "default_value": "[50, 100, 500]",
+                        "type": "Union[list[int],str]",
+                        "default_value": "(50, 100, 500)",
                         "doc": "The list of available page sizes that users can choose from."
                     },
                     {
                         "name": "columns",
-                        "type": "str|list[str]|dict[str, dict[str, str|int]]",
+                        "type": "Union[str,list[str],dict[str,dict[str,Union[str,int]]]]",
                         "default_value": "<i>shows all columns when empty</i>",
                         "doc": "The list of the column names to display.\n<ul>\n<li>str: Semicolon (';')-separated list of column names.</li>\n<li>list[str]: The list of column names.</li>\n<li>dict: A dictionary with entries matching: {\"col name\": {format: \"format\", index: 1}}.<br/>\nif <i>index</i> is specified, it represents the display order of the columns.\nIf <i>index</i> is not specified, the list order defines the index.<br/>\nIf <i>format</i> is specified, it is used for numbers or dates.</li>\n</ul>"
                     },
@@ -885,12 +885,12 @@
                     },
                     {
                         "name": "lov[<i>column_name</i>]",
-                        "type": "list[str]|str",
+                        "type": "Union[list[str],str]",
                         "doc": "The list of values of the indicated column."
                     },
                     {
                         "name": "downloadable",
-                        "type": "boolean",
+                        "type": "bool",
                         "doc": "If True, a clickable icon is shown so the user can download the data as CSV."
                     },
                     {
@@ -954,13 +954,13 @@
                     },
                     {
                         "name": "width",
-                        "type": "str|int",
+                        "type": "Union[str,int]",
                         "default_value": "\"360px\"",
                         "doc": "The width of this selector, in CSS units."
                     },
                     {
                         "name": "height",
-                        "type": "str|int",
+                        "type": "Union[str,int]",
                         "doc": "The height of this selector, in CSS units."
                     }
                 ]
@@ -977,7 +977,7 @@
                     {
                         "name": "content",
                         "default_property": true,
-                        "type": "dynamic(path|file|URL|ReadableBuffer|None)",
+                        "type": "dynamic(Union[path,file,URL,ReadableBuffer,None])",
                         "doc": "The content to transfer.<br/>If this is a string, a URL, or a file, then the content is read from this source.<br/>If a readable buffer is provided (such as an array of bytes...), and to prevent the bandwidth from being consumed too much, the way the data is transferred depends on the <i>data_url_max_size</i> parameter of the application configuration (which is set to 50kB by default):\n<ul>\n<li>If the buffer size is smaller than this setting, then the raw content is generated as a data URL, encoded using base64 (i.e. <code>\"data:&lt;mimetype&gt;;base64,&lt;data&gt;\"</code>).</li>\n<li>If the buffer size exceeds this setting, then it is transferred through a temporary file.</li>\n</ul>If this property is set to None, that indicates that dynamic content is generated. Please take a look at the examples below for details on dynamic generation."
                     },
                     {
@@ -988,7 +988,7 @@
                     {
                         "name": "on_action",
                         "type": "Callback",
-                        "doc": "The name of a function that is triggered when the download is terminated (or on user action if <i>content</i> is None).<br/>All the parameters of that function are optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>id (str|None): the identifier of the button.</li>\n<li>payload (dict): the details on this callback's invocation.<br/>\nThis dictionary has two keys:\n<ul>\n<li>action: the name of the action that triggered this callback.</li>\n<li>args: A list of two elements: <i>args[0]</i> reflects the <i>name</i> property and <i>args[1]</i> holds the file URL.</li>\n</ul>\n</li>\n</ul>",
+                        "doc": "The name of a function that is triggered when the download is terminated (or on user action if <i>content</i> is None).<br/>All the parameters of that function are optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>id (str): the identifier of the button if it has one.</li>\n<li>payload (dict): the details on this callback's invocation.<br/>\nThis dictionary has two keys:\n<ul>\n<li>action: the name of the action that triggered this callback.</li>\n<li>args: A list of two elements: <i>args[0]</i> reflects the <i>name</i> property and <i>args[1]</i> holds the file URL.</li>\n</ul>\n</li>\n</ul>",
                         "signature": [
                             [
                                 "state",
@@ -1052,7 +1052,7 @@
                     {
                         "name": "on_action",
                         "type": "Callback",
-                        "doc": "The name of the function that will be triggered.<br/>All the parameters of that function are optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>id (str|None): the identifier of the button.</li>\n<li>payload (dict): a dictionary that contains the key \"action\" set to the name of the action that triggered this callback.</li>\n</ul>",
+                        "doc": "The name of the function that will be triggered.<br/>All the parameters of that function are optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>id (str): the identifier of the button if it has one.</li>\n<li>payload (dict): a dictionary that contains the key \"action\" set to the name of the action that triggered this callback.</li>\n</ul>",
                         "signature": [
                             [
                                 "state",
@@ -1106,7 +1106,7 @@
                     {
                         "name": "content",
                         "default_property": true,
-                        "type": "dynamic(path|URL|file|ReadableBuffer)",
+                        "type": "dynamic(Union[path,URL,file,ReadableBuffer])",
                         "doc": "The image source.<br/>If a buffer is provided (string, array of bytes...), and in order to prevent the bandwidth to be consumed too much, the way the image data is transferred depends on the <i>data_url_max_size</i> parameter of the application configuration (which is set to 50kB by default):\n<ul>\n<li>If the size of the buffer is smaller than this setting, then the raw content is generated as a\n  data URL, encoded using base64 (i.e. <code>\"data:&lt;mimetype&gt;;base64,&lt;data&gt;\"</code>).</li>\n<li>If the size of the buffer is greater than this setting, then it is transferred through a temporary\n  file.</li>\n</ul>"
                     },
                     {
@@ -1117,7 +1117,7 @@
                     {
                         "name": "on_action",
                         "type": "Callback",
-                        "doc": "The name of a function that is triggered when the user clicks on the image.<br/>All the parameters of that function are optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>id (str|None): the identifier of the button.</li>\n<li>payload (dict): a dictionary that contains the key \"action\" set to the name of the action that triggered this callback.</li>\n</ul>",
+                        "doc": "The name of a function that is triggered when the user clicks on the image.<br/>All the parameters of that function are optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>id (str): the identifier of the button if it has one.</li>\n<li>payload (dict): a dictionary that contains the key \"action\" set to the name of the action that triggered this callback.</li>\n</ul>",
                         "signature": [
                             [
                                 "state",
@@ -1135,13 +1135,13 @@
                     },
                     {
                         "name": "width",
-                        "type": "str|int|float",
+                        "type": "Union[str,int,float]",
                         "default_value": "\"300px\"",
                         "doc": "The width of this image control, in CSS units."
                     },
                     {
                         "name": "height",
-                        "type": "str|int|float",
+                        "type": "Union[str,int,float]",
                         "doc": "The height of this image control, in CSS units."
                     }
                 ]
@@ -1157,7 +1157,7 @@
                     {
                         "name": "value",
                         "default_property": true,
-                        "type": "dynamic(int|float)",
+                        "type": "dynamic(Union[int,float])",
                         "doc": "The value to represent."
                     },
                     {
@@ -1168,19 +1168,19 @@
                     },
                     {
                         "name": "min",
-                        "type": "int|float",
+                        "type": "Union[int,float]",
                         "default_value": "0",
                         "doc": "The minimum value of this metric control's gauge."
                     },
                     {
                         "name": "max",
-                        "type": "int|float",
+                        "type": "Union[int,float]",
                         "default_value": "100",
                         "doc": "The maximum value of this metric control's gauge."
                     },
                     {
                         "name": "delta",
-                        "type": "dynamic(int|float)",
+                        "type": "dynamic(Union[int,float])",
                         "doc": "The delta value to display."
                     },
                     {
@@ -1201,7 +1201,7 @@
                     },
                     {
                         "name": "threshold",
-                        "type": "dynamic(int|float)",
+                        "type": "dynamic(Union[int,float])",
                         "doc": "The threshold value to display."
                     },
                     {
@@ -1232,13 +1232,13 @@
                     },
                     {
                         "name": "width",
-                        "type": "str|number",
+                        "type": "Union[str,number]",
                         "default_value": "None",
                         "doc": "The width of the metric control, in CSS units."
                     },
                     {
                         "name": "height",
-                        "type": "str|number",
+                        "type": "Union[str,number]",
                         "default_value": "None",
                         "doc": "The height of the metric control, in CSS units."
                     },
@@ -1317,13 +1317,13 @@
                     },
                     {
                         "name": "min",
-                        "type": "int|float",
+                        "type": "Union[int,float]",
                         "default_value": "0",
                         "doc": "The minimum value of the range."
                     },
                     {
                         "name": "max",
-                        "type": "int|float",
+                        "type": "Union[int,float]",
                         "default_value": "100",
                         "doc": "The maximum value of the range."
                     },
@@ -1367,14 +1367,14 @@
                     {
                         "name": "lov",
                         "default_property": true,
-                        "type": "dynamic(str|list[str|Icon|any])",
+                        "type": "dynamic(Union[str,list[Union[str,Icon,any]]])",
                         "doc": "The list of menu option values."
                     },
                     {
                         "name": "adapter",
                         "type": "Function",
                         "default_value": "`\"lambda x: str(x)\"`",
-                        "doc": "The function that transforms an element of <i>lov</i> into a <i>tuple(id:str, label:str|Icon)</i>."
+                        "doc": "The function that transforms an element of <i>lov</i> into a <i>tuple(id:str, label:Union[str,Icon])</i>."
                     },
                     {
                         "name": "type",
@@ -1389,7 +1389,7 @@
                     },
                     {
                         "name": "inactive_ids",
-                        "type": "dynamic(str|list[str])",
+                        "type": "dynamic(Union[str,list[str]])",
                         "doc": "Semicolon (';')-separated list or a list of menu items identifiers that are disabled."
                     },
                     {
@@ -1407,7 +1407,7 @@
                     {
                         "name": "on_action",
                         "type": "Callback",
-                        "doc": "The name of the function that is triggered when a menu option is selected.<br/><br/>All the parameters of that function are optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>id (str): the identifier of the button.</li>\n<li>payload (dict): the details on this callback's invocation.<br/>\nThis dictionary has the following keys:\n<ul>\n<li>action: the name of the action that triggered this callback.</li>\n<li>args: List where the first element contains the id of the selected option.</li>\n</ul>\n</li>\n</ul>",
+                        "doc": "The name of the function that is triggered when a menu option is selected.<br/><br/>All the parameters of that function are optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>id (str): the identifier of the button if it has one.</li>\n<li>payload (dict): the details on this callback's invocation.<br/>\nThis dictionary has the following keys:\n<ul>\n<li>action: the name of the action that triggered this callback.</li>\n<li>args: List where the first element contains the id of the selected option.</li>\n</ul>\n</li>\n</ul>",
                         "signature": [
                             [
                                 "state",
@@ -1453,7 +1453,7 @@
                     {
                         "name": "value",
                         "default_property": true,
-                        "type": "tuple|dict|list[dict]|list[tuple]",
+                        "type": "Union[tuple,dict,list[dict],list[tuple]]",
                         "doc": "The different status items to represent. See below."
                     },
                     {
@@ -1482,7 +1482,7 @@
                     {
                         "name": "on_action",
                         "type": "Callback",
-                        "doc": "The name of the function that is triggered when the dialog button is pressed.<br/><br/>All the parameters of that function are optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>id (str): the identifier of the button.</li>\n<li>payload (dict): the details on this callback's invocation.<br/>\nThis dictionary has the following keys:\n<ul>\n<li>action: the name of the action that triggered this callback.</li>\n<li>args: a list with three elements:\n<ul><li>The first element is the username</li><li>The second element is the password</li><li>The third element is the current page name</li></ul></li></li>\n</ul>\n</li>\n</ul><br/>When the button is pressed, and if this property is not set, Taipy will try to find a callback function called <i>on_login()</i> and invoke it with the parameters listed above.",
+                        "doc": "The name of the function that is triggered when the dialog button is pressed.<br/><br/>All the parameters of that function are optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>id (str): the identifier of the button if it has one.</li>\n<li>payload (dict): the details on this callback's invocation.<br/>\nThis dictionary has the following keys:\n<ul>\n<li>action: the name of the action that triggered this callback.</li>\n<li>args: a list with three elements:\n<ul><li>The first element is the username</li><li>The second element is the password</li><li>The third element is the current page name</li></ul></li></li>\n</ul>\n</li>\n</ul><br/>When the button is pressed, and if this property is not set, Taipy will try to find a callback function called <i>on_login()</i> and invoke it with the parameters listed above.",
                         "signature": [
                             [
                                 "state",
@@ -1523,7 +1523,7 @@
                     },
                     {
                         "name": "users",
-                        "type": "dynamic(list[str|Icon])",
+                        "type": "dynamic(list[Union[str,Icon]])",
                         "doc": "The list of users. See the <a href=\"../../binding/#list-of-values\">section on List of Values</a> for details."
                     },
                     {
@@ -1565,7 +1565,7 @@
                     },
                     {
                         "name": "height",
-                        "type": "str|int|float",
+                        "type": "Union[str,int,float]",
                         "doc": "The maximum height of this chat control, in CSS units."
                     }
                 ]
@@ -1580,7 +1580,7 @@
                 "properties": [
                     {
                         "name": "expanded",
-                        "type": "dynamic(bool|str[])",
+                        "type": "dynamic(Union[bool,list[str]])",
                         "default_value": "True",
                         "doc": "If Boolean and False, only one node can be expanded at one given level. Otherwise this should be set to an array of the node identifiers that need to be expanded."
                     },
@@ -1687,7 +1687,7 @@
                     {
                         "name": "on_action",
                         "type": "Callback",
-                        "doc": "Name of a function triggered when a button is pressed.<br/>The parameters of that function are all optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>id (str): the identifier of the dialog.</li>\n<li>payload (dict): the details on this callback's invocation.<br/>This dictionary has the following keys:\n<ul>\n<li>action: the name of the action that triggered this callback.</li>\n<li>args: a list where the first element contains the index of the selected label.</li>\n</ul>\n</li>\n</ul>",
+                        "doc": "Name of a function triggered when a button is pressed.<br/>The parameters of that function are all optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>id (str): the identifier of the dialog if it has one.</li>\n<li>payload (dict): the details on this callback's invocation.<br/>This dictionary has the following keys:\n<ul>\n<li>action: the name of the action that triggered this callback.</li>\n<li>args: a list where the first element contains the index of the selected label.</li>\n</ul>\n</li>\n</ul>",
                         "signature": [
                             [
                                 "state",
@@ -1711,17 +1711,17 @@
                     },
                     {
                         "name": "labels",
-                        "type": " str|list[str]",
+                        "type": "Union[str,list[str]]",
                         "doc": "A list of labels to show in a row of buttons at the bottom of the dialog. The index of the button in the list is reported as args in the <tt>on_action</tt> callback (that index is -1 for the <i>close</i> icon)."
                     },
                     {
                         "name": "width",
-                        "type": "str|int|float",
+                        "type": "Union[str,int,float]",
                         "doc": "The width of this dialog, in CSS units."
                     },
                     {
                         "name": "height",
-                        "type": "str|int|float",
+                        "type": "Union[str,int,float]",
                         "doc": "The height of this dialog, in CSS units."
                     }
                 ]
@@ -1776,7 +1776,7 @@
                     {
                         "name": "on_close",
                         "type": "Callback",
-                        "doc": "The name of a function that is triggered when this pane is closed (if the user clicks outside of it or presses the Esc key).<br/>All parameters of that function are optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>id (str|None): the identifier of the button.</li>\n</ul><br/>If this property is not set, no function is called when this pane is closed.",
+                        "doc": "The name of a function that is triggered when this pane is closed (if the user clicks outside of it or presses the Esc key).<br/>All parameters of that function are optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>id (optional[str]): the identifier of the close button if it has one.</li>\n</ul><br/>If this property is not set, no function is called when this pane is closed.",
                         "signature": [
                             [
                                 "state",
@@ -1852,7 +1852,7 @@
                         "name": "adapter",
                         "type": "Function",
                         "default_value": "`lambda x: str(x)`",
-                        "doc": "The function that transforms an element of <i>lov</i> into a <i>tuple(id:str, label:str|Icon)</i>."
+                        "doc": "The function that transforms an element of <i>lov</i> into a <i>tuple(id:str, label:Union[str,Icon])</i>."
                     },
                     {
                         "name": "type",
@@ -1901,7 +1901,7 @@
                 "properties": [
                     {
                         "name": "partial",
-                        "type": "Partial",
+                        "type": "taipy.gui.Partial",
                         "doc": "A Partial object that holds the content of the block.<br/>This should not be defined if <i>page</i> is set."
                     },
                     {
@@ -1942,7 +1942,7 @@
                     {
                         "name": "on_action",
                         "type": "Callback",
-                        "doc": "Name of a function that is triggered when a specific key is pressed.<br/>The parameters of that function are all optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>id (str): the identifier of the input.</li>\n<li>payload (dict): the details on this callback's invocation.<br/>\nThis dictionary has the following keys:\n<ul>\n<li>action: the name of the action that triggered this callback.</li>\n<li>args (list):\n<ul><li>key name</li><li>variable name</li><li>current value</li></ul>\n</li>\n</ul>\n</li>\n</ul>",
+                        "doc": "Name of a function that is triggered when a specific key is pressed.<br/>The parameters of that function are all optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>id (str): the identifier of the control if it has one.</li>\n<li>payload (dict): the details on this callback's invocation.<br/>\nThis dictionary has the following keys:\n<ul>\n<li>action: the name of the action that triggered this callback.</li>\n<li>args (list):\n<ul><li>key name</li><li>variable name</li><li>current value</li></ul>\n</li>\n</ul>\n</li>\n</ul>",
                         "signature": [
                             [
                                 "state",
@@ -1974,25 +1974,21 @@
                     {
                         "name": "id",
                         "type": "str",
-                        "default_value": "None",
                         "doc": "The identifier that is assigned to the rendered HTML component."
                     },
                     {
                         "name": "properties",
                         "type": "dict[str, any]",
-                        "default_value": "None",
                         "doc": "Bound to a dictionary that contains additional properties for this element."
                     },
                     {
                         "name": "class_name",
                         "type": "dynamic(str)",
-                        "default_value": "None",
                         "doc": "The list of CSS class names that are associated with the generated HTML Element.<br/>These class names are added to the default <code>taipy-&lt;element_type&gt;</code> class name."
                     },
                     {
                         "name": "hover_text",
                         "type": "dynamic(str)",
-                        "default_value": "None",
                         "doc": "The information that is displayed when the user hovers over this element."
                     }
                 ]

+ 0 - 8
tools/gui/builder/block.txt

@@ -1,8 +0,0 @@
-
-class {{name}}(_Block):
-    _ELEMENT_NAME: str
-    def __init__(self, {{properties}}) -> None:
-        """### Arguments:
-{{doc_arguments}}
-        """
-        ...

+ 0 - 8
tools/gui/builder/control.txt

@@ -1,8 +0,0 @@
-
-class {{name}}(_Control):
-    _ELEMENT_NAME: str
-    def __init__(self, {{properties}}) -> None:
-        """### Arguments:
-{{doc_arguments}}
-        """
-        ...

+ 125 - 93
tools/gui/generate_pyi.py

@@ -12,24 +12,25 @@
 import json
 import os
 import re
-import typing as t
+import sys
 
 from markdownify import markdownify
 
-# ############################################################
-# Generate Python interface definition files
-# ############################################################
-from taipy.gui.config import Config
+# Make sure we can import the mandatory packages
+script_dir = os.path.dirname(os.path.realpath(__file__))
+if not os.path.isdir(os.path.abspath(os.path.join(script_dir, "taipy"))):
+    sys.path.append(os.path.abspath(os.path.join(script_dir, os.pardir, os.pardir)))
 
-# ############################################################
+# ##################################################################################################
 # Generate gui pyi file (gui/gui.pyi)
-# ############################################################
+# ##################################################################################################
 gui_py_file = "./taipy/gui/gui.py"
-gui_pyi_file = gui_py_file + "i"
+gui_pyi_file = f"{gui_py_file}i"
+from taipy.config import Config  # noqa: E402
 
+# Generate Python interface definition files
 os.system(f"pipenv run stubgen {gui_py_file} --no-import --parse-only --export-less -o ./")
 
-
 gui_config = "".join(
     f", {k}: {v.__name__} = ..."
     if "<class" in str(v)
@@ -44,14 +45,15 @@ with open(gui_pyi_file, "r") as file:
             replace_str = line[line.index(", run_server") : (line.index("**kwargs") + len("**kwargs"))]
             # ", run_server: bool = ..., run_in_thread: bool = ..., async_mode: str = ..., **kwargs"
             line = line.replace(replace_str, gui_config)
-        replaced_content = replaced_content + line
+        replaced_content += line
 
 with open(gui_pyi_file, "w") as write_file:
     write_file.write(replaced_content)
 
-# ################
+# ##################################################################################################
+# Generate Page Builder pyi file (gui/builder/__init__.pyi)
+# ##################################################################################################
 # Read the version
-# ################
 current_version = "latest"
 with open("./taipy/gui/version.json", "r") as vfile:
     version = json.load(vfile)
@@ -59,108 +61,138 @@ with open("./taipy/gui/version.json", "r") as vfile:
         current_version = "develop"
     else:
         current_version = f'release-{version.get("major", 0)}.{version.get("minor", 0)}'
-taipy_doc_url = f"https://docs.taipy.io/en/{current_version}/manuals/userman/gui/viselements/generic/"
 
+taipy_doc_url = f"https://docs.taipy.io/en/{current_version}/manuals/userman/gui/viselements/generic/"
 
-# ############################################################
-# Generate Page Builder pyi file (gui/builder/__init__.pyi)
-# ############################################################
 builder_py_file = "./taipy/gui/builder/__init__.py"
-builder_pyi_file = builder_py_file + "i"
+builder_pyi_file = f"{builder_py_file}i"
 with open("./taipy/gui/viselements.json", "r") as file:
     viselements = json.load(file)
-with open("./tools/gui/builder/block.txt", "r") as file:
-    block_template = file.read()
-with open("./tools/gui/builder/control.txt", "r") as file:
-    control_template = file.read()
-
 os.system(f"pipenv run stubgen {builder_py_file} --no-import --parse-only --export-less -o ./")
 
 with open(builder_pyi_file, "a") as file:
-    file.write("from ._element import _Element, _Block, _Control\n")
+    file.write("from typing import Union\n")
+    file.write("\n")
+    file.write("from ._element import _Block, _Control, _Element\n")
 
 
-def get_properties(element, viselements) -> t.List[t.Dict[str, t.Any]]:
-    properties = element["properties"]
-    if "inherits" not in element:
+def resolve_inherit(name: str, properties, inherits, viselements) -> list[dict[str, any]]:
+    if not inherits:
         return properties
-    for inherit in element["inherits"]:
-        inherit_element = next((e for e in viselements["undocumented"] if e[0] == inherit), None)
-        if inherit_element is None:
-            inherit_element = next((e for e in viselements["blocks"] if e[0] == inherit), None)
-        if inherit_element is None:
-            inherit_element = next((e for e in viselements["controls"] if e[0] == inherit), None)
-        if inherit_element is None:
-            raise RuntimeError(f"Can't find element with name {inherit}")
-        properties += get_properties(inherit_element[1], viselements)
+    for inherit_name in inherits:
+        inherited_desc = next((e for e in viselements["undocumented"] if e[0] == inherit_name), None)
+        if inherited_desc is None:
+            inherited_desc = next((e for e in viselements["blocks"] if e[0] == inherit_name), None)
+        if inherited_desc is None:
+            inherited_desc = next((e for e in viselements["controls"] if e[0] == inherit_name), None)
+        if inherited_desc is None:
+            raise RuntimeError(f"Element type '{name}' inherits from unknown element type '{inherit_name}'")
+        inherited_desc = inherited_desc[1]
+        for inherit_prop in inherited_desc["properties"]:
+            prop_desc = next((p for p in properties if p["name"] == inherit_prop["name"]), None)
+            if prop_desc: # Property exists
+                def override(current, inherits, p: str):
+                    if p not in current and (inherited := inherits.get(p, None)):
+                        current[p] = inherited
+                override(prop_desc, inherit_prop, "type")
+                override(prop_desc, inherit_prop, "default_value")
+                override(prop_desc, inherit_prop, "doc")
+                override(prop_desc, inherit_prop, "signature")
+            else:
+                properties.append(inherit_prop)
+            properties =  resolve_inherit(inherit_name, properties, inherited_desc.get("inherits", None), viselements)
     return properties
 
+def format_as_parameter(property):
+    type = property["type"]
+    if m := re.match(r"indexed\((.*)\)", type):
+        type = m[1]
+        property["indexed"] = " (indexed)"
+    else:
+        property["indexed"] = ""
+    if m := re.match(r"dynamic\((.*)\)", type):
+        type = m[1]
+        property["dynamic"] = " (dynamic)"
+    else:
+        property["dynamic"] = ""
+    if type == "Callback" or type == "Function":
+        type = ""
+    else:
+        type = f": {type}"
+    default_value = property.get("default_value", None)
+    if default_value is not None:
+        try:
+            eval(default_value)
+            default_value = f" = {default_value}"
+        except Exception:
+            default_value = ""
+    else:
+        default_value = ""
+    return f"{property['name']}{type}{default_value}"
 
-def build_doc(name: str, element: t.Dict[str, t.Any]):
-    if "doc" not in element:
+def build_doc(name: str, desc: dict[str, any]):
+    if "doc" not in desc:
         return ""
-    doc = str(element["doc"]).replace("\n", f'\n{16*" "}')
-    doc = re.sub(
-        r"^(.*\..*\shref=\")([^h].*)(\".*\..*)$",
-        r"\1" + taipy_doc_url + name + r"/\2\3",
-        doc,
-    )
-    doc = re.sub(
-        r"^(.*\.)(<br/>|\s)(See below((?!href=).)*\.)(.*)$",
-        r"\1\3",
-        doc,
-    )
-    doc = markdownify(doc, strip=["br"])
-    return f"{element['name']} ({element['type']}): {doc} {'(default: '+markdownify(element['default_value']) + ')' if 'default_value' in element else ''}"  # noqa: E501
-
-
-for control_element in viselements["controls"]:
-    name = control_element[0]
-    property_list: t.List[t.Dict[str, t.Any]] = []
-    property_names: t.List[str] = []
-    hidden_properties: t.List[str] = []
-    for property in get_properties(control_element[1], viselements):
-        if "hide" in property and property["hide"] is True:
-            hidden_properties.append(property["name"])
-            continue
-        if (
-            property["name"] not in property_names
-            and "[" not in property["name"]
-            and property["name"] not in hidden_properties
-        ):
+    doc = desc["doc"]
+    if desc["name"] == "class_name":
+        doc = doc.replace("<element_type>", name)
+    # This won't work for Scenartio Management and Block elements
+    doc = re.sub(r"(href=\")\.\.((?:.*?)\")", r"\1" + taipy_doc_url + name + r"/../..\2", doc)
+    doc = "\n  ".join(markdownify(doc).split("\n"))
+    doc = doc.replace("  \n", "  \\n")
+    doc = re.sub(r"(?:\s+\\n)?\s+See below(?:[^\.]*)?\.", "", doc).replace("\n", "\\n")
+    return f"{desc['name']}{desc['dynamic']}{desc['indexed']}\\n  {doc}\\n\\n"
+
+
+element_template = """
+
+class {{name}}(_{{base_class}}):
+    _ELEMENT_NAME: str
+    def __init__(self, {{properties_decl}}) -> None:
+        \"\"\"Creates a{{n}} {{name}} element.\\n\\nParameters\\n----------\\n\\n{{properties_doc}}\"\"\"  # noqa: E501
+        ...
+"""
+
+def generate_elements(category: str, base_class: str):
+    for element in viselements[category]:
+        name = element[0]
+        desc = element[1]
+        properties_doc = ""
+        property_list: list[dict[str, any]] = []
+        property_names: list[str] = []
+        properties = resolve_inherit(name, desc["properties"], desc.get("inherits", None), viselements)
+        # Remove hidden properties and indexed properties (TODO?)
+        properties = [p for p in properties if not p.get("hide", False) and "[" not in p["name"]]
+        # Generate function parameters
+        properties_decl = [format_as_parameter(p) for p in properties]
+        # Generate properties doc
+        for property in properties:
             if "default_property" in property and property["default_property"] is True:
                 property_list.insert(0, property)
                 property_names.insert(0, property["name"])
                 continue
             property_list.append(property)
             property_names.append(property["name"])
-    properties = ", ".join([f"{p} = ..." for p in property_names if p not in hidden_properties])
-    doc_arguments = "\n".join([build_doc(name, p) for p in property_list if p["name"] not in hidden_properties])
-    # append properties to __init__.pyi
-    with open(builder_pyi_file, "a") as file:
-        file.write(
-            control_template.replace("{{name}}", name)
-            .replace("{{properties}}", properties)
-            .replace("{{doc_arguments}}", doc_arguments)
-        )
-
-for block_element in viselements["blocks"]:
-    name = block_element[0]
-    property_list = []
-    property_names = []
-    for property in get_properties(block_element[1], viselements):
-        if property["name"] not in property_names and "[" not in property["name"]:
-            property_list.append(property)
-            property_names.append(property["name"])
-    properties = ", ".join([f"{p} = ..." for p in property_names])
-    doc_arguments = "\n".join([build_doc(name, p) for p in property_list])
-    # append properties to __init__.pyi
-    with open(builder_pyi_file, "a") as file:
-        file.write(
-            block_template.replace("{{name}}", name)
-            .replace("{{properties}}", properties)
-            .replace("{{doc_arguments}}", doc_arguments)
-        )
+        # Append properties doc to element doc (once ordered)
+        for property in property_list:
+            property_doc = build_doc(name, property)
+            properties_doc += property_doc
+        if (len(properties_decl) > 1):
+            properties_decl.insert(1, "*")
+        # Append element to __init__.pyi
+        with open(builder_pyi_file, "a") as file:
+            n = "n" if name[0] in ["a", "e", "i", "o"] else ""
+            file.write(
+                element_template.replace("{{name}}", name).replace("{{n}}", n)
+                .replace("{{base_class}}", base_class)
+                .replace("{{properties_decl}}", ", ".join(properties_decl))
+                .replace("{{properties_doc}}", properties_doc)
+            )
+
+
+
+generate_elements("controls", "Control")
+generate_elements("blocks", "Block")
 
 os.system(f"pipenv run isort {gui_pyi_file}")
 os.system(f"pipenv run black {gui_pyi_file}")