Dynamically creating callbacks for a dynamically created layout

What I am attempting to build:

A platform for an analyst to upload sql code and define certain parts of the sql code as variables. When the analyst uploads the code, I want to be able to create a dash applications which gives anyone the ability to choose the variables they want and run the code.

I have created widgets for various variables that someone might want to use:
Here is the example of variables:

variable_widgets={
“single_date”:dcc.DatePickerSingle(
month_format=‘MMM Do, YY’,
date=dt.now(),
placeholder=‘MMM Do, YY’,
),
“text”:dcc.Input(
placeholder=‘Enter a value…’,
type=‘text’,
),
}

Here is the code to create the dash layout:
def create_template(var,job_id,job_name):
variable_list = []
for idx, i in enumerate(var):
if i in variable_widgets.keys():
print(i)
print(idx)
y = variable_widgets[i]
y = y.to_plotly_json()
y[‘props’][‘id’] = ‘job_{}var{}’.format(job_id,idx)
variable_list.append(y)
else:
print(“DID NOT MATCH”)
template = html.Div(
[
html.H1(job_name),
html.Div(id=‘job_{}’.format(job_id),children=variable_list, className= ‘row’),
html.Div(‘Email’,className=‘row’),
html.Div([
html.Div([dcc.Input(type=‘text’,id=‘job_id_{}email’.format(job_id))],className=‘three columns’),
html.Div([html.Button(‘submit request’,id='job_id
{}_button’)],className=‘three columns’),
],className=‘row’)
]
)
print(template)
return template

And here is what the output of the layout will look like.
image

My next step is to save the dash layout in a file. I’m saving all the layouts within one file and assigning them to a variable so they can be called dynamically.

All the templates created will be part of one multi page app.

I am planning to create one file with all the callbacks. Where I am stuck at is how to dynamically create the callbacks associated to each application and save that to a python file.

to generate callbacks dynamically, you can use something like this:

        def create_callback(output_element,retfunc):
            """creates a callback function"""
            def callback(*input_values):
            print ('callback fired with :"{}"  output:{}/{}'.format(input_values,output_element.component_id,output_element.component_property ))
            if input_values is not None and input_values!='None':
                try:
                    retval = retfunc(*input_values)
                except Exception as e:
                    exc_type, exc_obj, exc_tb = sys.exc_info()
                    fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
                    print('Callback Exception:',e,exc_type, fname, exc_tb.tb_lineno)
                    print('parameters:',*input_values)
                return retval
            else:
                return []
        return callback

then register callbacks by:

    dyn_func = create_callback(Output('elem','attr'),somefunction)
    # app referencing the dash app object
    app.callback(output=OutoputObj(),
                       inputs=[InputObjects()...],
                       state=[StateObjects() if any...],
                       events=[EventObjects()... if any]
                      )(dyn_func)

the way i use it in my multi page app, each dash app is a class, which defines a layout, and a list of callback configs, i then iterate over the callbacks list and register the callbacks

example:
callbacks=[
  (Output(someoutputelement), [Input(some input element),...], [] , [], some_function),
  ...
]
# replace [] and [] with state and event objects if needed
6 Likes

That is neat! I might check this out for my own app. Do you have any implementation example you can show perhaps?

next time i’m near my compute i’ll share the main super class

2 Likes

Appreciate the reply @yosiz0! Working on implementing your proposed solution.

1 Like

Nice! If I’ll make my version work I’ll also share the code.

1 Like

here is most of my baseApp class , every app is a class which inherits from this one:
feel free to improve :):hugs: and/or suggest better approaches

i’m using this with the great example of ead and nedned of running dash in django

import dash_html_components as html
import dash_core_components as dcc
from dash.dependencies import Input, Output, State, Event
from flask_caching import Cache

import zlib
import sys, os, traceback
import logging

