BUG-Multi-user does not work for properties of an object
AlexandreSajus opened this issue · 9 comments
Description
Changing the property of an object with state.object.property = new_value
seems to change the property for all users instead of the user who changed it.
How to reproduce
Using this code:
from taipy.gui import Gui, State, notify
class ModalWindow:
def __init__(self):
self.open = True
MODAL_MANAGER = ModalWindow()
modal_dialog = """
<|{MODAL_MANAGER.open}|dialog|title=Are you a scientist?|width=30%|
**Warning**
You must be a scientist to be able to view this page
<br/>
<|I am a scientist|button|class_name=fullwidth plain|on_action=close_modal_window_and_render_page|>
|>
<|part|render={not MODAL_MANAGER.open}|
All the sciency stuff now shows up here
|>
"""
def close_modal_window_and_render_page(state:State):
state.MODAL_MANAGER.open = False
state.refresh("MODAL_MANAGER")
notify(state, "S", "Welcome, Scientist!")
gui = Gui(page=modal_dialog)
gui.run()
1 - Open the app in a browser
2 - Click the "I am a scientist" button, this closes the dialog window
3 - Open the app in a incognito tab, the app starts with the dialog window closed which should not be the case
This is fixed if I replace the open property with an open variable outside of an object
from taipy.gui import Gui, State, notify
opening = True
modal_dialog = """
<|{opening}|dialog|title=Are you a scientist?|width=30%|
**Warning**
You must be a scientist to be able to view this page
<br/>
<|I am a scientist|button|class_name=fullwidth plain|on_action=close_modal_window_and_render_page|>
|>
<|part|render={not opening}|
All the sciency stuff now shows up here
|>
"""
def close_modal_window_and_render_page(state: State):
state.opening = False
notify(state, "S", "Welcome, Scientist!")
gui = Gui(page=modal_dialog)
gui.run()
Expected behavior
Changing properties of objects should only change the state-bound property and should not change it for all users
Screenshots
When available and relevant, screenshots better help show the problem.
Runtime environment
- OS: Windows 11
- Browser: Chrome
- Taipy: 3.0.0
of course it does
you need to assign a copy of the object in on_init
of course it does you need to assign a copy of the object in on_init
Can you clarify how would that look on the code? I am not sure I understand the comment. I have raised this issue on discord as my entire code is using python objects like the example and I see this issue everywhere 😕
Ah, I think I get it. Adding this to the code fixes the issue:
def on_init(state):
state.MODAL_MANAGER = ModalWindow()
This means that when a new user connects to the app we create a new object to him.
Maybe we should highlight this more in the doc or change this behaviour.
So the issue seems to be that this is not needed for simple variables (integers, floats, etc. I think lists may have this problem), why is that the case?
I know Python sometimes likes to assign the memory address of arrays/objects to variables, instead of a new instance. it looks similar to what is happening
Also, it seems that for it to properly work you need to declare MODAL_MANAGER
twice, once on on_init
and once in the root code, so it does seem like unexpected behaviour
@gbritoda What happens is exactly what you point to: each 'state', representing the variables for a given user (that is, a connection from a browser) shares the same object: in Python, setting a property of a dictionary does not change the dict itself, but replaces the value for that property. This behavior does not occur (and cannot occur) for literals, where there is no such value indirection.
So if you want to have user-dependent value hidden in dictionaries exposed in user states, you need to create a copy of the object. A good place to do that is in the on_init
callback. Then, each user gets its own dictionary, and you don't face the problem any longer.
Is that a satisfying response?
Alright. I'll try the on_init
approach on my applications and see how I get on. Thank you
But is that going to be considered expected behaviour or an issue?
That would be the expected behavior, or else you would have to deep-duplicate every objects in every state, which would be a burden for performances. And that would also be divergent from how Python deals with object assignment,
Could anyone clarify just one thing, please? Looking at the code snippet below the line with MODAL_MANAGER = ModalWindow()
is needed, so we need to declare the variable twice?
from taipy.gui import Gui, State, notify
class ModalWindow:
def __init__(self):
self.open = True
MODAL_MANAGER = ModalWindow()
modal_dialog = """
<|{MODAL_MANAGER.open}|dialog|title=Are you a scientist?|width=30%|
**Warning**
You must be a scientist to be able to view this page
<br/>
<|I am a scientist|button|class_name=fullwidth plain|on_action=close_modal_window_and_render_page|>
|>
<|part|render={not MODAL_MANAGER.open}|
All the sciency stuff now shows up here
|>
"""
def on_init(state):
state.MODAL_MANAGER = ModalWindow()
def close_modal_window_and_render_page(state:State):
state.MODAL_MANAGER.open = False
state.refresh("MODAL_MANAGER")
notify(state, "S", "Welcome, Scientist!")
gui = Gui(page=modal_dialog)
gui.run()
Also If this behaviour is going to stay the same I'd suggest to close the ticket, if @AlexandreSajus agrees
Actually, you can replace MODAL_MANAGER = ModalWindow()
with MODAL_MANAGER = None
. The goal of the first declaration is just to declare the existence of MODAL_MANAGER as a variable. The real issue is that we should make it clear in the docs that objects should be created in on_init() but I am not sure where we could put this in the doc.