GibbsConsulting / django-plotly-dash

Expose plotly dash apps as django tags
MIT License
560 stars 125 forks source link

AttributeError: 'function' object has no attribute 'expanded' #484

Open zvi-quantivly opened 10 months ago

zvi-quantivly commented 10 months ago

I have a requirement to create my Dash application within the context of a class. I tried following the example here to have something like:

from dash import Input, Output, State, dcc, html
from django_plotly_dash import DjangoDash

class MyClass(Base):
    def __init___(self, *args, **kwrags):
        self.app = DjangoDash("name")
        plot = dcc.Graph(id="my-plot")
        store = dcc.Store(id="my-store")
        div = html.Div("my-div", children=[])
        self.app.layout = html.Div(children=[plot, store, div])
        self.app.callback(Output("my-plot", "figure"), Input("my-store", "data"))(self.update_plot)

    def update_plot(self, data):
        ...
        return a_figure

However, this raises:

File "/usr/local/lib/python3.9/site-packages/django_plotly_dash/views.py", line 75, in update
     return _update(request, ident, stateless, **kwargs)
File "/usr/local/lib/python3.9/site-packages/django_plotly_dash/views.py", line 95, in _update
     resp = app.dispatch_with_args(request_body, arg_map)
File "/usr/local/lib/python3.9/site-packages/django_plotly_dash/dash_wrapper.py", line 699, in dispatch_with_args
     if callback.expanded is not None:
AttributeError: 'function' object has no attribute 'expanded'

Since self.app is a DjangoDash instance, so far, I haven't managed to figure out why expanded isn't set. Any help resolving this would be greatly appreciated.

EDIT: It seems it is caused by the inclusion of self (i.e., static methods are not effected).

zvi-quantivly commented 10 months ago

I tried excluding self inside DjangoDash.get_expanded_arguments() (see here). Unfortunately, there was no difference.

GibbsConsulting commented 10 months ago

@zvi-quantivly what is the driver behind wanting to embed the DjangoDash instance inside a class? In particular, is there something specific that leads you to this code structure?

zvi-quantivly commented 10 months ago

Yes. It's a little difficult to explain briefly, but I am working on a Django project that needs to be able to run user provided code as a “plugin”. Each plugin is written as a small Python package that depends on a common SDK and exposes a subclass of some base Plugin class (from the SDK). These plugin subclasses are dynamically imported, initialized, and executed by the project. The initialization procedures populate specific plugin attributes with endpoints and authorization information that is required for its execution.

E.g., if we consider the example above, Base provides something like self.run_query(), which uses an attribute such as self.server_address, which is populated by the Django project as part of the initialization of a plugin. The self.update_plot() method needs to be able to use self.run_query() to run. HTH.

zvi-quantivly commented 10 months ago

It looks like the source of the problem is that setting method attributes on bound methods is disallowed [ref]. This happens in the custom callback() decorator's func.expanded assignment (here) whenever self.app.callback(...)(self.some_method) is called (in which case, func is a bound method). I still haven't found a solution for this. Now trying to think whether function objects (see second example here) might somehow work.