class baseApp(object):
    def __init__(self, app, server,name='App', title='Application',ctx='',loglevel=logging.ERROR,use_cache=False,app_path='./data'):
        self.app = app
        self.server = server
        self.name = name
        self.title = title
        self.ctx = ctx
        self.logger = logging.getLogger(__name__ + '.'+ self.__class__.__name__)
        logging.basicConfig(filename=ctx+'.'+__name__ + '.'+ self.__class__.__name__+'.log', filemode='w',
        level=loglevel,format='%(asctime)s %(levelname)s:%(message)s', datefmt='%d/%m/%Y %H:%M:%S ')
        self.logger.debug('init app, ctx:{ctx}'.format(ctx=self.ctx))
        self.store = {}
        if use_cache is True:
            self.init_cache()
        
        self.datastore = app_path + 'data/'
        if self.datastore is not None and len(self.datastore) > 1:
            os.makedirs(self.datastore,exist_ok=True)

    def init_cache(self):
        self.cache = Cache(self.app.server,config={'CACHE_TYPE': 'filesystem','CACHE_DIR':'./cache/'+self.name})
        return self

    def header(self):
        return  html.Div([
            html.H1(self.title,className='display-4')
        ],className='jumbotron')

    def footer(self):
        return html.Footer([
                    html.Div([
                        html.Span(['ps tools (c)'],className='text-muted')
                        ],className='container')],className='footer')

    def getComponentId(self,name):
        return '{name}_{ctx}'.format(name=name,ctx=self.ctx)

    def register_callbacks(self,callbacks):
        print('registering {} callbacks for {}'.format(len(callbacks),self.name))
        
        for callback_data in callbacks:
            # self.logger.debug('%s] callback_data[0]: %s',self.ctx,callback_data[0])
            # self.logger.debug('%s] callback_data[1]: %s',self.ctx,callback_data[1])
            # self.logger.debug('%s] callback_data[2]: %s',self.ctx,callback_data[2])
            # self.logger.debug('%s] callback_data[3]: %s',self.ctx,callback_data[3])
            # self.logger.debug('%s] callback_data[4]: %s',self.ctx,callback_data[4])
            
            dynamically_generated_function = self.create_callback(callback_data[0],callback_data[4])
            #callback_data[2]
            self.app.callback(output=callback_data[0], inputs=callback_data[1],state=callback_data[2],events=callback_data[3])(dynamically_generated_function)



    def print_exception(self,e,ctx,name,*params):
        print('[{ctx}] Exception in {name} : {msg}'.format(ctx=ctx),name=name,msg=e)
        print('[{ctx}] Exception parameters:'.format(ctx=ctx),*params)
        exc_type, exc_obj, exc_tb = sys.exc_info()
        traceback.print_tb(exc_tb)

    def create_callback(self,output_element,retfunc,name='callback'):
        """creates a callback function"""
        def callback(*input_values):
            print ('callback fired with :"{}"  output:{}/{}'.format(input_values,output_element.component_id,output_element.component_property ))
            retval = []
            if input_values is not None and input_values!='None':
                try:
                    retval = retfunc(*input_values)
                except Exception as e:
                    exc_type, exc_obj, exc_tb = sys.exc_info()
                    fname = traceback.extract_tb(exc_tb, 1)[0][2]
                    filename = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
                    print('Callback Exception:',e,exc_type, filename, exc_tb.tb_lineno, fname)
                    print('parameters:',*input_values)
                    traceback.print_tb(exc_tb)
                    
            return retval                    
        return callback


    def define_callback(self,output,input,func=None,state=None,event=None,):
        """defines the callback set"""
        return (
                Output(self.getComponentId(output[0]),output[1]),
                [Input(self.getComponentId(id), attr) for (id,attr) in input],
                [] if state is None else [State(self.getComponentId(id), attr) for (id,attr) in state],
                [] if event is None else [Event(self.getComponentId(id), attr) for (id,attr) in event],
                self.dummy_callback if func is None else func
                )

    def dummy_callback(self,*input_data):
        print('dummy callback with:',*input_data)
        return []
8 Likes

and my app skeleton class:

