FastAPI-Integration#
Panel läuft üblicherweise auf einem Bokeh-Server, der wiederum auf Tornado läuft. Es ist jedoch auch oft nützlich, eine Panel-App in eine große Webanwendung einzubetten, wie z.B. einen FastAPI-Webserver. Die Integration in FastAPI ist einfacher im Vergleich zu anderen wie z.B. Flask, da es ein leichtgewichtigeres Framework ist. Die Verwendung von Panel mit FastAPI erfordert nur ein wenig mehr Aufwandals bei Notebooks und Bokeh-Servern.
Konfiguration#
Bevor wir mit dem Hinzufügen einer Bokeh-Anwendung zu unserem FastApi-Server
beginnen, müssen wir einige der grundlegenden Konfigurationen in
fastAPI/main.py
einrichten:
Zunächst importieren wir alle erforderlichen Elemente:
Als nächstes definieren wir
app
als Instanz vonFastAPI
und definieren den Pfad zum Vorlagenverzeichnis:10app = FastAPI() 11templates = Jinja2Templates(directory="templates")
Nun erstellen wir unsere erste Routine über eine asynchrone Funktion und verweisen sie an unseren 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 )
Wie ihr dem Code ansehen könnt, wird ein Jinja2-Template
fastAPI/templates/base.html
erwartet. Dieses kann z.B. folgenden Inhalt haben: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>
Kehren wir nun zurück zu unserer
fastAPI/main.py
-Datei um mitpn.serve()
unseren Bokeh-Server zu starten:createApp
ruft in diesem Beispiel unsere Panel-App auf, die jedoch erst im nächsten Abschnitt behandelt wird.
address
,port
Adresse und Port, an dem der Server auf Anfragen lauscht; in unserem Fall also
http://127.0.0.1:5000
.show=False
sorgt dafür, dass der Bokeh-Server zwar gestartet wird, jedoch nicht unmittelbar im Browser angezeigt wird.
allow_websocket_origin
listet die Hosts auf, die sich mit dem Websocket verbinden können. In unserem Beispiel soll das
fastApi
sein, also verwenden wir127.0.0.1:8000
.
Nun definieren wir die
sliders
-App auf Basis einer Standardvorlage für FastAPI-Apps, die zeigt, wie Panel und FastAPI integriert werden können:fastAPI/sliders/sinewave.py
ein parametrisiertes Objekt, das euren bereits vorhandenen Code darstellt:
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
erstellt eine App-Funktion aus der
SineWave
-Klasse: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()
Schließlich kehren wir zu unserer
fastAPI/main.py
zurück und importieren diecreateApp
-Funktion:fastAPI/main.py#4from sliders.pn_app import createApp
Die Dateistruktur sollte nun folgendermaßen aussehen:
fastAPI
├── main.py
├── sliders
│ ├── pn_app.py
│ └── sinewave.py
└── templates
└── base.html
Ihr könnt den Server nun starten mit:
$ 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.
Anschließend solltet ihr im Web-Browser unter der URL http://127.0.0.1:8000
folgendes sehen:
