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

[OTHER] Taipy unable to assign a state variable, apparent leak between clients? #2441

Open
2 of 7 tasks
LMaiorano opened this issue Feb 7, 2025 · 18 comments
Open
2 of 7 tasks
Assignees
Labels
Gui: Back-End 🖰 GUI Related to GUI 💥Malfunction Addresses an identified problem. 🟥 Priority: Critical Must be addressed as soon as possible

Comments

@LMaiorano
Copy link

LMaiorano commented Feb 7, 2025

What went wrong? 🤔

I've been stuck on a really critical bug for the past week. It's preventing pages from loading, and the only current workaround is restarting the server. It occurs (seemingly) random, even with automated tests.

The issue arises a nested dict-like data structure object is saved to a state variable. This object is used to save datasets, figures, values, etc. The size of this data structure is dynamic and is different for each client session, defined by a query parameter in the URL called client_handle (important). The data structure is constructed during on_init() of the page without any issue, and then assigned to a state variable in a simple line: state.risk_tree = tree which causes the (occasional) error.

The first client to connect to the server (since server startup) always initializes without a problem. Subsequent clients can have the issue, if they use a different client_handle. For example, assuming each client connects from an isolated browser and creates its own Taipy session:

  1. client_handle = vm_groningen_dev (initialization OK)
  2. client_handle = vm_gelderland_dev (initialization OK)
  3. client_handle = vm_limburg_dev (ERROR)

The error raised by during the initialization of connection #3 (vm_limburg_dev) is:

File "taipy/gui/utils/_attributes.py", line 26, in _getscopeattr_drill 
  return attrgetter(name)(gui._get_data_scope())
AttributeError("'types.SimpleNamespace' object has no attribute 'tp_TpExPr_gui_get_adapted_lov_risk_tree_vm_gelderland_dev_root_0_thema_2_rp_0_ind_0_var_CHART_AGG_LOV_str_TPMDL_9_0'")

This long attribute id refers to the key which is used to retrieve an element from the data structure (in this case an LOV).

Note: in the middle of the attribute id is a part vm_gelderland_dev (the client_handle). The currently connected client is vm_limburg_dev. This indicates Taipy is trying to bind a callback from another client session, which it obviously cannot find in this session.

Expected Behavior

The state variable state.risk_tree should be set. 9/10 times this works without a problem.

Steps to Reproduce Issue

The biggest difficulty is that this bug is not consistent. 9/10 times it works fine, even when I reproduce client page-visit combinations that previously caused an error. So the only way to debug this is by inspecting the logs. I realize this is very little to go off, but since I can't even reliably reproduce the error, creating a minimum example is pretty much impossible.

See isolated_error.log, which also contains the variables. The actions to produce this:

  • The first 5 entries show how a client with client_handle = vm_gelderland_dev initializes without an issue.
  • Browser is then closed.
  • New browser is opened, client with client_handle = vm_limburg_dev fails to initialize.
    • Note that the error contains the client_handle of the previous client

The issue occurs in the page on_init(). Constructing the object works without an issue. Saving the object to a state variable causes the issue.

<imports>
# Declare page state variable
risk_tree = None

def on_init(state):
    # Create the basic tree structure based on client settings. Data is added later in Long-Running Callback
    state_var_name = "risk_tree"
    tree = TreeRoot(
        client_subniveau=state.client_settings["subniveau"],
        client_alt_subniveaus=state.client_settings["subniveau_alt"],
        state_var_name=state_var_name,
        client_handle=state.client_handle,
        children=[
            ThemaNode(
                id=idx,
                thema_naam=thema,
                risico_profielen=risicos,
                state_var_name=state_var_name,
            )
            for idx, (thema, risicos) in enumerate(state.client_settings["risico_themas"].items())
        ],
        client_settings=state.client_settings,
        client_data_ops_fn=apply_client_allpages_data_operations,
    )

    state.risk_tree = tree # ERROR OCCURS HERE
    
    tpgui.invoke_long_callback(
        state=state,
        user_function=LRCB_load_risicotree_data,
        user_function_args=[
            state.get_gui(),
            tpgui.get_state_id(state),
            tree,
            state.client_handle,
            state.pg_uri,
        ],
        user_status_function=status_LRCB_tree_loading,  # invoked at the end of and possibly during the runtime of user_function
        period=10000,  # Interval in milliseconds to check the status of the long callback
    )

