FastAPI integration#
Panel usually runs on a Bokeh-Server, which in turn runs on Tornado. However, it is also often useful to embed a Panel app into a large web application, such as a FastAPI web server. Integration with FastAPI is easier compared to others such as Flask, as it is a more lightweight framework. Using Panel with FastAPI requires only a little more effort than notebooks and Bokeh servers.
Configuration#
Before we start adding a Bokeh application to our FastApi server, we need to set
up some of the basic configuration in fastAPI/main.py
:
First, we import all the necessary elements:
fastAPI/main.py#1import panel as pn 2 3from bokeh.embed import server_document 4 5from fastapi import FastAPI, Request 6from fastapi.templating import Jinja2Templates
Next, we define
app
as an instance ofFastAPI
and define the path to the template directory:10app = FastAPI() 11templates = Jinja2Templates(directory="templates")
Now we create our first routine via an asynchronous function and refer it to our BokehServer:
14@app.get("/") 15async def bkapp_page(request: Request): 16 script = server_document("http://127.0.0.1:5000/app") 17 return templates.TemplateResponse( 18 "base.html", {"request": request, "script": script} 19 )
As you can see from the code, a Jinja2 template
fastAPI/templates/base.html
is expected. This can have the following content, for example:fastAPI/templates/base.html#1<!DOCTYPE html> 2<html> 3 <head> 4 <title>Panel in FastAPI: sliders</title> 5 </head> 6 <body> 7 {{ script|safe }} 8 </body> 9</html>
Let’s now return to our
fastAPI/main.py
file to start our bokeh server withpn.serve()
:22pn.serve( 23 {"/app": createApp}, 24 address="127.0.0.1", 25 port=5000, 26 show=False, 27 allow_websocket_origin=["127.0.0.1:8000"], 28)
createApp
calls up our panel app in this example, but this is not covered until the next section.
address
,port
Address and port at which the server listens for requests; in our case
http://127.0.0.1:5000
.show=False
ensures that the Bokeh server is started but is not immediately displayed in the browser.
allow_websocket_origin
lists the hosts that can connect to the websocket. In our example, this should be
fastApi
, so we use127.0.0.1:8000
.
Now we define the
sliders
app based on a standard template for FastAPI apps, which shows how Panel and FastAPI can be integrated:fastAPI/sliders/sinewave.py
a parameterised object that represents your existing code:
fastAPI/sliders/sinewave.py#1import numpy as np 2import param 3 4from bokeh.models import ColumnDataSource 5from bokeh.plotting import figure 6 7 8class SineWave(param.Parameterized): 9 offset = param.Number(default=0.0, bounds=(-5.0, 5.0)) 10 amplitude = param.Number(default=1.0, bounds=(-5.0, 5.0)) 11 phase = param.Number(default=0.0, bounds=(0.0, 2 * np.pi)) 12 frequency = param.Number(default=1.0, bounds=(0.1, 5.1)) 13 N = param.Integer(default=200, bounds=(0, None)) 14 x_range = param.Range(default=(0, 4 * np.pi), bounds=(0, 4 * np.pi)) 15 y_range = param.Range(default=(-2.5, 2.5), bounds=(-10, 10)) 16 17 def __init__(self, **params): 18 super(SineWave, self).__init__(**params) 19 x, y = self.sine() 20 self.cds = ColumnDataSource(data=dict(x=x, y=y)) 21 self.plot = figure( 22 plot_height=400, 23 plot_width=400, 24 tools="crosshair, pan, reset, save, wheel_zoom", 25 x_range=self.x_range, 26 y_range=self.y_range, 27 ) 28 self.plot.line("x", "y", source=self.cds, line_width=3, line_alpha=0.6) 29 30 @param.depends( 31 "N", 32 "frequency", 33 "amplitude", 34 "offset", 35 "phase", 36 "x_range", 37 "y_range", 38 watch=True, 39 ) 40 def update_plot(self): 41 x, y = self.sine() 42 self.cds.data = dict(x=x, y=y) 43 self.plot.x_range.start, self.plot.x_range.end = self.x_range 44 self.plot.y_range.start, self.plot.y_range.end = self.y_range 45 46 def sine(self): 47 x = np.linspace(0, 4 * np.pi, self.N) 48 y = ( 49 self.amplitude * np.sin(self.frequency * x + self.phase) 50 + self.offset 51 ) 52 return x, y
fastAPI/sliders/pn_app.py
creates an app function from the
SineWave
class:fastAPI/sliders/pn_app.py#1import panel as pn 2 3from .sinewave import SineWave 4 5 6def createApp(): 7 sw = SineWave() 8 return pn.Row(sw.param, sw.plot).servable()
Finally, we return to our
fastAPI/main.py
and import thecreateApp
function:fastAPI/main.py#4from sliders.pn_app import createApp
The file structure should now look like this:
fastAPI
├── main.py
├── sliders
│ ├── pn_app.py
│ └── sinewave.py
└── templates
└── base.html
You can now start the server with:
$ bin/uvicorn main:app --reload
INFO: Will watch for changes in these directories: ['/srv/jupyter/jupyter-tutorial/docs/web/dashboards/panel/fastAPI']
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Started reloader process [218214] using StatReload
Launching server at http://127.0.0.1:5000
INFO: Started server process [218216]
INFO: Waiting for application startup.
INFO: Application startup complete.
You should then see the following in your web browser under the URL
http://127.0.0.1:8000
:
