nebari-dev / jhub-apps

Application creator and general launcher for JupyterHub
https://jhub-apps.nebari.dev/
BSD 3-Clause "New" or "Revised" License
19 stars 11 forks source link

[DOC] Syntax for deploying Panel apps #361

Open kcpevey opened 1 week ago

kcpevey commented 1 week ago

Preliminary Checks

Summary

There are some usecases in Panel which don't directly support the .servable() approach to deploying.

For those that DO support .servable() putting it in a python file protect by main works well:

if __name__ == "__main__":
    app = pn.Column('stuff')

    app.panel().servable()

However, in the case of pipelines, direct usage of .servable() is not possible and the docs suggest using pn.serve. As far as I can tell, jhub-apps doesn't support this approach (or I couldn't sort out the syntax while testing). Instead, I needed to use pn.panel(pipeline).servable().

HOWEVER, this could not be within the protected main. For example this works:

import param
import panel as pn
pn.extension()

class Stage1(param.Parameterized):

    a = param.Integer(default=2, bounds=(0, 10))
    b = param.Integer(default=3, bounds=(0, 10))

    def view(self):
        return pn.Column('some stuff')

    def panel(self):
        return pn.Row(self.param, self.view)

class Stage2(param.Parameterized):

    c = param.Integer(default=6, bounds=(0, None))
    exp = param.Number(default=0.1, bounds=(0, 3))

    def view(self):
        return pn.Column('some other stuff')

    def panel(self):
        return pn.Row(self.param, self.view)

pipeline = pn.pipeline.Pipeline(debug=True)
pipeline.add_stage('Stage 1', Stage1)
pipeline.add_stage('Stage 2', Stage2)

pn.panel(pipeline).servable()

But this doesn't work:

import param
import panel as pn
pn.extension()

class Stage1(param.Parameterized):

    a = param.Integer(default=2, bounds=(0, 10))
    b = param.Integer(default=3, bounds=(0, 10))

    def view(self):
        return pn.Column('some stuff')

    def panel(self):
        return pn.Row(self.param, self.view)

class Stage2(param.Parameterized):

    c = param.Integer(default=6, bounds=(0, None))
    exp = param.Number(default=0.1, bounds=(0, 3))

    def view(self):
        return pn.Column('some other stuff')

    def panel(self):
        return pn.Row(self.param, self.view)

if __name__ == "__main__":
    pipeline = pn.pipeline.Pipeline(debug=True)
    pipeline.add_stage('Stage 1', Stage1)
    pipeline.add_stage('Stage 2', Stage2)

    pn.panel(pipeline).servable()

There are errors in the logs which may be relevant:

  |   | 2024-06-28 13:53:39 | [E 240628 13:53:39 proxyhandlers:781] b'DEBUG:bokeh.document.modules:Deleting 0 modules for document <bokeh.document.document.Document object at 0x7f215cb966e0>\n'
-- | -- | -- | --
  |   | 2024-06-28 13:53:39 | [E 240628 13:53:39 proxyhandlers:781] b"DEBUG:bokeh.server.contexts:Discarding session 'ERFlWqgjcvtUpJMA1wMT8O0nstdlcgTce0fX3D7avSkT' last in use 15405.79219199717 milliseconds ago\n"
  |   | 2024-06-28 13:53:39 | [E 240628 13:53:39 proxyhandlers:781] b'DEBUG:bokeh.server.contexts:Scheduling 1 sessions to discard\n'
  |   | 2024-06-28 13:53:37 | [E 240628 13:53:37 proxyhandlers:781] b'DEBUG:bokeh.server.tornado:[pid 19]   /ready-check has 1 sessions with 1 unused\n'

  |   | 2024-06-28 13:53:39 | [E 240628 13:53:39 proxyhandlers:781] b'DEBUG:bokeh.document.modules:Deleting 0 modules for document <bokeh.document.document.Document object at 0x7f215cb966e0>\n'
-- | -- | -- | --
  |   | 2024-06-28 13:53:39 | [E 240628 13:53:39 proxyhandlers:781] b"DEBUG:bokeh.server.contexts:Discarding session 'ERFlWqgjcvtUpJMA1wMT8O0nstdlcgTce0fX3D7avSkT' last in use 15405.79219199717 milliseconds ago\n"
  |   | 2024-06-28 13:53:39 | [E 240628 13:53:39 proxyhandlers:781] b'DEBUG:bokeh.server.contexts:Scheduling 1 sessions to discard\n'
  |   | 2024-06-28 13:53:37 | [E 240628 13:53:37 proxyhandlers:781] b'DEBUG:bokeh.server.tornado:[pid 19]   /ready-check has 1 sessions with 1 unused\n'

  |   | 2024-06-28 13:53:39 | [E 240628 13:53:39 proxyhandlers:781] b'DEBUG:bokeh.document.modules:Deleting 0 modules for document <bokeh.document.document.Document object at 0x7f215cb966e0>\n'
-- | -- | -- | --
  |   | 2024-06-28 13:53:39 | [E 240628 13:53:39 proxyhandlers:781] b"DEBUG:bokeh.server.contexts:Discarding session 'ERFlWqgjcvtUpJMA1wMT8O0nstdlcgTce0fX3D7avSkT' last in use 15405.79219199717 milliseconds ago\n"
  |   | 2024-06-28 13:53:39 | [E 240628 13:53:39 proxyhandlers:781] b'DEBUG:bokeh.server.contexts:Scheduling 1 sessions to discard\n'
  |   | 2024-06-28 13:53:37 | [E 240628 13:53:37 proxyhandlers:781] b'DEBUG:bokeh.server.tornado:[pid 19]   /ready-check has 1 sessions with 1 unused\n'

Steps to Resolve this Issue

Add some documentation to explain the recommended approach to deploying these kinds of Panel apps.

kcpevey commented 1 week ago

I have a more complicated example where the equivalent of:

if __name__ == "__main__":
    app = pn.Column('stuff')

    app.panel().servable()

does NOT work - i.e. it "deploys" but just shows a blank screen. I don't understand why there would be a difference though. At any rate, its safe to say that placing .servable inside of __main__ produces mixed results. Its safer to stay away from __main__.