After the Long-Running Callback is complete, a Taipy Partial generates the content of the page. As seen below, the GUI elements reference attributes inside the data structure.

def create_taipy_partial_content(tree_node, page_context=None):
    """Create the content for the current node in the Taipy Page format
    """
    class_name = f"{tree_node.css_class_name} content-block"

    # All other nodes (RisicoNode, IndicatorNode, VariabeleNode)
    with tgb.part(class_name=class_name) as content:
        if is_thema_node:
            # Just title
            tgb.text(
                f"## {tree_node.get_label_text().upper()}",
                mode="markdown",
                class_name="card-title",
            )

        else:
            # Title and link to docs
            with tgb.layout(columns="2 1"):
                tgb.text(
                    f"**{tree_node.get_label_text().title()}**",
                    mode="markdown",
                    class_name="card-title",
                )
                tgb.button(
                    "{docs_icon}",                   # docs_icon is defined in main.py
                    on_action="{navigate_to_docs}",  # navigate_to_docs is defined in main.py
                    class_name="docs-button",
                    hover_text="Open documentatie",
                )

        # Description
        tgb.text(f"{tree_node.help}", mode="markdown")

        # Figure
        if tree_node.figure is not None and tree_node.selected:
            tgb.toggle(
                value="{"
                + f"{tree_node.state_var_name}['{tree_node.id}']" #client_handle is part of the tree_node.id
                + ".chart_agg_toggle}",
                lov="{"
                + f"{tree_node.state_var_name}['{tree_node.id}']"
                + ".CHART_AGG_LOV}",
                on_change=callback_chart_agg_toggle,
            )
            tgb.chart(
                figure="{"
                + f"{tree_node.state_var_name}['{tree_node.id}']"
                + ".figure}"
            )

        # Build children in nested content blocks
        for child in tree_node.children:
            if child.count_selected() == 0:
                continue
            create_taipy_partial_content(child)

    return content

Runtime Environment

Docker Container: python:3.12-slim-bookworm

Browsers

Chrome, Firefox, Safari

Version of Taipy

4.0.2

Additional Context

Acceptance Criteria

  • A unit test reproducing the bug is added.
  • Any new code is covered by a unit tested.
  • Check code coverage is at least 90%.
  • The bug reporter validated the fix.
  • Related issue(s) in taipy-doc are created for documentation and Release Notes are updated.

Code of Conduct

  • I have checked the existing issues.
  • I am willing to work on this issue (optional)
@LMaiorano LMaiorano added the 💥Malfunction Addresses an identified problem. label Feb 7, 2025
@jrobinAV jrobinAV added 🟥 Priority: Critical Must be addressed as soon as possible 🖰 GUI Related to GUI 🆘 Help wanted Open to participation from the community Gui: Back-End labels Feb 7, 2025
@FredLL-Avaiga FredLL-Avaiga self-assigned this Feb 18, 2025
@FredLL-Avaiga FredLL-Avaiga removed the 🆘 Help wanted Open to participation from the community label Feb 18, 2025
@LMaiorano
Copy link
Author

Hi,

I've been able to isolate the error and produce a minimum example that (mostly) reproduces the error.

Running this example causes warnings to appear in the terminal, but the dashboard still functions okay. These warnings are nearly identical to the issue above, and the current (temporary) workaround to resolve these warnings has also resolved the issue above.

Minimum working example

Taipy version: 4.0.2

import taipy.gui.builder as tgb
import taipy.gui as tpg

client_handle = None
ui_elements_data = {} # In the real application, this is a dictionary-like object with the workaround implemented in its __getitem__ method. 

