Skip to content
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

Feature:Added a function in gui class to support dev callback #457 #2028

Open
wants to merge 29 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
50dfe83
Feature:Added a function in gui class to support dev callback for uns…
Ujj1225 Oct 11, 2024
0ff9901
Bug:on_invalid_data_callback modified to support None type
Ujj1225 Oct 13, 2024
15f6e8d
Bug:Removed trailing whitespaces
Ujj1225 Oct 13, 2024
66428b8
Format: Removing trailing whitespace
Ujj1225 Oct 13, 2024
752cbf2
Merge branch 'develop' into feature/#457callbackToChangeUnsupportedDa…
Ujj1225 Oct 14, 2024
449c304
Formatting of on_invalid_data changed
Ujj1225 Oct 14, 2024
1111129
Test for checking unsupported data type
Ujj1225 Oct 14, 2024
2f2a50f
Bug:Moved the invocation for better data validation
Ujj1225 Oct 14, 2024
080c876
Fixed Formatting for partial tests
Ujj1225 Oct 14, 2024
35aa37c
Changed formatting back to how it was
Ujj1225 Oct 14, 2024
284adcf
Bug:Function invocation moved, formatting corrected, test removed
Ujj1225 Oct 14, 2024
2903c5e
Removed trailing whitespaces
Ujj1225 Oct 14, 2024
ce77584
Testing get_instance method of data_accessor
Ujj1225 Oct 15, 2024
1fa955c
Resolved comments featuring removal of extra warnings
Ujj1225 Oct 15, 2024
94d3798
Organized imports
Ujj1225 Oct 15, 2024
af1412c
Test to check function handle_invalid_data
Ujj1225 Oct 15, 2024
2f1f6a5
fixed linting errors
Ujj1225 Oct 15, 2024
56eff23
Replaced print with logging
Ujj1225 Oct 15, 2024
1566635
Organized import and removed unused ones
Ujj1225 Oct 15, 2024
8eca311
Test through public get_data
Ujj1225 Oct 22, 2024
ddf6e3d
Merge branch 'develop' into feature/#457callbackToChangeUnsupportedDa…
Ujj1225 Oct 22, 2024
f4892e7
Resolving comments of making function private
Ujj1225 Oct 24, 2024
7fbbd93
Merge branch 'develop' into feature/#457callbackToChangeUnsupportedDa…
FredLL-Avaiga Oct 25, 2024
4b29bda
Merge branch 'develop' into feature/#457callbackToChangeUnsupportedDa…
FredLL-Avaiga Oct 25, 2024
315d4c2
Merge branch 'develop' into feature/#457callbackToChangeUnsupportedDa…
FredLL-Avaiga Oct 30, 2024
4d67639
Merge branch 'develop' into feature/#457callbackToChangeUnsupportedDa…
FredLL-Avaiga Nov 2, 2024
f16f76b
Merge branch 'develop' into feature/#457callbackToChangeUnsupportedDa…
FredLL-Avaiga Nov 7, 2024
704ef45
Merge branch 'develop' into feature/#457callbackToChangeUnsupportedDa…
FredLL-Avaiga Nov 8, 2024
1ef1f60
Merge branch 'develop' into feature/#457callbackToChangeUnsupportedDa…
FredLL-Avaiga Nov 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions contributors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
jrobinAV
FabienLelaquais
florian-vuillemot
FredLL-Avaiga
dinhlongviolin1
joaoandre-avaiga
toan-quach
trgiangdo
gmarabout
tsuu2092
arcanaxion
Dr-Irv
enarroied
bobbyshermi
Forchapeatl
yarikoptic
Luke-0162
Satoshi-Sh
kushal34712
DeepanshuProgrammer
Ujj1225

5 changes: 5 additions & 0 deletions taipy/gui/data/data_accessor.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,11 @@ def __get_instance(self, value: _TaipyData) -> _DataAccessor: # type: ignore
access = self.__access_4_type.get(type(value))
if access is None:
if value is not None:
transformed_value = self.__gui._handle_invalid_data(value)
if transformed_value is not None:
Ujj1225 marked this conversation as resolved.
Show resolved Hide resolved
transformed_access = self.__access_4_type.get(type(transformed_value))
if transformed_access is not None:
return transformed_access
_warn(f"Can't find Data Accessor for type {str(type(value))}.")
return self.__invalid_data_accessor
return access
Expand Down
23 changes: 23 additions & 0 deletions taipy/gui/gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,13 @@ def __init__(
The returned HTML content can therefore use both the variables stored in the *state*
and the parameters provided in the call to `get_user_content_url()^`.
"""
self.on_invalid_data: t.Optional[t.Callable] = None
"""
The function that is called to transform data into a valid data type.
Invokes the callback to transform unsupported data into a valid format.
:param value: The unsupported data type encountered.
:return: Transformed data or None if no transformation is possible.
"""

# sid from client_id
self.__client_id_2_sid: t.Dict[str, t.Set[str]] = {}
Expand Down Expand Up @@ -407,6 +414,21 @@ def __init__(
for library in libraries:
Gui.add_library(library)

def _handle_invalid_data(self, value: t.Any) -> t.Optional[t.Any]:
"""
Handles unsupported data by invoking the callback if available.
:param value: The unsupported data encountered.
:return: Transformed data or None if no transformation is possible.
"""
try:
if _is_function(self.on_invalid_data):
return self.on_invalid_data(value) # type: ignore
else:
return None
except Exception as e:
_warn(f"Error transforming data: {str(e)}")
return None

@staticmethod
def add_library(library: ElementLibrary) -> None:
"""Add a custom visual element library.
Expand Down Expand Up @@ -2624,6 +2646,7 @@ def __bind_default_function(self):
self.__bind_local_func("on_exception")
self.__bind_local_func("on_status")
self.__bind_local_func("on_user_content")
self.__bind_local_func("on_invalid_data")

def __register_blueprint(self):
# add en empty main page if it is not defined
Expand Down
92 changes: 92 additions & 0 deletions tests/gui/gui_specific/test_get_instance.py
Ujj1225 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
from unittest.mock import MagicMock
from taipy.gui import Gui
from taipy.gui.data.data_accessor import (
_DataAccessor,
_DataAccessors,
_InvalidDataAccessor,
)
from taipy.gui.data.data_format import _DataFormat
from taipy.gui.utils.types import _TaipyData
from unittest.mock import Mock
import typing as t


def mock_taipy_data(value):
"""Helper to mock _TaipyData objects."""
mock_data = Mock(spec=_TaipyData)
mock_data.get.return_value = value
return mock_data

Ujj1225 marked this conversation as resolved.
Show resolved Hide resolved

class MyDataAccessor(_DataAccessor):
@staticmethod
def get_supported_classes() -> t.List[t.Type]:
return [int]

def get_data(
self,
var_name: str,
value: t.Any,
payload: t.Dict[str, t.Any],
data_format: _DataFormat,
) -> t.Dict[str, t.Any]:
return {"value": value}

def get_col_types(self, var_name: str, value: t.Any) -> t.Dict[str, str]: # type: ignore
pass

def to_pandas(self, value: t.Any) -> t.Union[t.List[t.Any], t.Any]:
pass

def on_edit(self, value: t.Any, payload: t.Dict[str, t.Any]) -> t.Optional[t.Any]:
pass

def on_delete(self, value: t.Any, payload: t.Dict[str, t.Any]) -> t.Optional[t.Any]:
pass

def on_add(
self,
value: t.Any,
payload: t.Dict[str, t.Any],
new_row: t.Optional[t.List[t.Any]] = None,
) -> t.Optional[t.Any]:
pass

def to_csv(self, var_name: str, value: t.Any) -> t.Optional[str]:
pass


def test_get_data_with_valid_data(gui: Gui):
"""Test if get_data() returns the correct accessor for valid data."""
data_accessors = _DataAccessors(gui)
data_accessors._DataAccessors__access_4_type = {int: Mock(get_data=lambda *args: "valid_data")} # type: ignore
Ujj1225 marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why don't you use the _register method and the MyDataAccessor class ?

result = data_accessors.get_data("var_name", mock_taipy_data(123), {})

assert result == "valid_data"


def test_get_data_with_invalid_data(gui: Gui):
"""Test if get_data() handles invalid data correctly."""
data_accessors = _DataAccessors(gui)
gui._handle_invalid_data = lambda x: None # type: ignore
mock_taipy_data = MagicMock()
mock_taipy_data.get.return_value = "invalid_data"
instance = data_accessors._DataAccessors__get_instance(mock_taipy_data) # type: ignore
assert isinstance(
instance, _InvalidDataAccessor
), f"Expected _InvalidDataAccessor but got {type(instance)}"
result = data_accessors.get_data("var_name", mock_taipy_data, {})
print(f"Result from get_data: {result}")

assert result == {}, f"Expected {{}} but got {result}"


def test_get_data_transformation_successful(gui: Gui):
"""Test if get_data() transforms invalid data correctly when a callback is set."""
data_accessors = _DataAccessors(gui)
gui.handle_invalid_data = lambda x: 123 # type: ignore
data_accessors._DataAccessors__access_4_type = {int: Mock(get_data=lambda *args: "valid_data")} # type: ignore
mock_data = mock_taipy_data(123)
result = data_accessors.get_data("var_name", mock_data, {})

assert result == "valid_data", f"Expected 'valid_data' but got {result}"
67 changes: 67 additions & 0 deletions tests/gui/gui_specific/test_handle_invalid_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import logging

import pytest

from taipy.gui import Gui


def test_handle_invalid_data_no_callback():
gui = Gui() # No callback set
result = gui.handle_invalid_data("invalid_data")

assert result is None # Should return None


def test_handle_invalid_data_callback_returns_none():
def callback(value):
return None # Simulates a failed transformation

gui = Gui()
gui.on_invalid_data = callback # Set the callback

result = gui.handle_invalid_data("invalid_data")
assert result is None # Transformation should result in None


def test_handle_invalid_data_callback_transforms_data():
def callback(value):
return "valid_data" # Successful transformation

gui = Gui()
gui.on_invalid_data = callback # Set the callback

result = gui.handle_invalid_data("invalid_data")
assert result == "valid_data" # Data transformed correctly


def test_handle_invalid_data_callback_raises_exception(capfd, monkeypatch):
def callback(value):
raise ValueError("Transformation error") # Simulate an error

def mock_warn(message: str):

logging.warning(message) # Ensure the warning goes to stderr.

gui = Gui()
gui.on_invalid_data = callback # Set the callback

# Patch the _warn function inside the taipy.gui._warnings module.
monkeypatch.setattr("taipy.gui._warnings._warn", mock_warn)

result = gui.handle_invalid_data("invalid_data")
out, _ = capfd.readouterr()

assert result is None # Should return None on exception
assert "Error transforming data: Transformation error"


@pytest.mark.parametrize("input_data", [None, 123, [], {}, set()])
def test_handle_invalid_data_with_various_inputs(input_data):
def callback(value):
return "valid_data" # Always returns valid data

gui = Gui()
gui.on_invalid_data = callback # Set the callback

result = gui.handle_invalid_data(input_data)
assert result == "valid_data" # Transformed correctly for all inputs
Loading