Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python Decorator as Callback in Dash Using Dash Object That is an Instance Variable - Fails

I'm updating some code to use Dash and plotly. The main code for graphing is defined within a class. I replaced some Bokeh widgets with Dash controls, and ended up with a callback that looks like this:

class MakeStuff:
    def __init__(self, ..., **optional):
        ...
        self.app = dash.Dash(...)
        ...

    @self.app.callback(
    dash.dependencies.Output('indicator-graphic', 'figure'),
        [dash.dependencies.Input('start-time-slider', 'value'),
         dash.dependencies.Input('graph-width-slider', 'value')]
        )
    def update_graphs(self,range_start,graph_width):
        print(...)

I am following some examples from the Dash website. I was able to run the examples, including callbacks. In my code, without the decorator, the code runs without error, producing the graphics and controls as I expected it to. (Of course, the code is incomplete, but there is no error.) When I include the decorator, I get this error:

NameError: name 'self' is not defined

I tired it this way, first, just mimicking the code examples:

class MakeStuff:
    def __init__(self, ..., **optional):
        ...
        app = dash.Dash(...)
        ...

    @app.callback(
    dash.dependencies.Output('indicator-graphic', 'figure'),
    [dash.dependencies.Input('start-time-slider', 'value'),
     dash.dependencies.Input('graph-width-slider', 'value')]
    )
    def update_graphs(self,range_start,graph_width):
        print(...)

Of course, the variable "app" is only know within the scope of the init function, so it's no surprise that that doesn't work, giving the similar error:

NameError: name 'app' is not defined

Is there a straightforward way to set up this decorator to work while still keeping my code within a class definition? I am guessing some pre-processing is going on with the decorator, but I don't understand it well enough to come up with a solution.

like image 746
Jim Avatar asked Feb 17 '19 02:02

Jim


2 Answers

You could call the callback function not as a decorator, as shown in this answer. This should work from within your __init__ function:

class MakeStuff:
    def __init__(self, ..., **optional):
        ...
        self.app = dash.Dash(...)
        self.app.callback(
            dash.dependencies.Output('indicator-graphic', 'figure'),
            [dash.dependencies.Input('start-time-slider', 'value'),
             dash.dependencies.Input('graph-width-slider', 'value')]
            )(self.update_graphs)
        ...

    def update_graphs(self,range_start,graph_width):
        print(...)

I've never tried it with a class instance before, but see no reason for it not to work.

like image 182
Shovalt Avatar answered Sep 28 '22 10:09

Shovalt


ned2 provides a solution here, he uses the following structure to set up the decorators within a class definition.

class BaseBlock:
def __init__(self, app=None):
    self.app = app

    if self.app is not None and hasattr(self, 'callbacks'):
        self.callbacks(self.app)

class MyBlock(BaseBlock):
    layout = html.Div('layout for this "block".')

    def callbacks(self, app):

        @app.callback(Output('foo', 'figure'), [Input('bar')])
        def do_things(bar):
            return SOME_DATA

        @app.callback(Output('baz', 'figure'), [Input('boop')])
        def do_things(boop):
            return OTHER_DATA

# creating a new MyBlock will register all callbacks
block = MyBlock(app=app)

# now insert this component into the app's layout 
app.layout['slot'] = block.layout
like image 31
TomasSalvadores Avatar answered Sep 28 '22 09:09

TomasSalvadores