class ClientUiElementStruct:
    """Very simplified example"""
    TOGGLE_LOV = ['item1', 'item2']
    
    def __init__(self):
        self.toggle_value = 'item1'
    
    def __repr__(self):
        return f"{self.__class__.__name__}(toggle_value={self.toggle_value!r})"


def refresh_partial(state):
    """Only create content for the client if a client name is set. This implies that the necessary datastructure is also available"""
    if cname := state.client_handle:
        with tgb.Page() as client_part:
            with tgb.part("card"):
                tgb.text("The client name is: " + cname)
                
                tgb.toggle(
                    value="{" + f"ui_elements_data['{cname}']" + ".toggle_value}",
                    lov="{" + f"ui_elements_data['{cname}']" + ".TOGGLE_LOV}",
                    on_change=callback_toggle
                )
                
                tgb.text("The active selected toggle value is: {ui_elements_data['" + cname + "'].toggle_value}")
    
                tgb.text("The complete content of state.ui_elements_data: {str(dict(ui_elements_data))}")
                
        state.client_partial.update_content(state, client_part)     


def callback_toggle(state, var_name, value):
    state.ui_elements_data[state.client_handle].toggle_value = value
    state.refresh('ui_elements_data')


# Initialize the application state
def on_init(state):
    refresh_partial(state)


def on_navigate(state: tpg.State, page_name: str, params: dict = {}) -> str:
    """Extract the client name from the query parameters and update the state"""
    # If a client parameter is provided, update client-specific settings
    if handle := params.get("client", False):
        if len(handle) > 0:
            print(f"Client name query parameter provided: {handle}")
            
            # Theoretically, state variable 'ui_elements_data' should be unique for each client (empty for a new client)
            #   (as per documentation: https://docs.taipy.io/en/latest/refmans/reference/pkg_taipy/pkg_gui/State/)
            # However, since this (for some reason) is not the case, we manually set any previous client data to 'None'. This simulates the behavior of the actual application. (Alternative: remove the entries, but this causes issues)
            # For context: a new client connecting to the dashboard will ALWAYS have the query parameter 'client' set
            previous_handles = [k for k in state.ui_elements_data.keys() if k != handle]
            if len(previous_handles) > 0:
                for k in previous_handles:
                    state.ui_elements_data[k] = None
                    # state.ui_elements_data.pop(k) # Alternative: Removing the entries also causes issues
            
            state.client_handle = handle
            state.ui_elements_data[handle] = ClientUiElementStruct()
            state.refresh('ui_elements_data') # Just to be sure that the ui_elements_data is updated
            
            refresh_partial(state)
            
            tpg.navigate(state, to="/", tab="_self") # force navigation to the root page without query parameters. Prevents endless loop

    return page_name

if __name__ == "__main__":
    
    # Define the main page layout
    with tgb.Page() as main_page:
        tgb.text("Multi-Client DEBUG Dashboard", class_name="h1")
        tgb.part(partial="{client_partial}")
    
    pages = {"/": main_page}

    # Create and run the Taipy GUI
    gui = tpg.Gui(main_page)
    client_partial = gui.add_partial("")
    gui.run()

Steps to reproduce

Note: ensure the query parameter ?client=... is provided for each new client connection. This simulates the proper behavior.

  1. Run the example dashboard above
  2. Go to http://localhost:5000?client=first
  3. Verify in python terminal that there are no errors
  4. Close browser (no longer needed)
  5. In new browser (incognito to ensure independent client), go to http://localhost:5000?client=second
  6. Note in the webUI the final line: state.ui_elements_data: {'first': None, 'second': ClientUiElementStruct(toggle_value='item1')}
    a. See issue 1) below...
  7. See errors in python terminal. See issue 2) below. Should start like this:
