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:

  1. 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
    
  2. Next, we define app as an instance of FastAPI and define the path to the template directory:

    10app = FastAPI()
    11templates = Jinja2Templates(directory="templates")
    
  3. 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    )
    
  4. 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>
    
  5. Let’s now return to our fastAPI/main.py file to start our bokeh server with pn.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 use 127.0.0.1:8000.

  6. 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            height=400,
    23            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()
    
  7. Finally, we return to our fastAPI/main.py and import the createApp 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:

Widgets and sine curve in bokeh plot