From 39f5a454c8b716aab80d539b8f92ddb528952f77 Mon Sep 17 00:00:00 2001 From: Fabien Lelaquais <86590727+FabienLelaquais@users.noreply.github.com> Date: Sat, 10 Aug 2024 12:23:21 +0200 Subject: [PATCH] Improve content of generated pyi for the Page Builder API (#1653) - Change Page Builder API pyi generation script - Updated actions - Details in element examples --- .github/actions/gui-test/pyi/action.yml | 2 +- .../build-and-release-single-package.yml | 2 +- .github/workflows/build-and-release.yml | 2 +- doc/gui/examples/controls/chat-discuss.py | 3 +- doc/gui/examples/controls/metric-color-map.py | 30 +-- doc/gui/examples/controls/metric-layout.py | 2 +- doc/gui/examples/controls/metric-simple.py | 9 +- taipy/gui/viselements.json | 122 +++++----- tools/gui/builder/block.txt | 8 - tools/gui/builder/control.txt | 8 - tools/gui/generate_pyi.py | 218 ++++++++++-------- 11 files changed, 209 insertions(+), 197 deletions(-) delete mode 100644 tools/gui/builder/block.txt delete mode 100644 tools/gui/builder/control.txt diff --git a/.github/actions/gui-test/pyi/action.yml b/.github/actions/gui-test/pyi/action.yml index a4ddddfeab..e80c9e7912 100644 --- a/.github/actions/gui-test/pyi/action.yml +++ b/.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 diff --git a/.github/workflows/build-and-release-single-package.yml b/.github/workflows/build-and-release-single-package.yml index 2a4a7cac57..87159a7e29 100644 --- a/.github/workflows/build-and-release-single-package.yml +++ b/.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' diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index 978cd924cf..76fdb9d605 100644 --- a/.github/workflows/build-and-release.yml +++ b/.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' diff --git a/doc/gui/examples/controls/chat-discuss.py b/doc/gui/examples/controls/chat-discuss.py index 858962fa94..180ba3da71 100644 --- a/doc/gui/examples/controls/chat-discuss.py +++ b/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") diff --git a/doc/gui/examples/controls/metric-color-map.py b/doc/gui/examples/controls/metric-color-map.py index c33c5979fe..2735bbcc05 100644 --- a/doc/gui/examples/controls/metric-color-map.py +++ b/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() diff --git a/doc/gui/examples/controls/metric-layout.py b/doc/gui/examples/controls/metric-layout.py index 33c220901d..09451a8dd0 100644 --- a/doc/gui/examples/controls/metric-layout.py +++ b/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 = { diff --git a/doc/gui/examples/controls/metric-simple.py b/doc/gui/examples/controls/metric-simple.py index 0448f89428..ed4793a1fb 100644 --- a/doc/gui/examples/controls/metric-simple.py +++ b/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() diff --git a/taipy/gui/viselements.json b/taipy/gui/viselements.json index c0fcdbbb9b..9b392d6d4a 100644 --- a/taipy/gui/viselements.json +++ b/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.
The parameters of that function are all optional:\n", + "doc": "The name of a function that is triggered when the button is pressed.
The parameters of that function are all optional:\n", "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 step 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.
If this slider is based on a lov then this property can be set to the lov element.
This value can also hold an array of numbers to indicate that the slider reflects a range (within the [min,max] domain) defined by several knobs that the user can set independently.
If this slider is based on a lov 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.
This is ignored when lov is defined." }, { "name": "max", - "type": "int|float", + "type": "Union[int,float]", "default_value": "100", "doc": "The maximum value.
This is ignored when lov 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 (max-min) being divisible by step.
This property is ignored when lov is defined." + "doc": "The step value, which is the gap between two consecutive values the slider set. It is a good practice to have (max-min) being divisible by step.
This property is ignored when lov 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.
If set to True, this slider uses the labels of the lov if there are any.
If set to a dictionary, the slider uses the dictionary keys as a lov 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.
The function receives three parameters:\n", + "doc": "The callback function that is invoked when the visible part of the x axis changes.
The function receives three parameters:\n", "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": "All columns", "doc": "The list of column names\n" }, @@ -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.
See line for details.
If the value is a string, it must be a dash type or pattern (see dash style of lines 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": "shows all columns when empty", "doc": "The list of the column names to display.\n" }, @@ -885,12 +885,12 @@ }, { "name": "lov[column_name]", - "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.
If this is a string, a URL, or a file, then the content is read from this source.
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 data_url_max_size parameter of the application configuration (which is set to 50kB by default):\nIf 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 content is None).
All the parameters of that function are optional:\n", + "doc": "The name of a function that is triggered when the download is terminated (or on user action if content is None).
All the parameters of that function are optional:\n", "signature": [ [ "state", @@ -1052,7 +1052,7 @@ { "name": "on_action", "type": "Callback", - "doc": "The name of the function that will be triggered.
All the parameters of that function are optional:\n", + "doc": "The name of the function that will be triggered.
All the parameters of that function are optional:\n", "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.
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 data_url_max_size parameter of the application configuration (which is set to 50kB by default):\n" }, { @@ -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.
All the parameters of that function are optional:\n", + "doc": "The name of a function that is triggered when the user clicks on the image.
All the parameters of that function are optional:\n", "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 lov into a tuple(id:str, label:str|Icon)." + "doc": "The function that transforms an element of lov into a tuple(id:str, label:Union[str,Icon])." }, { "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.

All the parameters of that function are optional:\n", + "doc": "The name of the function that is triggered when a menu option is selected.

All the parameters of that function are optional:\n", "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.

All the parameters of that function are optional:\n
When the button is pressed, and if this property is not set, Taipy will try to find a callback function called on_login() and invoke it with the parameters listed above.", + "doc": "The name of the function that is triggered when the dialog button is pressed.

All the parameters of that function are optional:\n
When the button is pressed, and if this property is not set, Taipy will try to find a callback function called on_login() 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 section on List of Values 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.
The parameters of that function are all optional:\n", + "doc": "Name of a function triggered when a button is pressed.
The parameters of that function are all optional:\n", "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 on_action callback (that index is -1 for the close 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).
All parameters of that function are optional:\n
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).
All parameters of that function are optional:\n
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 lov into a tuple(id:str, label:str|Icon)." + "doc": "The function that transforms an element of lov into a tuple(id:str, label:Union[str,Icon])." }, { "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.
This should not be defined if page 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.
The parameters of that function are all optional:\n", + "doc": "Name of a function that is triggered when a specific key is pressed.
The parameters of that function are all optional:\n", "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.
These class names are added to the default taipy-<element_type> class name." }, { "name": "hover_text", "type": "dynamic(str)", - "default_value": "None", "doc": "The information that is displayed when the user hovers over this element." } ] diff --git a/tools/gui/builder/block.txt b/tools/gui/builder/block.txt deleted file mode 100644 index e37bde2246..0000000000 --- a/tools/gui/builder/block.txt +++ /dev/null @@ -1,8 +0,0 @@ - -class {{name}}(_Block): - _ELEMENT_NAME: str - def __init__(self, {{properties}}) -> None: - """### Arguments: -{{doc_arguments}} - """ - ... diff --git a/tools/gui/builder/control.txt b/tools/gui/builder/control.txt deleted file mode 100644 index 7d0a2ce5e8..0000000000 --- a/tools/gui/builder/control.txt +++ /dev/null @@ -1,8 +0,0 @@ - -class {{name}}(_Control): - _ELEMENT_NAME: str - def __init__(self, {{properties}}) -> None: - """### Arguments: -{{doc_arguments}} - """ - ... diff --git a/tools/gui/generate_pyi.py b/tools/gui/generate_pyi.py index 531b8d1ca6..8fd235142f 100644 --- a/tools/gui/generate_pyi.py +++ b/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 " 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"^(.*\.)(
|\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("", 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}")