Embed Panel in fastApi

The first step to get it added to the Panel docs would be to open a feature request on GitHub.

I think it would be helpful if you describe one or more ways it could be added in the request.

My main question is if the Panel docs mainly should refer to your doc and example or whether the example should be included in the gallery of Panel. Personally I think it makes sense to add it similarly to adding flask and django examples.

In the latter case it would be added via a PR.

Really great writeup @t-houssian! Agree with Marc. I’d probably like to have some form of nesting of the TOC tree in the user guide and then put Flask, Django and Fast API guides under a header about “Embedding Panel Apps”. Let’s start with a PR and then we can iterate.

2 Likes

Thanks for the response and feedback! As for your questions:

  1. For this example I used a thread. I tried using panel serve at first but couldn’t quite get that working, any pointers on what I’d need to do?

  2. Our case for using fastApi with panel is to embed the panel app in our internal automation app that we’ve been building out with fastApi. We wanted to keep using fastApi to take advantage of it’s speed and extensive framework, but also wanted to use the data visualization tools in panel.

  3. I didn’t realize their was a panel server class in panel. I will have to check that out.

Maybe could you help me re-write this part to use panel instead of bokeh:

def bk_worker():
    server = Server({'/app': createApp},
        port=5000, io_loop=IOLoop(), 
        allow_websocket_origin=["*"])

    server.start()
    server.io_loop.start()

th = Thread(target=bk_worker)
th.daemon = True
th.start()

Here is what I already tried:

pn.serve({'/app': createApp},
        port=5000, address="127.0.0.1",
         websocket_origin=["*"]
1 Like

Thanks, sounds good! I will work on getting a PR up.

Hi @t-houssian

Could you try if replacing from bokeh.server.server import Server with from panel.io.server import Server works?

Ah, yes, that did work!

1 Like

Could you @philippjfr confirm that it would be the right Server to use? Thanks.

(I’m also using the Panel Server here https://github.com/ideonate/bokeh-root-cmd/blob/c26eee1414d3305749a8724b8740d9a4eaca0cf7/bokeh_root_cmd/main.py#L169).

i followed @t-houssain following link and shifted my three panel app in fastApi server

and Now my dashboards is blazing fast and upload in browsers in less then a minute.

Thanks @t-houssian

one problem i am facing

if two people access the same dashbaord on their browsers they work asynchronously.
i.e. if one access any page on its browser other person’s dashboard which is loaded in browser updated to same page

4 Likes

Hi @khannaum

Could you post a minimum reproducible example?

Hi @khannaum I’m glad you were able to get this up and running. I ran into a similar issue a little bit ago when using a django server with panel. For me the issue came from using widgets as class variables. I had to only use params as class variables within my Parameterized class. Could you share what your code looks like in your panel.py files?

1 Like

@t-houssian @Marc
Thanks for the e-mails

I donot test it with django server and but it’s loading time amazing fast in fastApi.

iI donot understand how it has slow loading-rendering time in browsers when run from panel command line

You can view the code and working example i mentioned in following below my previous post mentioned slow loading-rendering-time-in-browser .

Br,
Nauman Ahmad Khan

1 Like

@khannaum Thanks for sharing your code. It looks like in your cust_mapping function there you will need to change your widgets that are set to variables like here

RBU_select = pn.widgets.Select(name='RBUs', options=list(cust_group_by_work_projected.RBU.unique()))
City_select =  pn.widgets.Select(name='CityLevel', options=list(sorted(cust_group_by_work_projected.cityname.unique())))
Complaint_select = pn.widgets.MultiSelect(name='Complaints', options=list(sorted(cust_group_by_work_projected.compl.unique())))

To params like this:

RBU_select = param.ObjectSelector(label='RBUs', objects=list(cust_group_by_work_projected.RBU.unique()))
City_select =  param.ObjectSelector(label='CityLevel', objects=list(sorted(cust_group_by_work_projected.cityname.unique())))
Complaint_select = param.ListSelector(label='Complaints', objects=list(sorted(cust_group_by_work_projected.compl.unique())))

For the multi select, you can also render the equivalent param.ListSelector like this:

widgets = pn.WidgetBox(City_select,pn.Param(apex.param.Complaint_select, widgets={'Complaint_select': pn.widgets.CheckBoxGroup}),

To get the same functionality.
I think after converting some of your widgets into params and possibly using a parameterized class that might help your mutliple users problem (At least for me it did). Check out the guide on this example to maybe get some ideas on refactoring your code.
https://panel.holoviz.org/gallery/param/reactive_tables.html#param-gallery-reactive-tables

3 Likes

@Marc I ended up getting it to work without a thread and without the middleware. Here is what main.py looks like now:

import panel as pn
from bokeh.embed import server_document
from fastapi import FastAPI, Request
from fastapi.templating import Jinja2Templates

from panelApps.pn_app import createApp

app = FastAPI()
templates = Jinja2Templates(directory="templates")


@app.get("/")
async def bkapp_page(request: Request):
    script = server_document('http://127.0.0.1:5000/app')
    return templates.TemplateResponse("base.html", {"request": request, "script": script})

pn.serve({'/app': createApp},
        port=5000, allow_websocket_origin=["127.0.0.1:8000"],
         address="127.0.0.1", show=False)

and pn_app.py simply is:

import panel as pn

from .app1 import SineWave

def createApp():
    sw = SineWave()
    return pn.Row(sw.param, sw.plot).servable()

Full code here https://github.com/t-houssian/FastAPI-Panel

5 Likes

Beautiful :heart:

Question: should panel not be running in a separate process and file?

1 Like

Possibly, what exactly do you mean by that?

Seems like there are already solutions here, but, FWIW, I’m accomplishing something similar using Nginx as a reverse proxy:

nginx.conf

events {}
http {
  server {
    listen 1001;  # The port where clients should connect

    location /api/ {  # forward requests with this prefix to FastAPI (or any other app)
      rewrite                     ^/api/(.*)$ /$1 break;
      proxy_pass                  http://127.0.0.1:1002;  # The other app should be listening here
      proxy_set_header            Host $host;
      proxy_set_header            X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header            X-Forwarded-Proto $scheme;
      proxy_redirect              off;
    }

    location / {  # forward the rest to Panel
      proxy_pass                  http://127.0.0.1:1003;  # Panel should be listening here
      proxy_set_header            X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header            X-Forwarded-Proto $scheme;
      proxy_set_header            Host $host;
      proxy_redirect              off;
      proxy_buffering             off;
      proxy_http_version          1.1;
      proxy_set_header            Upgrade $http_upgrade;
      proxy_set_header            Connection "Upgrade";
    }
  }
}

For FastAPI, you’ll have to set the root_path to /api.

If running in something like Docker, it might also be helpful to use something like supervisord to manage all three processes:

supervisord.conf

[supervisord]
user = root
nodaemon = true  # this expects supervisord to be run as the main command in a Docker container
logfile = /dev/null  # don't log to a file - only stdout/stderr
logfile_maxbytes = 0  # disable log file rotation

[program:fastapi]
command = gunicorn --bind 0.0.0.0:1002 ...  # However you want to run FastAPI

startsecs = 5
autostart = true
autorestart = true
stopwaitsecs = 300
stderr_logfile = /dev/stderr
stderr_logfile_maxbytes = 0  # disable log file rotation
stdout_logfile = /dev/stdout
stdout_logfile_maxbytes = 0  # disable log file rotation

[program:panel]
command = panel serve --port 1003 --allow-websocket-origin ...  # However you want to run Panel

startsecs = 5
autostart = true
autorestart = true
stopwaitsecs = 300
stderr_logfile = /dev/stderr
stderr_logfile_maxbytes = 0  # disable log file rotation
stdout_logfile = /dev/stdout
stdout_logfile_maxbytes = 0  # disable log file rotation

[program:nginx]
command = nginx -c /path/to/nginx.conf -g 'daemon off;'

startsecs = 5
autostart = true
autorestart = true
stopwaitsecs = 300
stderr_logfile = /dev/stderr
stderr_logfile_maxbytes = 0  # disable log file rotation
stdout_logfile = /dev/stdout
stdout_logfile_maxbytes = 0  # disable log file rotation

Might be more moving parts and more processes to monitor… but I imagine it should work for any framework, not just FastAPI.

6 Likes

@t-houssian @Marc @philippjfr thank you all, this is very useful and I have been using it, as described in the online doc at Integrating Panel with FastAPI — Panel v1.3.9a1 (holoviz.org).

However, I have come up with an issue for me.

My app has a large amount of shared state. This particular piece of data is purely read-only, and needs to be read exactly once. Ideally, I would read and cache it during the startup of the FastAPI server, and then reuse it for all apps and all documents.

Of course, my app also has a smaller amount of dynamic things to do, which do not need to be in the shared state.

Now, here is my problem. I started up the panel server with pn.serve, as described. But I do not know how to pass a reference to the shared state from the main FastAPI startup code to the panel createApp function.

I did find a workaround: I put the createApp function in a separate python file and did initializations in it’s global space, before the createApp function definition. This works!

But it only allows me to create global variables for the panel server’s createApp function, and I may need the global variables to be available to some of my other FastAPI-based functionality.

Any ideas please?

Thank you!

GPN

Does putting things in pn.state.cache work?

@philippjfr Thank you! I was not aware of the pn.state object. I looked for documentation and I found documentation from version 0.14:

Session State and Callbacks — Panel v0.14.0 (holoviz.org)

Is this 0.14 doc up to date, or should I look elsewhere?

Thank you
GPN

Most up to date docs are at https://panel.holoviz.org/

  • pn.state is described here
  • pn.state.cache is described here
1 Like