"""
simple dash app 
 - no cache
 - no database access
"""
from dash.dependencies import Input, Output, State, Event
import dash_html_components as html
import dash_core_components as dcc

import sys, os 
sys.path.append(os.getcwd()+'/lib')

from dash_fs import components as fscomp
from dash_fs.baseApp import baseApp
from . import config as cfg


# 1: imports



class DemoApp(baseApp):
    def __init__(self,app,server):
        self.ctx = cfg.context 
        baseApp.__init__(self,app,server,cfg.name,cfg.title,self.ctx)
        print ('[{}] app initialized: {} (datastore:{})'.format(__name__,self.ctx,self.datastore))

# 2: define layout
    def layout(self):
        """defines the web ui layout"""
        self.initComponents()
        return html.Div([
            self.header(),
            fscomp.get_component('dummyComp')(self.ctx),
            self.footer(),
        ])


# 3. define custom components
    def initComponents(self):
        """define custom ui components"""
        pass

# 4. define callbacks
    def set_callbacks(self):
        """set callbacks for the app, data: [(Output,[Input],[State],[Event],callback_func), ...]"""
        callbacks =[
            self.define_callback(('dummyComp','children'),[('dummyComp','text')],
                func=lambda text: text+' --- demo' if text is not None else  []
                #func=self.dummy_callback
            ),
        ]
        self.register_callbacks(callbacks)


8 Likes

Amazing, thanks! Gonna check it out in the next few days. :slight_smile:

Hello,

I tried implementing your code on a test app i am working on.

I am generating a table from a callback (creating n rows on table B, for every selected row on a datatable).

On this generated table, i’m having a column including dropdown elements, and i need to fire a callback when this dropdown is changed

Creation call from within the callback

    for element in dynamicdropids:
    print("\n\nLine 3081 : element " + str(element))
    dyn_func = create_callback(dash.dependencies.Output('okout'+str(i), 'children'), un_callback)  #, [dash.dependencies.Input("dynamicDrop"+str(element), 'value')])
    # app referencing the dash app object
    app.callback(output=dash.dependencies.Output('okout'+str(i), 'children'), inputs=[dash.dependencies.Input("dynamicDrop"+str(element), 'value')])(dyn_func)

Callback to execute + Create_callback

# callback to execute
def un_callback(*input_data):
    print("Line 3099 : callback fired" + str(input_data))
    return []

def create_callback(output_element, retfunc):
    """creates a callback function"""
    def callback(*input_values):
        print('Line 3102 : callback fired with :"{}"  output:{}'.format(input_values, output_element))
        retval = []
        if input_values is not None and input_values != 'None':
            try:
                retval = retfunc(*input_values)
            except Exception as e:
                exc_type, exc_obj, exc_tb = sys.exc_info()
                fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
                print('Callback Exception:', e, exc_type, fname, exc_tb.tb_lineno)
                print('parameters:', *input_values)
            return retval
        else:
            return []

    return callback

This is what app.callback_map contains

{‘inputs’: [{‘id’: ‘dynamicDrop3213’, ‘property’: ‘value’}], ‘state’: [], ‘events’: [], ‘callback’: <function create_callback..callback at 0x1177e5158>}

Hey
Not sure I understand the question if there is one…

Hi All,

I hope someone can help me. I have an app with several cards being displayed (Divs). They were generated in a for loop and each one has it’s id. Now, I want to filter out of the view the cards that are not selected in a dropdown menu. My idea was to use style display:none. However, I am blocked in the for loop I have to perform to create callbacks when there are more than one card selected.
The callback will always update the same property (style) but in different components (div). My code for now is that:

@app.callback(Output('text', 'value'),
             [Input('Button','n_clicks')],
             [State('drop_asset','value')])
def list_asset(clicks, selected_assets):
    assets = df.idAsset.unique()
    indexes = range(0,len(assets))
    if selected_assets:
        good_idx = [list(assets).index(s) for s in selected_assets]
        bad_idx = [i for i in indexes if i not in good_idx]
        return bad_idx

@app.callback(Output('dummy','value'),
             [Input('Button','n_clicks'),
             Input('text','value')])
def run_filter(n, asset_list):
    if asset_list:
        for ass in asset_list:
            id_div = 'target-%s' % ass
            app.callback(Output(id_div, 'style'),
                          [Input('dummy','value'),
                          Input('Button','n_clicks')])(
                                {'display':'none'})

It is not working but it does not raise any error either. The first callback updates a dummy div with the index to be filtered out, the second is a trigger (I’ve also tried without this callback) and the third one, inside the loop is in the one which actually should change the style property.
Any idea what is wrong here?
I’m relatively new to callbacks so any comment is welcome.

Thank you very much!

1 Like

just to be registered I figured out a solution:

def generate_callback(i):
    def change_style(inp,n,list_assets):
        if i in inp:
            return {'display':'none'}
        else:
            return {'display':'block'}
    return change_style

[app.callback(Output('target-%s' % i,'style'),
            [Input('data-holder','children'),Input('Button','n_clicks'), Input('dummy','children')]) (generate_callback(i)) 
                                                                             for i in app.layout['dummy'].children]

I am not sure if it is the best, but it is working for now.

2 Likes

Hi!

Thanks for posting your code, looks like it could be really helpful.

Maybe a dumb question, but in your imports, where do dash_fs and config come from?

Thanks!

hey,

a bit late on the reply, but still :slight_smile:

dash_fs is part of the framework i’ve build to handle some of the other stuff , it’s not required for this.

We can now declare multiple Output in a single callback. How would you update register_callbacks to accommodate multiple Output? Just running a loop that registers the callback for each Output individually, writing something like

def register_callbacks(self, callbacks):
    for callback_data in callbacks:
        for output in callback_data[0]:

and continuing as per yosiz0 with only output replacing callback_data[0]? Or is there a proper other way?
And, thanks yosiz0 for posting! Helped a lot.

Sorry, multiple output works just fine even in a dynamic callback, but, of course, I need to return the right amount of parameters in a tuple. Sorry, folks.

Hi, a bit doubt, here I’m dynamically generating cards, in card, there would be a link “click here” on clicking on it , the link should replace with input field but some how , it is not calling the method itself - replace_link_with_input(n_clicks):

any help on this ?

def make_card(linkVar,index):
return dbc.Card(
[
dbc.Row(
[

                dbc.Col(
                    dbc.CardBody(
                        [
                            
                            dbc.Row([
                                dbc.Col([
                                    html.A(
                                        "Click here",
                                        id={"type": "update-link", "index": index},
                                        href=linkVar,
                                        target="_blank",
                                        style={"display": "block"},
                                        n_clicks=0
                                    ) if linkVar else None,
                                    dbc.Input(
                                        id={"type": "update-input", "index": index},
                                        type="text",
                                        placeholder="Enter Name",
                                        style={"display": "none"}
                                    ),
                                ],
                                    width=2
                                )
                            ]), 

                        ]
                    ),
                ),
            ],
        )

    ],
    className="w-100 mb-3",
    id={"type": "card", "index": index}
)

@app.callback(
[
Output({“type”: “update-link”, “index”: MATCH}, “style”),
Output({“type”: “update-input”, “index”: MATCH}, “style”)
],
[Input({“type”: “update-link”, “index”: ALL}, “n_clicks”)],

[State({“type”: “card”, “index”: ALL}, “id”)]

)
def replace_link_with_input(n_clicks):
index = int(ctx.triggered[0][‘prop_id’].split(‘,’)[1].split(‘:’)[1])

print(ctx.triggered[0]['prop_id'])
output_styles = [
    {"display": "none"} if "update-link" in ctx.triggered[0]['prop_id'] else {"display": "block"},
    {"display": "block"} if "update-link" in ctx.triggered[0]['prop_id'] else {"display": "none"}
]
return output_styles