.venv/lib/python3.12/site-packages/taipy/gui/utils/_evaluator.py:372: TaipyGuiWarning: Exception raised evaluating dataset['first'].toggle_value:
Traceback (most recent call last):
  File ".venv/lib/python3.12/site-packages/taipy/gui/utils/_evaluator.py", line 369, in re_evaluate_expr
    expr_evaluated = eval(expr_string, ctx)
                     ^^^^^^^^^^^^^^^^^^^^^^
  File "<string>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'toggle_value'

  _warn(f"Exception raised evaluating {expr_string}", e)
...

Issue Descriptions:

1) State variable shared across clients

Each time a new client connects to the dashboard and provides the query parameter client=<handle>, this handle is stored in the state variable state.dataset[<handle>] = ClientUiElementStruct(). The ClientUiElementStruct() object contains the LOV and value attributes to be used for the toggle.

In theory, this state variable should be unique to each client. To track this, its contents are displayed in the partial. The expected value is a dictonary with one key-value pair. For the second client, this should be {'second': ClientUiElementsStruct(toggle_value='item1')}

In practice, once the second client has connected, the state variable used by the second client contains BOTH the first client's handle and the second client's handle: {'first': None, 'second': ClientUiElementStruct(toggle_value='item1')}

Maybe I misunderstand how this should work, but the way I read the documentation, each connection (client) should hold it's own independent set of state variables.

2) Taipy backend - trying to access attribute of NoneType object, for binding of different client

To further simulate the behavior of the actual application, any unwanted 'shared' variables are set to None in the on_navigate() method.

The Taipy partial only updates content if the state.client_handle has been specified and the necessary ClientUiElementStruct() is available.

Two types of errors can be observed:

  1. Taipy can no longer bind the ui elements of client 'first', giving errors such as Failed to get: tp_TpExPr_ui_elements_data_first_TOGGLE_LOV_TPMDL_0_0. This makes sense, since the first client session state is technically still active on the server-side, and the state-variable has suddenly been set to None.
    a. Nonetheless, it would be helpful if there was a proper way of 'disconnecting' the bindings
  2. AttributeError: 'NoneType' object has no attribute 'toggle_value' Also logical since what was previously an object is now NoneType. However, in the context of the actual application this is more complex. The data structure there is dynamic and can change in size, therefore it's not unreasonable that certain entries may be deleted or set to None.

Suggested fixes

  1. New Taipy functionality to 'disconnect' or 'delete' bindings. Since gui elements can be 'created' through a partial, there should also be a way to 'remove' them. In this case that would be useful when the bound state variable is modified or deleted.
  2. The current Taipy code handles an AttributeError fine using a try-except for "taipy/gui/utils/_evaluator.py", line 369, in re_evaluate_expr: expr_evaluated = eval(expr_string, ctx). However, in the case of the actual application, the critical error was nearly identical but found in a slightly different place:
taipy/gui/utils/_attributes.py", line 26, in _getscopeattr_drill
    return attrgetter(name)(gui._get_data_scope())
 
builtins.AttributeError: 'types.SimpleNamespace' object has no attribute 'tp_TpExPr_gui_get_adapted_lov_risk_tree_vm_gelderland_dev_root_0_thema_2_rp_0_ind_0_var_CHART_AGG_LOV_str_TPMDL_9_0'

The cause is the same: the state variable which contains an attribute that is bound to a UI element is either missing or replaced. Thus, a similar try-except error handling should be applied here. This would also effectively 'unbind' the UI connection, without raising an execption that reaches the highest level and prevents any other bindings from being updated.

Current workaround

For now, I've ensured that the dict-like data structure risk_tree in which the variables are stored (similar to the variable ui_elements-data in minimum example above) uses a custom __getitem__ method. This ensures that if the item cannot be found (which would normally raise a KeyError or return None), now returns an empty instance of the class with the necessary attributes that Taipy has previously bound UI elements to (such as toggle_lov)

Of course this only works for this case and is not scalable for use with other data structures.

I hope this gives you more insights into the issue. Thanks!

@FredLL-Avaiga
Copy link
Member

