-
Notifications
You must be signed in to change notification settings - Fork 2k
Fix: table delete callback error in Taipy GUI #2768
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Conversation
|
@ibrahim307le Thanks a lot for your input. |
|
Thanks for this opportunity, and I look forward to updating the changes needed for this. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Every data access has to be made through the data accessors.
Gui should have no direct dependency to pandas
I don't see any link with the referenced issue in the description.
If there's no issue connected this PR will be rejected
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR attempts to fix a table delete callback error in Taipy GUI by introducing new helper methods for variable access and modifying the table_on_delete and table_on_add implementations. However, the implementation has several critical issues that break existing functionality.
Key Changes:
- Added
_get_var_from_name()and_set_var_in_name()helper methods for variable retrieval and storage - Replaced the data accessor pattern in
table_on_delete()andtable_on_add()with direct pandas DataFrame manipulation - Added pandas import at module level
- Added a basic test for
table_on_delete()
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 11 comments.
| File | Description |
|---|---|
| taipy/gui/gui.py | Introduced helper methods for variable access and modified table callback implementations, breaking the existing data accessor architecture |
| taipy/gui/test_table.py | Added a test that checks internal implementation details rather than actual functionality |
Comments suppressed due to low confidence (1)
taipy/gui/gui.py:1969
- Variable var is not used.
var = self._get_var_from_name(var_name)
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
taipy/gui/gui.py
Outdated
| """Set or update a variable in internal store, GUI state, or globals.""" | ||
| try: | ||
| if not hasattr(self, "_variables"): | ||
| self._variables = {} | ||
|
|
||
| self._variables[var_name] = value | ||
|
|
||
| if hasattr(self, "state") and var_name in getattr(self, "state", {}): | ||
| self.state[var_name] = value | ||
| return | ||
|
|
||
| if hasattr(self, var_name): | ||
| setattr(self, var_name, value) | ||
| return | ||
|
|
||
| globals()[var_name] = value | ||
|
|
||
| except Exception as e: | ||
| _warn(f"Failed to set variable '{var_name}'.", e) | ||
|
|
Copilot
AI
Nov 24, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The logic in _set_var_in_name always sets the value in self._variables (line 1892) AND then tries to set it elsewhere. This creates data inconsistency issues. If a variable exists in the state, it will be stored in both self._variables and self.state, potentially leading to synchronization issues. The method should have a clear priority order and only store in one location.
| """Set or update a variable in internal store, GUI state, or globals.""" | |
| try: | |
| if not hasattr(self, "_variables"): | |
| self._variables = {} | |
| self._variables[var_name] = value | |
| if hasattr(self, "state") and var_name in getattr(self, "state", {}): | |
| self.state[var_name] = value | |
| return | |
| if hasattr(self, var_name): | |
| setattr(self, var_name, value) | |
| return | |
| globals()[var_name] = value | |
| except Exception as e: | |
| _warn(f"Failed to set variable '{var_name}'.", e) | |
| """Set or update a variable in the highest-priority location: state, attribute, or internal store.""" | |
| try: | |
| if not hasattr(self, "_variables"): | |
| self._variables = {} | |
| # Priority 1: state | |
| if hasattr(self, "state") and var_name in getattr(self, "state", {}): | |
| self.state[var_name] = value | |
| return | |
| # Priority 2: attribute | |
| if hasattr(self, var_name): | |
| setattr(self, var_name, value) | |
| return | |
| # Priority 3: internal store | |
| self._variables[var_name] = value | |
| except Exception as e: | |
| _warn(f"Failed to set variable '{var_name}'.", e) |
taipy/gui/gui.py
Outdated
| """ | ||
| try: | ||
| setattr(state, var_name, self._get_accessor().on_delete(getattr(state, var_name), payload)) | ||
| var = self._get_var_from_name(var_name) |
Copilot
AI
Nov 24, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The variable var is assigned but never used. This line (1969) duplicates the assignment on line 1970 and should be removed.
| var = self._get_var_from_name(var_name) |
| #This function sets the value of a specific cell in the tabular dataset stored in | ||
| *var_name*, typically bound to the *data* property of a table control. |
Copilot
AI
Nov 24, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment formatting is broken. The # should be indented properly on its own line with triple quotes, not inline. This should be formatted as:
"""Default implementation of the `on_edit` callback for tables.
This function sets the value of a specific cell in the tabular dataset stored in
*var_name*, typically bound to the *data* property of a table control.| #This function sets the value of a specific cell in the tabular dataset stored in | |
| *var_name*, typically bound to the *data* property of a table control. | |
| This function sets the value of a specific cell in the tabular dataset stored in | |
| *var_name*, typically bound to the *data* property of a table control. |
taipy/gui/gui.py
Outdated
| def _get_var_from_name(self, var_name: str): | ||
| """Retrieve a variable from internal store, GUI state, or globals.""" | ||
| try: | ||
|
|
||
| if not hasattr(self, "_variables"): | ||
| self._variables = {} | ||
|
|
||
| if var_name in self._variables: | ||
| return self._variables[var_name] | ||
|
|
||
| if hasattr(self, "state") and var_name in getattr(self, "state", {}): | ||
| return self.state[var_name] | ||
|
|
||
| if hasattr(self, var_name): | ||
| return getattr(self, var_name) | ||
|
|
||
| if var_name in globals(): | ||
| return globals()[var_name] | ||
|
|
||
| except Exception as e: | ||
| _warn(f"Error while retrieving variable '{var_name}'.", e) | ||
| return None | ||
|
|
||
| _warn(f"Variable '{var_name}' not found.") | ||
| return None | ||
|
|
||
| def _set_var_in_name(self, var_name: str, value): | ||
| """Set or update a variable in internal store, GUI state, or globals.""" | ||
| try: | ||
| if not hasattr(self, "_variables"): | ||
| self._variables = {} | ||
|
|
||
| self._variables[var_name] = value | ||
|
|
||
| if hasattr(self, "state") and var_name in getattr(self, "state", {}): | ||
| self.state[var_name] = value | ||
| return | ||
|
|
||
| if hasattr(self, var_name): | ||
| setattr(self, var_name, value) | ||
| return | ||
|
|
||
| globals()[var_name] = value | ||
|
|
||
| except Exception as e: | ||
| _warn(f"Failed to set variable '{var_name}'.", e) | ||
|
|
Copilot
AI
Nov 24, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The _get_var_from_name and _set_var_in_name methods completely ignore the state parameter that is passed to table_on_delete and table_on_add. These methods should use state (via getattr(state, var_name) and setattr(state, var_name, value)) instead of implementing a custom variable lookup/storage mechanism. The State object is the proper way to access and modify variables in Taipy GUI callbacks.
| def _get_var_from_name(self, var_name: str): | |
| """Retrieve a variable from internal store, GUI state, or globals.""" | |
| try: | |
| if not hasattr(self, "_variables"): | |
| self._variables = {} | |
| if var_name in self._variables: | |
| return self._variables[var_name] | |
| if hasattr(self, "state") and var_name in getattr(self, "state", {}): | |
| return self.state[var_name] | |
| if hasattr(self, var_name): | |
| return getattr(self, var_name) | |
| if var_name in globals(): | |
| return globals()[var_name] | |
| except Exception as e: | |
| _warn(f"Error while retrieving variable '{var_name}'.", e) | |
| return None | |
| _warn(f"Variable '{var_name}' not found.") | |
| return None | |
| def _set_var_in_name(self, var_name: str, value): | |
| """Set or update a variable in internal store, GUI state, or globals.""" | |
| try: | |
| if not hasattr(self, "_variables"): | |
| self._variables = {} | |
| self._variables[var_name] = value | |
| if hasattr(self, "state") and var_name in getattr(self, "state", {}): | |
| self.state[var_name] = value | |
| return | |
| if hasattr(self, var_name): | |
| setattr(self, var_name, value) | |
| return | |
| globals()[var_name] = value | |
| except Exception as e: | |
| _warn(f"Failed to set variable '{var_name}'.", e) | |
| def _get_var_from_name(self, state: State, var_name: str): | |
| """Retrieve a variable from the provided State object.""" | |
| try: | |
| return getattr(state, var_name, None) | |
| except Exception as e: | |
| _warn(f"Error while retrieving variable '{var_name}' from state.", e) | |
| return None | |
| _warn(f"Variable '{var_name}' not found in state.") | |
| return None | |
| def _set_var_in_name(self, state: State, var_name: str, value): | |
| """Set or update a variable in the provided State object.""" | |
| try: | |
| setattr(state, var_name, value) | |
| except Exception as e: | |
| _warn(f"Failed to set variable '{var_name}' in state.", e) |
taipy/gui/gui.py
Outdated
| setattr(self, var_name, value) | ||
| return | ||
|
|
||
| globals()[var_name] = value |
Copilot
AI
Nov 24, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Modifying global variables directly (line 1902) is a dangerous practice that can lead to unpredictable behavior and side effects. This violates best practices for state management in Python applications. Variables should be managed through the proper State object mechanism rather than manipulating globals.
taipy/gui/gui.py
Outdated
| import json | ||
| import math | ||
| import os | ||
| import pandas as pd |
Copilot
AI
Nov 24, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The pandas import is added at the module level but the code should handle cases where pandas might not be available. The existing codebase uses a data accessor pattern that abstracts away pandas-specific operations. Adding a direct pandas dependency here could cause issues if pandas is not installed or in environments where it's optional.
| import pandas as pd |
taipy/gui/gui.py
Outdated
| var = self._get_var_from_name(var_name) | ||
| df = self._get_var_from_name(var_name) | ||
| if isinstance(df, pd.DataFrame): | ||
| idx = payload.get("index") | ||
| if idx is None: | ||
| _warn("No index provided for deletion.") | ||
| return | ||
| try: | ||
| df = df.drop(df.index[int(idx)]) | ||
| df.reset_index(drop=True, inplace=True) | ||
| self._set_var_in_name(var_name, df) | ||
| except (ValueError, IndexError, KeyError): | ||
| if idx in df.index: | ||
| df = df.drop(idx) | ||
| df.reset_index(drop=True, inplace=True) | ||
| self._set_var_in_name(var_name, df) | ||
| else: | ||
| _warn(f"Row '{idx}' not found in DataFrame.") | ||
| else: | ||
| _warn(f"Variable '{var_name}' is not a DataFrame.") |
Copilot
AI
Nov 24, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new implementation bypasses the existing data accessor architecture (self._get_accessor()) which handles different data types beyond pandas DataFrames. The original implementation used self._get_accessor().on_delete(getattr(state, var_name), payload) which properly delegates to type-specific handlers. This change breaks support for non-DataFrame data types that were previously supported through the accessor pattern.
| var = self._get_var_from_name(var_name) | |
| df = self._get_var_from_name(var_name) | |
| if isinstance(df, pd.DataFrame): | |
| idx = payload.get("index") | |
| if idx is None: | |
| _warn("No index provided for deletion.") | |
| return | |
| try: | |
| df = df.drop(df.index[int(idx)]) | |
| df.reset_index(drop=True, inplace=True) | |
| self._set_var_in_name(var_name, df) | |
| except (ValueError, IndexError, KeyError): | |
| if idx in df.index: | |
| df = df.drop(idx) | |
| df.reset_index(drop=True, inplace=True) | |
| self._set_var_in_name(var_name, df) | |
| else: | |
| _warn(f"Row '{idx}' not found in DataFrame.") | |
| else: | |
| _warn(f"Variable '{var_name}' is not a DataFrame.") | |
| self._get_accessor().on_delete(getattr(state, var_name), payload) |
taipy/gui/gui.py
Outdated
| df = self._get_var_from_name(var_name) | ||
| if isinstance(df, pd.DataFrame): | ||
| new_row = payload.get("row", {}) | ||
| df = pd.concat([df, pd.DataFrame([new_row], columns=df.columns)], ignore_index=True) | ||
| self._set_var_in_name(var_name, df) | ||
| else: | ||
| _warn(f"Variable '{var_name}' is not a DataFrame.") |
Copilot
AI
Nov 24, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new implementation bypasses the existing data accessor architecture (self._get_accessor()) which handles different data types beyond pandas DataFrames. The original implementation used self._get_accessor().on_add(getattr(state, var_name), payload, new_row) which properly delegates to type-specific handlers. This change breaks support for non-DataFrame data types and ignores the new_row parameter that was part of the function signature.
| df = self._get_var_from_name(var_name) | |
| if isinstance(df, pd.DataFrame): | |
| new_row = payload.get("row", {}) | |
| df = pd.concat([df, pd.DataFrame([new_row], columns=df.columns)], ignore_index=True) | |
| self._set_var_in_name(var_name, df) | |
| else: | |
| _warn(f"Variable '{var_name}' is not a DataFrame.") | |
| setattr(state, var_name, self._get_accessor().on_add(getattr(state, var_name), payload, new_row)) |
taipy/gui/gui.py
Outdated
| df = df.drop(df.index[int(idx)]) | ||
| df.reset_index(drop=True, inplace=True) | ||
| self._set_var_in_name(var_name, df) | ||
| except (ValueError, IndexError, KeyError): | ||
| if idx in df.index: | ||
| df = df.drop(idx) | ||
| df.reset_index(drop=True, inplace=True) | ||
| self._set_var_in_name(var_name, df) | ||
| else: | ||
| _warn(f"Row '{idx}' not found in DataFrame.") |
Copilot
AI
Nov 24, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The delete logic tries two different approaches with exception handling, but the proper implementation (as seen in pandas_data_accessor.py line 562) uses df.drop(self._get_index_value(payload.get("index", 0))) directly. The new implementation's fallback logic (lines 1980-1986) is overly complex and doesn't handle the case where the index is positional vs. label-based correctly. The original accessor implementation handles this properly through _get_index_value().
| df = df.drop(df.index[int(idx)]) | |
| df.reset_index(drop=True, inplace=True) | |
| self._set_var_in_name(var_name, df) | |
| except (ValueError, IndexError, KeyError): | |
| if idx in df.index: | |
| df = df.drop(idx) | |
| df.reset_index(drop=True, inplace=True) | |
| self._set_var_in_name(var_name, df) | |
| else: | |
| _warn(f"Row '{idx}' not found in DataFrame.") | |
| index_value = self._get_index_value(idx) | |
| df = df.drop(index_value) | |
| df.reset_index(drop=True, inplace=True) | |
| self._set_var_in_name(var_name, df) | |
| except (ValueError, IndexError, KeyError): | |
| _warn(f"Row '{idx}' not found in DataFrame.") |
| # Mock a variable in case Taipy uses an internal dict | ||
| if not hasattr(gui, "_variables"): | ||
| gui._variables = {} | ||
| gui._variables["dummy"] = "mocked_value" |
Copilot
AI
Nov 24, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing import for pandas in the test file. The test mocks a value as "mocked_value" (a string) but table_on_delete expects a pandas DataFrame (line 1971 checks isinstance(df, pd.DataFrame)). This test will always take the warning path and never actually test the delete functionality.
|
Hi @ibrahim307le |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We cannot accept this PR as it is.
You need to take the comments into consideration.
- Python test should be in the tests directory.
- pandas should not be used in gui.py
- the changes to setup.py or pyproject.toml are not relevant
- the branch is not up-to-date
Without real changes, this PR will be closed
What type of PR is this? (Check all that apply)
Description
Provide a clear and concise description of the changes introduced in this PR.
Related Tickets & Documents
For pull requests that relate to or close an issue, please include them below.
Following [GitHub's guidance on linking issues] (https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue) helps with automation.
Example:
Closes docs: convert markdown templates to issue forms #1234
Related to #5678
Related Issue
Closes
How to reproduce the issue
Provide step-by-step instructions to reproduce the issue or test the feature.
Backporting
Specify any branches or releases this change needs to be backported to.
This change should be backported to:
3.0
3.1
4.0
develop
Checklist
We encourage keeping the code coverage percentage at 80% or above.
If not, explain why:
If not, explain why:
If not, explain why:
If not, explain why: