Skip to content

Commit

Permalink
Update the FastApi tutorial (#2877)
Browse files Browse the repository at this point in the history
* Added FastApi in the user guide for embedding apps

* Changed the tutorial and apps to use pn.serve() instead of a thread. Also got rid of the middleware as it is not needed.
  • Loading branch information
t-houssian authored Nov 3, 2021
1 parent 80d1112 commit 04a92a6
Show file tree
Hide file tree
Showing 8 changed files with 37 additions and 151 deletions.
29 changes: 4 additions & 25 deletions examples/apps/fastApi/main.py
Original file line number Diff line number Diff line change
@@ -1,40 +1,19 @@
import panel as pn
from bokeh.embed import server_document
from panel.io.server import Server
from fastapi import FastAPI, Request
from fastapi.templating import Jinja2Templates
from starlette.middleware.cors import CORSMiddleware
from starlette.middleware import Middleware
from threading import Thread
from tornado.ioloop import IOLoop

from sliders.pn_app import createApp

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

app.add_middleware( # Middleware to serve panel apps asynchronously via starlette
CORSMiddleware,
allow_origins=['*'],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)


@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})


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()
pn.serve({'/app': createApp},
port=5000, allow_websocket_origin=["127.0.0.1:8000"],
address="127.0.0.1", show=False)
5 changes: 2 additions & 3 deletions examples/apps/fastApi/sliders/pn_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

from .sinewave import SineWave

def createApp(doc):
def createApp():
sw = SineWave()
row = pn.Row(sw.param, sw.plot)
row.server_doc(doc)
return pn.Row(sw.param, sw.plot).servable()
28 changes: 4 additions & 24 deletions examples/apps/fastApi_multi_apps/main.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,14 @@
import panel as pn
from bokeh.embed import server_document
from panel.io.server import Server
from fastapi import FastAPI, Request
from fastapi.templating import Jinja2Templates
from starlette.middleware.cors import CORSMiddleware
from starlette.middleware import Middleware
from threading import Thread
from tornado.ioloop import IOLoop

from sliders.pn_app import createApp
from sliders2.pn_app import createApp2

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

app.add_middleware( # Middleware to serve panel apps asynchronously via starlette
CORSMiddleware,
allow_origins=['*'],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)


@app.get("/")
async def bkapp_page(request: Request):
Expand All @@ -32,14 +20,6 @@ async def bkapp_page2(request: Request):
script = server_document('http://127.0.0.1:5000/app2')
return templates.TemplateResponse("app2.html", {"request": request, "script": script})

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

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

th = Thread(target=bk_worker)
th.daemon = True
th.start()
pn.serve({'/app': createApp, '/app2': createApp2},
port=5000, allow_websocket_origin=["127.0.0.1:8000"],
address="127.0.0.1", show=False)
5 changes: 2 additions & 3 deletions examples/apps/fastApi_multi_apps/sliders/pn_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

from .sinewave import SineWave

def createApp(doc):
def createApp():
sw = SineWave()
row = pn.Row(sw.param, sw.plot)
row.server_doc(doc)
return pn.Row(sw.param, sw.plot).servable()
5 changes: 2 additions & 3 deletions examples/apps/fastApi_multi_apps/sliders2/pn_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

from .sinewave import SineWave