Thanks a lot for the really detailed issue and description

@FredLL-Avaiga
Copy link
Member

FredLL-Avaiga commented Feb 20, 2025

The doc might want to be more precise on the variable state description:

  • each state (1 per client) has its own value for each variable except for the non scalar variables where the instances are shared.
    if you want a non scalar variable to be specific for each state, you need to set it in the on_init
def on_init(state):
    state.ui_elements_data = {}

@FredLL-Avaiga
Copy link
Member

FredLL-Avaiga commented Feb 20, 2025

In your case, would it be a good idea to instantiate the ClientUiElementStruct class for each state ?

@LMaiorano
Copy link
Author

I tried the following modifications:

...
client_handle = None
ui_elements_data = None # Must be declared, otherwise error in on_init(): AttributeError: Variable 'ui_elements_data' is not accessible.

...

# Initialize the application state
def on_init(state):
    state.ui_elements_data = {}
    refresh_partial(state)

...

The dashboard works fine for the first client, but then throws an error when loading the second client:

.venv/lib/python3.12/site-packages/taipy/gui/utils/_evaluator.py:372: TaipyGuiWarning: Exception raised evaluating ui_elements_data['first'].toggle_value:
Traceback (most recent call last):
  File ".venv/lib/python3.12/site-packages/taipy/gui/utils/_evaluator.py", line 369, in re_evaluate_expr
    expr_evaluated = eval(expr_string, ctx)
                     ^^^^^^^^^^^^^^^^^^^^^^
  File "<string>", line 1, in <module>
  File ".venv/lib/python3.12/site-packages/taipy/gui/utils/_map_dict.py", line 37, in __getitem__
    value = self._dict.__getitem__(key)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^
KeyError: 'first'
...

I'm not sure what you mean by this exactly?

@FredLL-Avaiga
Copy link
Member

I tried the following modifications:

...
client_handle = None
ui_elements_data = None # Must be declared, otherwise error in on_init(): AttributeError: Variable 'ui_elements_data' is not accessible.

...

Initialize the application state

def on_init(state):
state.ui_elements_data = {}
refresh_partial(state)

...
The dashboard works fine for the first client, but then throws an error when loading the second client:

.venv/lib/python3.12/site-packages/taipy/gui/utils/_evaluator.py:372: TaipyGuiWarning: Exception raised evaluating ui_elements_data['first'].toggle_value:
Traceback (most recent call last):
File ".venv/lib/python3.12/site-packages/taipy/gui/utils/_evaluator.py", line 369, in re_evaluate_expr
expr_evaluated = eval(expr_string, ctx)
^^^^^^^^^^^^^^^^^^^^^^
File "", line 1, in
File ".venv/lib/python3.12/site-packages/taipy/gui/utils/_map_dict.py", line 37, in getitem
value = self._dict.getitem(key)
^^^^^^^^^^^^^^^^^^^^^^^^^^^
KeyError: 'first'
...
I'm not sure what you mean by this exactly?

The technical explanation is that:

  • we calculate variable depencency
  • from your page, we have a dependency between ui_elements_data and ui_elements_data['first'].toggle_love
  • when the state.ui_elements_data, the system tries to refresh ui_elements_data['first'].toggle_love which doesn't exist anymore

@FredLL-Avaiga
Copy link
Member

I'm not sure what you mean by this exactly?

do you really need your client_handle ?
You could set an instance of ClientUiElementStruct for each client state in the on_init callback

@LMaiorano
Copy link
Author

Yes, client_handle is the core of the application and defines which database schema should be referenced for that client. In this database is a configuration table that effectively defines the extent of the ClientUiElementStruct and therefore also which UI elements need to be constructed.

For example, based on the config table:

  • Client1 needs 5 Toggles + 1 figure
  • Client2 needs 8 Toggles + 3 figures + 1 slider
  • ClientX needs ...

The database is dynamic, so all the possible combinations cannot be known before the client connects

@FredLL-Avaiga
Copy link
Member

