General questions about callbacks

I have general questions about how callbacks work. As per examples on Plotly website, a common convention (or maybe correct way) of writing callbacks looks like this:

@app.callback(
    Output('graph-with-slider', 'figure'),
    Input('year-slider', 'value'))
def update_figure(selected_year):
    filtered_df = df[df.year == selected_year]

    fig = px.scatter(filtered_df, x="gdpPercap", y="lifeExp",
                     size="pop", color="continent", hover_name="country",
                     log_x=True, size_max=55)

    fig.update_layout(transition_duration=500)

    return fig

My questions are:

  1. Do I have to always define functions below callbacks? Does it matter where I place my code associated with callback?

  2. Can I save functions in another file and import them in app.py file under callback?
    For example, can the code above be written like this:

import functions

@app.callback(
    Output('graph-with-slider', 'figure'),
    Input('year-slider', 'value'))
functions.function1(selected_year)
  1. Is it possible to define a function with *args or **kwargs? I’m asking this because, when I define a function explicitly with arguments, callback expects that those arguments are not empty from the beginning. I know, that I can avoid it by adding a control like this if argument is not None:, but it is not convenient.

HI @parvizalizada

  1. as far as I know, the decorator expects a def statement so what you propose won’t work.

  2. You could wrap your imported function. I did something like this in the past. It’s maybe not the best way to do it, but it works

@app.callback(
    Output(...),
     Input(...)
)
def update(*args):
    # which input type triggered the callback?
    trigger = ctx.triggered_id['type']
    
    extended_args = [arg for arg in args]
    extended_args.append(trigger)
    
    return imported_function(*extended_args)

It’s an interesting question and maybe there are others on the forum with better ideas.

@AIMPED , thanks for your answers. Regarding the second point, I wanted to avoid defining functions explicitly (i.e., def some_function:). But if it is the way callbacks work, the code will still look complicated, keeping all these defs in one file and having many callbacks.

hi @parvizalizada ,
You can define your callback without decorations. Just like this,
app.callback()(your_func)
Or
app.callback()(lambda x: your_func(x))

1 Like

hi @stu
I’m not sure if I understand. Do you mean this is the syntax:

import functions

app.callback()(functions.function1())

no, don’t pass in the instance, just pointer.

import functions

app.callback()(functions.function1)

1 Like

Hi there,

I’m aware that I’m reviving an old topic, but I rarely see this syntax being used.

With more compex callbacks or bigger apps, it could be convenient to group certain functions into different modules to help with code readibility.

So my question is, if there is any inconvenience caused by not using the decorator?

1 Like

hi bro,
In fact, this does not break away from using decorators, it just takes away the sugar ‘@’. The real decorator name is wrap_func, and the parameters it passes in and returns are function pointers. That’s why you can see two pairs of brackets together. I usually use this syntax when I need a lambda there, but I’m more likely to write some arrow functions to a client callback.

1 Like

Thanks for your answer. What are your referring to with this ^^ ?

just like this example,