plotly / dash

Data Apps & Dashboards for Python. No JavaScript Required.
https://plotly.com/dash
MIT License
21.48k stars 2.07k forks source link

Add a function to directly retrieve component property values in callbacks #2971

Open insistence opened 2 months ago

insistence commented 2 months ago

For example, I currently have a dcc.Store component in the application. The Store component stores the data that is required for most callbacks. In the current application, I have to add the Store component to the State in each callback. Just like below

@app.callback(
    Output(...),
    Input(...),
    State('store', 'data')
)
def callback1(...):
    ...

@app.callback(
    Output(...),
    Input(...),
    State('store', 'data')
)
def callback2(...):
    ...

@app.callback(
    Output(...),
    Input(...),
    State('store', 'data')
)
def callback3(...):
    ...

If there is a get_props function, I can encapsulate a universal function that directly retrieves the data of the Store component for processing, without the need to retrieve the data of the Store component through the State in each callback. Just like below

def global_deal_func():
    stroe_data = dash.get_props('store', 'data')
    ...

@app.callback(
    Output(...),
    Input(...)
)
def callback1(...):
    global_deal_func()
    ...

@app.callback(
    Output(...),
    Input(...)
)
def callback2(...):
    global_deal_func()
    ...

@app.callback(
    Output(...),
    Input(...)
)
def callback3(...):
    global_deal_func()
    ...

This is just a tentative feature request, perhaps there will be a better solution, thank you very much.

AnnMarieW commented 2 months ago

Thanks for the suggestion, however I wouldn’t recommend this feature. It’s just a different way to do it rather than a better way .

insistence commented 2 months ago

Sorry, I don't understand where you're not suggesting a better approach. Could you please explain it in detail? From my perspective, I think this approach can help save a lot of callback orchestration, especially when there are thousands of callbacks. Thanks!

aGitForEveryone commented 2 months ago

Currently, you need 1 line for adding the State, and you need to add the parameter to the list of input parameters of the callback.

In your approach, you add 1 line inside the callback function body for retrieving the value.

Comparing both approaches it seems tits for tats. An alternative way for retrieving data, but is it necessarily better? In the current way, it is clear from the callback signature what data the callback requires. In your suggestion, you add an extra layer of obfuscation. Also I believe that this way of retrieving data is invisible for callback parsers, for example when analyzing the callback chain. Furthermore, the amount of code you have to write to retrieve the data is about the same in both ways. Also in your suggestion you have to add the function call in each of your callbacks.

So, I agree with Ann Marie here, I am not (yet) seeing the added value.

insistence commented 2 months ago

Currently, you need 1 line for adding the State, and you need to add the parameter to the list of input parameters of the callback.

In you approach, you add 1 line inside the callback function body for retrieving the value.

Comparing both approaches it seems tits for tats. An alternative way for retrieving data, but is it necessarily better? In the current way, it is clear from the callback signature what data the callback requires. In your suggestion, you add an extra layer of obfuscation. Also I believe that this way of retrieving data is invisible for callback parsers, for example when analyzing the callback chain. Furthermore, the amount of code you have to write to retrieve the data is about the same in both ways. Also in your suggestion you have to add the function call in each of your callbacks.

So, I agree with Ann Marie here, I am not (yet) seeing the added value.

current approach

def global_deal_func(store_data):
    deal(store_data)
    ...

@app.callback(
    Output(...),
    Input(...),
    State('store', 'data')
)
def callback1(..., store_data):
    global_deal_func(store_data)
    ...

@app.callback(
    Output(...),
    Input(...),
    State('store', 'data')
)
def callback2(..., store_data):
    global_deal_func(store_data)
    ...

@app.callback(
    Output(...),
    Input(...),
    State('store', 'data')
)
def callback3(..., store_data):
    global_deal_func(store_data)
    ...

feature request approach

def global_deal_func():
    stroe_data = dash.get_props('store', 'data')
    deal(store_data)
    ...

@app.callback(
    Output(...),
    Input(...)
)
def callback1(...):
    global_deal_func()
    ...

@app.callback(
    Output(...),
    Input(...)
)
def callback2(...):
    global_deal_func()
    ...

@app.callback(
    Output(...),
    Input(...)
)
def callback3(...):
    global_deal_func()
    ...

Thank you for your explanation. I have updated the example code. In both ways, the global_deal_func function is required. In fact, it reduces the number of state orchestration. I agree with your point that the callback role is not clear enough. I just hope to provide another way to deal with this issue. When faced with a large number of callbacks, I really don't want to assign the same State role in every callback. If you can retrieve component property values by writing code only once, it feels quite good.

insistence commented 2 months ago

@T4rk1n Excuse me, may I know your opinion?

T4rk1n commented 2 months ago

It would make sense if the callbacks were async based, so only for background callbacks that takes a longer times you might want the latest values after running for a while. The implementation would be kinda slowish, included in the request pooling loop.

For regular callbacks there is no way to make it work with a single request.

insistence commented 2 months ago

It would make sense if the callbacks were async based, so only for background callbacks that takes a longer times you might want the latest values after running for a while. The implementation would be kinda slowish, included in the request pooling loop.

For regular callbacks there is no way to make it work with a single request.

Thank you for your detailed answer. If it can be implemented under limited conditions, I think it would still be a great feature. 😄

deadkex commented 2 months ago

You could try patching the app.callback function to always take the Store as last Input