def createApp2(doc):
def createApp2():
sw = SineWave()
row = pn.Row(sw.param, sw.plot)
row.server_doc(doc)
return pn.Row(sw.param, sw.plot).servable()
1 change: 1 addition & 0 deletions examples/apps/fastApi_multi_apps/templates/app2.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<title>Panel in FastAPI: sliders</title>
</head>
<body>
<h1>App2</h1>
{{ script|safe }}
</body>
</html>
1 change: 1 addition & 0 deletions examples/apps/fastApi_multi_apps/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<title>Panel in FastAPI: sliders</title>
</head>
<body>
<h1>App1</h1>
{{ script|safe }}
</body>
</html>
114 changes: 21 additions & 93 deletions examples/user_guide/FastApi_Apps.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,10 @@
"In `main.py` you'll need to import the following( which should all be already available from the above conda installs):\n",
"\n",
"```python\n",
"import panel as pn\n",
"from bokeh.embed import server_document\n",
"from panel.io.server import Server\n",
"from fastapi import FastAPI, Request\n",
"from fastapi.templating import Jinja2Templates\n",
"from starlette.middleware.cors import CORSMiddleware\n",
"from starlette.middleware import Middleware\n",
"from threading import Thread\n",
"from tornado.ioloop import IOLoop\n",
"```\n",
"\n",
"\n",
Expand All @@ -43,19 +39,6 @@
"templates = Jinja2Templates(directory=\"examples/apps/fastApi/templates\")\n",
"```\n",
"\n",
"Next, we are going to need to set up our middleware which will allow Panel to communicate with FastAPI. We will use starlette for this (Which FastAPI is built on). Add the following to your `main.py`:\n",
"\n",
"```python\n",
"\n",
"app.add_middleware(\n",
" CORSMiddleware,\n",
" allow_origins=['*'],\n",
" allow_credentials=True,\n",
" allow_methods=[\"*\"],\n",
" allow_headers=[\"*\"],\n",
")\n",
"```\n",
"\n",
"We will now need to create our first rout via an async function and point it to the path of our server:\n",
"\n",
"```python\n",
Expand All @@ -81,25 +64,17 @@
"</html>\n",
"```\n",
"\n",
"Return back to your `examples/apps/fastApi/main.py` file. We will now need to add the function bk_worker() to start the bokeh server (Which Panel is built on). Configure it to whatever port you want, for our example we will use port 5000. This also uses a [tornado Iloop](https://www.tornadoweb.org/en/stable/ioloop.html). The `createApp` function call in this example is how we call our panel app. This is not set up yet but will be in the next section.\n",
"Return back to your `examples/apps/fastApi/main.py` file. We will use pn.serve() to start the bokeh server (Which Panel is built on). Configure it to whatever port and address you want, for our example we will use port 5000 and address 127.0.0.1. show=False will make it so the bokeh server is spun up but not shown yet. The allow_websocket_origin will list of hosts that can connect to the websocket, for us this is fastApi so we will use (127.0.0.1:8000). The `createApp` function call in this example is how we call our panel app. This is not set up yet but will be in the next section.\n",
"\n",
"```python\n",
"def bk_worker():\n",
" server = Server({'/app': createApp},\n",
" port=5000, io_loop=IOLoop(), \n",
" allow_websocket_origin=[\"*\"])\n",
"\n",
" server.start()\n",
" server.io_loop.start()\n",
"pn.serve({'/app': createApp},\n",
" port=5000, allow_websocket_origin=[\"127.0.0.1:8000\"],\n",
" address=\"127.0.0.1\", show=False)\n",
"```\n",
"\n",
"We now will need to create a thread to call this function. Add the following code to your `examples/apps/fastApi/main.py`.\n",
"You could optionally add BOKEH_ALLOW_WS_ORIGIN=127.0.0.1:8000 as an environment variable instead of setting it here. In conda it is done like this.\n",
"\n",
"```python\n",
"th = Thread(target=bk_worker)\n",
"th.daemon = True\n",
"th.start()\n",
"```\n",
"`conda env config vars set BOKEH_ALLOW_WS_ORIGIN=127.0.0.1:8000`\n",
"\n",
"## Sliders app\n",
"\n",
Expand Down Expand Up @@ -173,17 +148,16 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"However the app itself is defined we need to configure an entry point, which is a function that accepts a bokeh Document and adds the application to it. In case of the slider app it looks like this in `sliders/pn_app.py`:\n",
"However the app itself is defined we need to configure an entry point, which is a function that adds the application to it. In case of the slider app it looks like this in `sliders/pn_app.py`:\n",
"\n",
"```python\n",
"import panel as pn\n",
"\n",
"from .sinewave import SineWave\n",
"\n",
"def createApp(doc):\n",
"def createApp():\n",
" sw = SineWave()\n",
" row = pn.Row(sw.param, sw.plot)\n",
" row.server_doc(doc)\n",
" return pn.Row(sw.param, sw.plot).servable()\n",
"```\n"
]
},
Expand Down Expand Up @@ -234,69 +208,32 @@
"And your finished `main.py` should look like this:\n",
"\n",
"```python\n",
"import panel as pn\n",
"from bokeh.embed import server_document\n",
"from panel.io.server import Server\n",
"from fastapi import FastAPI, Request\n",
"from fastapi.templating import Jinja2Templates\n",
"from starlette.middleware.cors import CORSMiddleware\n",
"from starlette.middleware import Middleware\n",
"from threading import Thread\n",
"from tornado.ioloop import IOLoop\n",
"\n",
"from sliders.pn_app import createApp\n",
"\n",
"app = FastAPI()\n",
"templates = Jinja2Templates(directory=\"templates\")\n",
"\n",
"app.add_middleware( # Middleware to serve panel apps asynchronously via starlette\n",
" CORSMiddleware,\n",
" allow_origins=['*'],\n",
" allow_credentials=True,\n",
" allow_methods=[\"*\"],\n",
" allow_headers=[\"*\"],\n",
")\n",
"\n",
"\n",
"@app.get(\"/\")\n",
"async def bkapp_page(request: Request):\n",
" script = server_document('http://127.0.0.1:5000/app')\n",
" return templates.TemplateResponse(\"base.html\", {\"request\": request, \"script\": script})\n",
"\n",
"\n",
"def bk_worker():\n",
" server = Server({'/app': createApp},\n",
" port=5000, io_loop=IOLoop(), \n",
" allow_websocket_origin=[\"*\"])\n",
"\n",
" server.start()\n",
" server.io_loop.start()\n",
"\n",
"th = Thread(target=bk_worker)\n",
"th.daemon = True\n",
"th.start()\n",
"pn.serve({'/app': createApp},\n",
" port=5000, allow_websocket_origin=[\"127.0.0.1:8000\"],\n",
" address=\"127.0.0.1\", show=False)\n",
"```"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"You now will need to set an environment variable in order to determine which origins will be allowed to access your bokeh server. Add the following variable to your environment:\n",
"\n",
"```\n",
"BOKEH_ALLOW_WS_ORIGIN=127.0.0.1:8000\n",
"```\n",
"\n",
"This is for the default local host. You may need to change this to whatever your local host is.\n",
"\n",
"If using a conda environment you can create this variable with the following command:\n",
"\n",
"```\n",
"conda env config vars set BOKEH_ALLOW_WS_ORIGIN=127.0.0.1:8000\n",
"```\n",
"\n",
"Your app should be ready to run now. To run your newly created app. Type the following command or copy and paste into your terminal:\n",
"\n",
"```\n",
"uvicorn main:app --reload\n",
"```\n",
Expand Down Expand Up @@ -327,10 +264,9 @@
"\n",
"from .sinewave import SineWave2\n",
"\n",
"def createApp2(doc):\n",
" sw = SineWave2()\n",
" row = pn.Row(sw.param, sw.plot)\n",
" return row.server_doc(doc)\n",
"def createApp2():\n",
" sw = SineWave()\n",
" return pn.Row(sw.param, sw.plot).servable()\n",
"```\n",
"\n",
"With this as your new file structure:\n",
Expand All @@ -353,7 +289,7 @@
"\n",
"3. Create a new html template (ex. app2.html) with the same contents as base.html in `examples/apps/fastApi/templates`\n",
"4. Import your new app in main.py `from sliders2.pn_app import createApp2`\n",
"5. Add your new app to the dictionary in bk_worker() Server\n",
"5. Add your new app to the dictionary in pn.serve()\n",
"\n",
"```python\n",
"{'/app': createApp, '/app2': createApp2}\n",
Expand All @@ -372,17 +308,9 @@
" script = server_document('http://127.0.0.1:5000/app2')\n",
" return templates.TemplateResponse(\"app2.html\", {\"request\": request, \"script\": script})\n",
"\n",
"def bk_worker():\n",
" server = Server({'/app': createApp, '/app2': createApp2},\n",
" port=5000, io_loop=IOLoop(), \n",
" allow_websocket_origin=[\"*\"])\n",
"\n",
" server.start()\n",
" server.io_loop.start()\n",
"\n",
"th = Thread(target=bk_worker)\n",
"th.daemon = True\n",
"th.start()\n",
"pn.serve({'/app': createApp, '/app2': createApp2},\n",
" port=5000, allow_websocket_origin=[\"127.0.0.1:8000\"],\n",
" address=\"127.0.0.1\", show=False)\n",
"```\n",
"\n",
"With this as your file structure\n",
Expand Down

0 comments on commit 04a92a6

Please sign in to comment.