I think you should avoid this ui_elements_data dict. That is what's causing your trouble.
I would recommend that you instantiate a ClientUiElementStruct in on_init.
Then you fill it in on_navigate depending on your client_handle.
Do you think that would work ?

@LMaiorano
Copy link
Author

To further clarify: in the actual application, just as you describe, the state variable is only the ClientUIElementStruct.

There is no separate dictionary in which the ClientUiElementStruct is placed. The structure itself resembles a dictionary and is populated based on the client_handle.

Referring back to the original issue: (where TreeRoot() is the real implementation of ClientUiElementStruct())

state.risk_tree is a single instance of the class TreeRoot(), as can be seen in the on_init() function. As seen in the code, this TreeRoot() is populated with data specific to the client_handle

NOTE: the specific on_init() function shown above is located inside a page definition, and is manually called in the main.py. (Apologies for the confusing naming, this should maybe better be named init_client())

The issue occurs in the page on_init(). Constructing the object works without an issue. Saving the object to a state variable causes the issue.

page.py:

<imports>
# Declare page state variable
risk_tree = None

def on_init(state):
    # Create the basic tree structure based on client settings. Data is added later in Long-Running Callback
    state_var_name = "risk_tree"
    tree = TreeRoot(...

In the main.py, this page's on_init() is manually called BY the global on_navigate().

To clarify, the overall control logic of a new client connecting is as follows:

graph TD
    A[New client connects<br>with client_handle in query params] -->| Taipy calls | C[on_navigate]

    C -->|Extract client_handle query parameter| D[Start client setup<br>to initialize pages]

    D --> J[Setup database connection]
    J --> L[Get client pages config from SQL]
    L --> M[Initialize pages: <br>run on_init of each page]
    M --> |Instantiate TreeRoot object| N[Save<br>state.risk_tree = TreeRoot]
    N --> |Start Long-Running callback| X[Retrieve data + create figures]
    N --> Y
    X -- LR Callback complete -->V[Update state.risk_tree object with new data + figures]
    V --> Y[Update partials]
    Y --> O{Check for failed pages}
    O -->|Success| E[Navigate user to dashboard]
    O -->|Failures| P[Remove failed pages]
    P --> E
Loading

Note: When the TreeRoot object is instantiated, all variables needed for UI elements are immediately created in the object. The long-running callback simply updates the values of these variables with actual data.

@FredLL-Avaiga
Copy link
Member

Thanks for the clarification.
Sadly I think the issue shown in your replication code is not the one that you encounter in your real code (even it is has the same output).

@FredLL-Avaiga
Copy link
Member

the naming could be indeed a problem as taipy looks for on_init function automatically and invokes it on the first render ...

@FredLL-Avaiga
Copy link
Member

value="{"
+ f"{tree_node.state_var_name}['{tree_node.id}']" #client_handle is part of the tree_node.id
+ ".chart_agg_toggle}"

I think that your problem here is that with such an expression your creating a huge dependency network between your variables.

for each toggle you create, the value property is bound to risk_tree['nodeid'].chart_agg_toggle.

Can you share your definition of the TreeRoot and ThemaNode classes ?

@FredLL-Avaiga
Copy link
Member

If you use the dot notation for dict access your values will be changed without needing a callback.
ie risk_tree['nodeid'].chart_agg_toggle will have no effect unless you specify a callback and manage the change
while risk_tree.nodeid..chart_agg_toggle will have an effect on the variable on click.

PS: I know this will not solve your issue but it might help

@shadingo
Copy link

An example that outlines how state seems to have the same id between two clients.
(Perhaps this is unexpected & could help understand OP's issue?)

from types import SimpleNamespace
from taipy.gui import Gui
import taipy.gui.builder as tgb
import numpy as np

class Status():
    def __init__(self):    
        self.login_attempt = True
        self.rat = 7
        self.dog = SimpleNamespace(a = 5)
        self.fish = np.random.rand()

def on_init(state):
    status = Status()
    state.status = status
        
status = None

if __name__ == "__main__":    
    with tgb.Page() as page:
        tgb.slider("{status.dog.a}")
        tgb.text("{status.dog.a}")
                 
        tgb.button("print", on_action = lambda state: print(id(state), state.status.__dict__))
        tgb.button("print", on_action = lambda state: print(id(state.status.dog), state.status.dog.a, "\n"))
        
    app = Gui(page = page)

    app.run()

Image

The output below the code follows a sequence of events A, B and C.
A. Client 1 starts, and presses the first & second buttons. Client 1 then moves the sliders, and presses first & second buttons again.
B. Client 2 starts, and does exactly as Client 1 did in event A.
C. Client 1 presses the first & second buttons.

The app works as expected and both clients appear to have independent Status objects tied to their state, and both seemingly can update the value of dog.a independently from each other (as event C points out). The sliders' behavior matches the output too.

But, notice the print outs for id(state) and id(state.status.dog): we see different ids for each client's dog, but the same id for each client's state...
(I wouldn't know if this is expected or not, but it might be unsettling especially with respect to the documentation!)

@FredLL-Avaiga
Copy link
Member

same id for each client's state this is expected and normal as the state is only a proxy to each client data

@LMaiorano
Copy link
Author

Hi Fred,
Thanks again for your time and effort looking into this!

the naming could be indeed a problem as taipy looks for on_init function automatically and invokes it on the first render

I changed the naming to page_init. This gets imported into main.py and is explicitly called inside the on_navigate() function. This change had no effect on the issue.

@LMaiorano
Copy link
Author

I'd like to clarify that there are two separate issues here. Just to make sure we're not confusing them with each other:

  1. State variable value shared across clients
    • Observation during debugging
  2. Taipy backend trying to access an attribute of a NoneType object (or of a SimpleNamespace object in production)
    • Raises a KeyError in the replication code, resulting in warning
    • Raises an AttributeError in production code, resulting in failed page load

The replication code I provided was to demonstrate both issues.

Issue 1 is something I observed during debugging and was inconsistent with my understanding of the state variable as described in the docs. Specifically, as demonstrated in the replication code:

... the state variable [visible to] the second client contains BOTH the first client's handle and the second client's handle: {'first': None, 'second': ClientUiElementStruct(toggle_value='item1')}

If you say this behavior is expected, then please regard this issue as resolved.

Issue 2 has been temporarily resolved via the workaround. The workaround ensures that if the key cannot be found, it returns an empty object instead of raising a KeyError (AttributeError in production code)

Nonetheless, I believe a more robust solution would be if some kind of error-handling could be built into Taipy. That would enable the page to continue to load even if certain elements could not be found. The replication code only raises a warning, which demonstrates that the error is correctly captured in a try-except statement (see "taipy/gui/utils/_evaluator.py", line 369, in re_evaluate_expr: expr_evaluated = eval(expr_string, ctx)).

However, in the production code, the equivalent error is not caught by taipy/gui/utils/_attributes.py", line 26. I say 'equivalent' because it's also caused by a key or attribute not being found in an object.
The error raised is:

taipy/gui/utils/_attributes.py", line 26, in _getscopeattr_drill
    return attrgetter(name)(gui._get_data_scope())
 
builtins.AttributeError: 'types.SimpleNamespace' object has no attribute 'tp_TpExPr_gui_get_adapted_lov_risk_tree_vm_gelderland_dev_root_0_thema_2_rp_0_ind_0_var_CHART_AGG_LOV_str_TPMDL_9_0'

Alternatively: would it make sense to add a feature/functionality that enables GUI bindings to be "disconnected" when no longer needed? Similar to how UI elements are created inside a partial, but then the opposite? Just thinking out loud...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Gui: Back-End 🖰 GUI Related to GUI 💥Malfunction Addresses an identified problem. 🟥 Priority: Critical Must be addressed as soon as possible
Projects
None yet
Development

No branches or pull requests

4 participants