diff --git a/docs/release-notes/index.md b/docs/release-notes/index.md
index 5082247c2..1d4bf3837 100644
--- a/docs/release-notes/index.md
+++ b/docs/release-notes/index.md
@@ -29,6 +29,15 @@ This is the list of changes to Taipy releases as they were published.
[`taipy-templates` 4.0](https://pypi.org/project/taipy-templates/4.0.0/), and
[`taipy-rest` 4.0](https://pypi.org/project/taipy-rest/4.0.0/) packages.
+## Known issues
+
+
taipy-gui 4.0.0
+
+- The [**-H**](../userman/advanced_features/configuration/gui-config.md#p-port) short command line
+ option for setting the server hostname is broken.
+ Please use the full **--host** option instead.
+ This issue will be fixed in the next technical release for Taipy GUI.
+
## New Features
taipy 4.0.0
@@ -262,6 +271,10 @@ additional features.
## New Features
+- Authentication now supports
+ [Microsoft Entra ID](https://www.microsoft.com/en-us/security/business/identity-access/microsoft-entra-id)
+ including SSO and GUI integration.
+ TODO - Documentation is in progress.
- Support for [Polars DataFrame Library](https://docs.pola.rs/).
Tabular data nodes (`CSVDataNode^`, `ParquetDataNode^`, `ExcelDataNode^`, `SQLTableDataNode^`,
and `SQLDataNode^`) can now expose the data as Polars objects. They all support
diff --git a/docs/tutorials/articles/using_tables/index.md b/docs/tutorials/articles/using_tables/index.md
index 52c07d0f4..b478269be 100644
--- a/docs/tutorials/articles/using_tables/index.md
+++ b/docs/tutorials/articles/using_tables/index.md
@@ -103,8 +103,8 @@ You can customize the style of a table in Taipy using two properties:
1. *class_name*: This property allows you to apply a CSS class to the entire table.
-2. *style*: With this property, you can apply a CSS class to specific rows, which you specify in
- Python.
+2. *row_class_name*: With this property, you can apply a CSS class to specific rows, which you
+ specify in Python.
### Property 1: class_name
@@ -134,17 +134,17 @@ script. For example, if your Taipy application code is in `main.py`, your CSS co
`main.css` in the same directory. You can find more details about this
[here](../../../userman/gui/styling/index.md#style-sheets).
-### Property 2: style
+### Property 2: row_class_name
-To have more precise control over our styling, we can utilize the style property of tables
-to assign CSS classes to individual rows in the table. For instance, we can assign a
-user-defined **highlight-row** CSS class to rows where the **Category** column is **Dessert** to
+To have more precise control over our styling, we can utilize the *row_class_name* property of
+the table control to assign CSS classes to individual rows in the table. For instance, we can assign
+a user-defined **highlight-row** CSS class to rows where the **Category** column is **Dessert** to
give them a yellow background.
![Property style](images/using_tables_3.png){width=50% : .tp-image-border }
-The style property accepts a function. This function is applied to each row of the table and
-returns a string specifying the CSS class to be used for that particular row. To create the
+The *row_class_name* property accepts a function. This function is applied to each row of the table
+and returns a string specifying the CSS class to be used for that particular row. To create the
table mentioned above, you can use the following code:
```python title="main.py"
@@ -153,11 +153,11 @@ def table_style(state, index, row):
table_properties = {
"class_name": "rows-bordered rows-similar", # optional
- "style": table_style,
+ "row_class_name": table_style,
}
main_md = Markdown("<|{food_df}|table|properties=table_properties|>")
-# or Markdown("<|{food_df}|table|class_name=rows-bordered rows-similar|style=table_style|>")
+# or Markdown("<|{food_df}|table|class_name=rows-bordered rows-similar|row_class_name=table_style|>")
```
```css
@@ -170,25 +170,25 @@ main_md = Markdown("<|{food_df}|table|properties=table_properties|>")
In our code, we also made use of the *class_name* property mentioned earlier, and we applied
two Stylekit table classes: **rows-bordered** and **rows-similar**. The **rows-similar** class
removes the default 0.5 opacity from odd rows. While it wasn't necessary, using it does enhance
-the table's appearance when applying our **highlight-row** style.
+the table's appearance when applying our **highlight-row** CSS class.
## Modifying Data
Tables offer various properties for modifying data within the table. Notably, the *on_edit*,
-*on_add*, and *on_delete* properties can receive user-defined **callback** functions. This
-function is executed when you interact with the table, but it only appears when you specify the
-relevant data modification property.
+*on_add*, and *on_delete* properties can receive user-defined **callback** functions. These
+functions are executed when you interact with the table. The interaction element appear when the
+table is explicitly defined as *editable*.
-Taipy doesn't come with default functions for these properties, so we'll define each function
-ourselves to match our specific needs. We're also including the
-[notify](../../../userman/gui/notifications.md) function within our data modification callback
+Although Taipy comes with default callback function implementation for these properties, we will
+define each function ourselves to match our specific needs. We're also including the
+[*notify()*](../../../userman/gui/notifications.md) function within our data modification callback
functions to send notifications to the user about their changes.
## Editing (*on_edit*)
-When the *on_edit* property is used, new buttons with a pencil icon are added to each cell.
-Clicking it allows the user to modify the value of that cell,
-then clicking the tick triggers the callback function:
+When the *editable* property is used, new buttons with a pencil icon are added to each cell.
+Clicking it allows the user to modify the value of that cell. If *on_edit* is set to a callback
+function, then clicking the tick triggers the callback function:
![Editing](images/tables-on_edit.gif){width=50% : .tp-image-border }
@@ -208,7 +208,7 @@ def food_df_on_edit(state, var_name, payload):
state.food_df = new_food_df
notify(state, "I", f"Edited value from '{old_value}' to '{value}'. (index '{index}', column '{col}')")
-main_md = Markdown("<|{food_df}|table|on_edit=food_df_on_edit|>")
+main_md = Markdown("<|{food_df}|table|editable|on_edit=food_df_on_edit|>")
```
The table documentation provides more information on the function signature which is slightly
@@ -221,9 +221,9 @@ we instead create a copy of the DataFrame, modify the relevant cell, then assign
## Adding (*on_add*)
-Adding and deleting are quite similar to editing. When you specify the *on_add* property, a
-`button` with a 'plus' icon is included, and when clicked, it triggers the defined *on_add*
-callback function.
+Adding and deleting are quite similar to editing. When *editable* is True, a
+`button` with a 'plus' icon is included, and when clicked, it triggers the callback function
+speficied in the *on_add* property.
![Adding](images/tables-on_add.gif){width=50% : .tp-image-border }
@@ -236,7 +236,7 @@ def food_df_on_add(state, var_name, payload):
notify(state, "S", f"Added a new row.")
-main_md = Markdown("<|{food_df}|table|on_add=food_df_on_add|>")
+main_md = Markdown("<|{food_df}|table|editable|on_add=food_df_on_add|>")
```
This code simply adds a new empty row to the top of the table (DataFrame).
@@ -258,7 +258,7 @@ def food_df_on_delete(state, var_name, payload):
state.food_df = state.food_df.drop(index=index)
notify(state, "E", f"Deleted row at index '{index}'")
-main_md = Markdown("<|{food_df}|table|on_delete=food_df_on_delete|>")
+main_md = Markdown("<|{food_df}|table|editable|on_delete=food_df_on_delete|>")
```
## Complete Code
@@ -311,6 +311,7 @@ if __name__ == "__main__":
table_properties = {
"class_name": "rows-bordered",
+ "editable": True,
"filter": True,
"on_edit": food_df_on_edit,
"on_delete": food_df_on_delete,
diff --git a/mkdocs.yml_template b/mkdocs.yml_template
index df5c725f4..fc13b22fe 100644
--- a/mkdocs.yml_template
+++ b/mkdocs.yml_template
@@ -387,6 +387,8 @@ extra:
# to enable disqus, uncomment the following and put your disqus id below
# disqus: disqus_id
generator: false
+exclude_docs: |
+ *.md_template
# uncomment the following and put your google tracking id below to enable GA
#google_analytics:
#- UA-xxx
diff --git a/tools/fetch_source_files.py b/tools/fetch_source_files.py
index 547c9210c..a2d9f8e49 100644
--- a/tools/fetch_source_files.py
+++ b/tools/fetch_source_files.py
@@ -3,7 +3,11 @@
import shutil
import subprocess
-from _fetch_source_file import CLI, GitContext, read_doc_version_from_mkdocs_yml_template_file
+from _fetch_source_file import (
+ CLI,
+ GitContext,
+ read_doc_version_from_mkdocs_yml_template_file,
+)
# Assuming this script is in taipy-doc/tools
TOOLS_PATH = os.path.dirname(os.path.abspath(__file__))
@@ -18,6 +22,9 @@
OPTIONAL_PACKAGES = {"gui": ["pyarrow", "pyngrok", "python-magic", "python-magic-bin"]}
+# Ecosystem offering may have a different version than the main Taipy version
+VERSION_MAP = {"designer": {"4.0": "1.2"}}
+
args = CLI(os.path.basename(__file__), REPOS).get_args()
# Read version from mkdocs.yml template
@@ -25,7 +32,8 @@
# Gather version information for each repository
repo_defs = {
- repo if repo == "taipy" else f"taipy-{repo}": {"version": "local", "tag": None} for repo in REPOS + PRIVATE_REPOS
+ repo if repo == "taipy" else f"taipy-{repo}": {"version": "local", "tag": None}
+ for repo in REPOS + PRIVATE_REPOS
}
CATCH_VERSION_RE = re.compile(r"(^\d+\.\d+?)(?:(\.\d+)(\..*)?)?|develop|local$")
for version in args.version:
@@ -62,13 +70,28 @@
repo_defs[repo]["version"] = version
repo_defs[repo]["tag"] = tag
+# Remap version if necessary
+for repo, version_remap_desc in VERSION_MAP.items():
+ repo_desc = repo_defs.get(repo, None)
+ if repo_desc is None:
+ repo = f"taipy-{repo}"
+ repo_desc = repo_defs.get(repo, None)
+ if repo_desc and (
+ remapped_version := version_remap_desc.get(repo_desc["version"], None)
+ ):
+ repo_desc["version"] = remapped_version
+
# Test git, if needed
git_command = "git"
if args.no_pull and all(v["version"] == "local" for v in repo_defs.values()):
git_command = None
else:
git_path = shutil.which(git_command)
- if git_path is None or subprocess.run(f'"{git_path}" --version', shell=True, capture_output=True) is None:
+ if (
+ git_path is None
+ or subprocess.run(f'"{git_path}" --version', shell=True, capture_output=True)
+ is None
+ ):
raise IOError(f'Couldn\'t find command "{git_command}"')
git_command = git_path
@@ -91,14 +114,19 @@
elif version == "develop":
with GitContext(repo, PRIVATE_REPOS):
cmd = subprocess.run(
- f'"{git_path}" ls-remote -q -h {github_root}{repo}.git', shell=True, capture_output=True, text=True
+ f'"{git_path}" ls-remote -q -h {github_root}{repo}.git',
+ shell=True,
+ capture_output=True,
+ text=True,
)
if cmd.returncode:
if repo in PRIVATE_REPOS or repo[6:] in PRIVATE_REPOS:
repo_defs[repo]["skip"] = True
continue
else:
- raise SystemError(f"Problem with {repo}:\nOutput: {cmd.stdout}\nError: {cmd.stderr}")
+ raise SystemError(
+ f"Problem with {repo}:\nOutput: {cmd.stdout}\nError: {cmd.stderr}"
+ )
else:
with GitContext(repo, PRIVATE_REPOS):
cmd = subprocess.run(
@@ -112,9 +140,13 @@
repo_defs[repo]["skip"] = True
continue
else:
- raise SystemError(f"Couldn't query branches from {loggable_github_root}{repo}.")
+ raise SystemError(
+ f"Couldn't query branches from {loggable_github_root}{repo}."
+ )
if f"release/{version}\n" not in cmd.stdout:
- raise ValueError(f"No branch 'release/{version}' in repository '{repo}'.")
+ raise ValueError(
+ f"No branch 'release/{version}' in repository '{repo}'."
+ )
tag = repo_defs[repo]["tag"]
if tag:
cmd = subprocess.run(
@@ -161,6 +193,7 @@ def safe_rmtree(dir: str):
frontend_dir = os.path.join(ROOT_DIR, "taipy-fe")
+
# Fetch files
def move_files(repo: str, src_path: str):
# Read Pipfile dependency packages
@@ -171,7 +204,9 @@ def move_files(repo: str, src_path: str):
with open(pipfile_path, "r") as pipfile:
while True:
line = pipfile.readline()
- if str(line) == "" or (reading_packages and (not line.strip() or line[0] == "[")):
+ if str(line) == "" or (
+ reading_packages and (not line.strip() or line[0] == "[")
+ ):
break
line = line.strip()
if line == "[packages]":
@@ -181,7 +216,10 @@ def move_files(repo: str, src_path: str):
if match and not match.group(1).startswith("taipy"):
package = match.group(1).lower()
version = match.group(2)
- if repo_optional_packages is None or package not in repo_optional_packages:
+ if (
+ repo_optional_packages is None
+ or package not in repo_optional_packages
+ ):
if package in pipfile_packages:
versions = pipfile_packages[package]
if version in versions:
@@ -197,37 +235,55 @@ def move_files(repo: str, src_path: str):
for step_dir in [
step_dir
for step_dir in os.listdir(gs_dir)
- if step_dir.startswith("step_") and os.path.isdir(os.path.join(gs_dir, step_dir))
+ if step_dir.startswith("step_")
+ and os.path.isdir(os.path.join(gs_dir, step_dir))
]:
safe_rmtree(os.path.join(gs_dir, step_dir))
for step_dir in [
step_dir
for step_dir in os.listdir(src_path)
- if step_dir.startswith("step_") and os.path.isdir(os.path.join(src_path, step_dir))
+ if step_dir.startswith("step_")
+ and os.path.isdir(os.path.join(src_path, step_dir))
]:
- shutil.copytree(os.path.join(src_path, step_dir), os.path.join(gs_dir, step_dir))
+ shutil.copytree(
+ os.path.join(src_path, step_dir), os.path.join(gs_dir, step_dir)
+ )
safe_rmtree(os.path.join(gs_dir, "src"))
shutil.copytree(os.path.join(src_path, "src"), os.path.join(gs_dir, "src"))
- shutil.copy(os.path.join(src_path, "index.md"), os.path.join(gs_dir, "index.md"))
+ shutil.copy(
+ os.path.join(src_path, "index.md"), os.path.join(gs_dir, "index.md")
+ )
saved_dir = os.getcwd()
os.chdir(os.path.join(ROOT_DIR, "docs", "getting_started", repo[6:]))
subprocess.run(
- f"python {os.path.join(src_path, 'generate_notebook.py')}", shell=True, capture_output=True, text=True
+ f"python {os.path.join(src_path, 'generate_notebook.py')}",
+ shell=True,
+ capture_output=True,
+ text=True,
)
os.chdir(saved_dir)
elif repo == "taipy-designer":
- designer_doc_dir = os.path.join(ROOT_DIR, "docs", "userman", "ecosystem", "designer")
+ designer_doc_dir = os.path.join(
+ ROOT_DIR, "docs", "userman", "ecosystem", "designer"
+ )
safe_rmtree(designer_doc_dir)
src_documentation_dir = os.path.join(src_path, "documentation")
saved_dir = os.getcwd()
os.chdir(saved_dir)
subprocess.run(
- f"python {os.path.join(src_path, 'copy_examples.py')}", shell=True, capture_output=True, text=True
+ f"python {os.path.join(src_path, 'copy_examples.py')}",
+ shell=True,
+ capture_output=True,
+ text=True,
)
os.chdir(saved_dir)
- shutil.copytree(os.path.join(src_documentation_dir, "taipy_docs"), designer_doc_dir)
- shutil.copy(os.path.join(src_documentation_dir, "mkdocs_taipy.yml"),
- os.path.join(designer_doc_dir, "mkdocs.yml_template"))
+ shutil.copytree(
+ os.path.join(src_documentation_dir, "taipy_docs"), designer_doc_dir
+ )
+ shutil.copy(
+ os.path.join(src_documentation_dir, "mkdocs_taipy.yml"),
+ os.path.join(designer_doc_dir, "mkdocs.yml_template"),
+ )
else:
try:
@@ -243,7 +299,9 @@ def copy(item: str, src: str, dst: str, rel_path: str):
rel_path = f"{rel_path}/{item}"
for sub_item in os.listdir(full_src):
copy(sub_item, full_src, full_dst, rel_path)
- elif any(item.endswith(ext) for ext in [".py", ".pyi", ".json", ".ipynb"]):
+ elif any(
+ item.endswith(ext) for ext in [".py", ".pyi", ".json", ".ipynb"]
+ ):
if os.path.isfile(full_dst): # File exists - compare
with open(full_src, "r") as f:
src = f.read()
@@ -274,9 +332,17 @@ def copy(item: str, src: str, dst: str, rel_path: str):
if not os.path.isdir(frontend_dir):
os.mkdir(frontend_dir)
fe_src_dir = os.path.join(src_path, "frontend", "taipy-gui")
- shutil.copytree(os.path.join(fe_src_dir, "src"), os.path.join(frontend_dir, "src"))
- for f in [f for f in os.listdir(fe_src_dir) if f.endswith(".md") or f.endswith(".json")]:
- shutil.copy(os.path.join(fe_src_dir, f), os.path.join(frontend_dir, f))
+ shutil.copytree(
+ os.path.join(fe_src_dir, "src"), os.path.join(frontend_dir, "src")
+ )
+ for f in [
+ f
+ for f in os.listdir(fe_src_dir)
+ if f.endswith(".md") or f.endswith(".json")
+ ]:
+ shutil.copy(
+ os.path.join(fe_src_dir, f), os.path.join(frontend_dir, f)
+ )
finally:
pass
"""
@@ -286,7 +352,10 @@ def copy(item: str, src: str, dst: str, rel_path: str):
frontend_dir = os.path.join(ROOT_DIR, "taipy-fe")
if os.path.isdir(os.path.join(frontend_dir, "node_modules")):
- shutil.move(os.path.join(frontend_dir, "node_modules"), os.path.join(ROOT_DIR, "fe_node_modules"))
+ shutil.move(
+ os.path.join(frontend_dir, "node_modules"),
+ os.path.join(ROOT_DIR, "fe_node_modules"),
+ )
if os.path.isdir(os.path.join(frontend_dir)):
shutil.rmtree(frontend_dir)
@@ -300,7 +369,9 @@ def copy(item: str, src: str, dst: str, rel_path: str):
if not args.no_pull:
cwd = os.getcwd()
os.chdir(src_path)
- subprocess.run(f'"{git_path}" pull', shell=True, capture_output=True, text=True)
+ subprocess.run(
+ f'"{git_path}" pull', shell=True, capture_output=True, text=True
+ )
os.chdir(cwd)
print(f" Copying from {src_path}...", flush=True)
move_files(repo, src_path)
@@ -320,7 +391,12 @@ def copy(item: str, src: str, dst: str, rel_path: str):
# Checkout tag version
saved_dir = os.getcwd()
os.chdir(clone_dir)
- subprocess.run(f'"{git_path}" checkout {tag}', shell=True, capture_output=True, text=True)
+ subprocess.run(
+ f'"{git_path}" checkout {tag}',
+ shell=True,
+ capture_output=True,
+ text=True,
+ )
os.chdir(saved_dir)
move_files(repo, clone_dir)
@@ -338,8 +414,13 @@ def handleRemoveReadonly(func, path, exc):
shutil.rmtree(clone_dir, onerror=handleRemoveReadonly)
-if os.path.isdir(os.path.join(ROOT_DIR, "fe_node_modules")) and os.path.isdir(os.path.join(frontend_dir)):
- shutil.move(os.path.join(ROOT_DIR, "fe_node_modules"), os.path.join(frontend_dir, "node_modules"))
+if os.path.isdir(os.path.join(ROOT_DIR, "fe_node_modules")) and os.path.isdir(
+ os.path.join(frontend_dir)
+):
+ shutil.move(
+ os.path.join(ROOT_DIR, "fe_node_modules"),
+ os.path.join(frontend_dir, "node_modules"),
+ )
# Manually add the taipy.run() function.
# TODO: Automate this, grabbing the function from the 'taipy' repository,
@@ -395,10 +476,16 @@ def run(*services: t.Union[Gui, Rest, Orchestrator], **kwargs) -> t.Optional[t.U
version = list(versions.keys())[0]
if package == "modin":
# Remove 'extras' from modin package requirements
- version = re.sub(r"\{\s*extras.*?,\s*version\s*=\s*(.*?)\s*}", r"\1", version)
+ version = re.sub(
+ r"\{\s*extras.*?,\s*version\s*=\s*(.*?)\s*}",
+ r"\1",
+ version,
+ )
new_pipfile.write(f"{package} = {version}\n")
if package not in legacy_pipfile_packages:
- pipfile_changes.append(f"Package '{package}' added ({version})")
+ pipfile_changes.append(
+ f"Package '{package}' added ({version})"
+ )
elif legacy_pipfile_packages[package] != version:
pipfile_changes.append(
f"Package '{package}' version changed from "
diff --git a/tools/postprocess.py b/tools/postprocess.py
index e11fc6864..211eac940 100644
--- a/tools/postprocess.py
+++ b/tools/postprocess.py
@@ -31,7 +31,7 @@
def define_env(env):
"""
- Mandatory to make this a proper MdDocs macro
+ Mandatory to make this a proper MkDocs macro
"""
match = re.search(r"/en/(develop|(?:release-(\d+\.\d+)))/$", env.conf["site_url"])
env.conf["branch"] = (
@@ -108,24 +108,22 @@ def remove_dummy_h3(content: str, ids: Dict[str, str]) -> str:
return content
-def create_navigation_buttons(site_url: str) -> str:
- def create_button(
- site_url: str, label: str, relative_url: str, class_name: str, group: str = ""
- ) -> str:
+def create_navigation_buttons() -> str:
+ def create_button(label: str, path: str, class_name: str, group: str = "") -> str:
gclass = " .tp-nav-button-group_element" if group else ""
html = f"""
-
+