holoviz / panel

Panel: The powerful data exploration & web app framework for Python
https://panel.holoviz.org
BSD 3-Clause "New" or "Revised" License
4.77k stars 519 forks source link

`pn.state.served` value when accessed from outside of the served file? #5623

Open maximlt opened 1 year ago

maximlt commented 1 year ago

Should pn.state.served be limited to returning True only if it's accessed from the served file? I see it more as a context value that should be True wherever I access it from, be it in the served file or one of its imported modules. However, its implementation suggests it's limited to the served file:

        try:
            return inspect.stack()[1].frame.f_globals['__name__'].startswith('bokeh_app_')
        except Exception:
            return False

Motivated by https://discourse.holoviz.org/t/how-do-i-know-if-im-in-serve-context-outside-main-module/6177/2

MarcSkovMadsen commented 1 year ago

The problem with pn.state.served is also that you need panel imported. I have many examples where I don't need panel imported unless the document is served. Then I import Panel, do pn.extension and some more things. So in many places I stick to if __name__.startswith("bokeh"):

philippjfr commented 1 year ago

There is a good reason for this, which is that the served property only ever makes sense in the context of the served application module. Any module you import inside an application will only ever be evaluated once (on the first import) and any subsequent import will simply load the cached module. Therefore there is no meaning to pn.state.served outside the context of the application module.

philippjfr commented 1 year ago

Will reopen, to document this properly.

philippjfr commented 1 year ago

The problem with pn.state.served is also that you need panel imported. I have many examples where I don't need panel imported unless the document is served. Then I import Panel, do pn.extension and some more things. So in many places I stick to if name.startswith("bokeh"):

I struggle seeing this as a problem. pn.state.served is there for convenience and readability, if you don't import Panel you can't expect that convenience.

maximlt commented 1 year ago

Therefore there is no meaning to pn.state.served outside the context of the application module.

I get what you mean,pn.state.served will be False if accessed from the top-level context of an imported module. But shouldn't we expand it to return True if pn.state.curdoc is not None? To cover cases like the following:

# app.py
import panel as pn

from util import import_debug

def script_debug(event):
    print(f'{pn.state.served}')

b1 = pn.widgets.Button(name='Script debug')
b1.on_click(script_debug)
b2 = pn.widgets.Button(name='Import debug')
b2.on_click(import_debug)

pn.Row(b1, b2).servable()
# Clicking b1 will print True, b2 will print False.

with

# util.py
import panel as pn

def import_debug(event):
    print(f'{pn.state.served}')
philippjfr commented 1 year ago

I think pn.state.curdoc is None is not a sufficient condition for served to be true, no. Specifically that would mean:

# util.py
import panel as pn

if pn.state.served:
    some_side_effect()

would be treated as True but then never re-execute causing potentially very hard to catch issues because in a testing setup everything looks fine but as soon as you load the app a second time the side-effects wouldn't re-run. pn.state.served specifically is designed to detect that you are inside the served app module and should be explained like that. I have no objection to adding a separate pn.state.in_session_context which internally just evaluates pn.state.curdoc is not None.

philippjfr commented 1 year ago

To be very clear pn.state.served could (and maybe should?) have been pn.state.in_served_app_module.

maximlt commented 1 year ago

Thanks for the clarification, that's a gotcha! I definitely need to check some internal code, I could have easily introduced some bug using pn.state.served from outside the main module.

As if pn.state.served is meant to be used somewhat in place of the traditional if __name__ == '__main__', it could have been been named pn.state.in_main_served.

philippjfr commented 1 year ago

I could have easily introduced some bug using pn.state.served from outside the main module.

Doesn't seem super likely since whatever statement you guarded would never have executed which you presumably would have noticed. The real danger really would have been if it were True the first time.