From 04a92a64d3c3e60ecf2349dfc3cf9621bca12451 Mon Sep 17 00:00:00 2001 From: Tyler Houssian Date: Wed, 3 Nov 2021 12:39:54 -0600 Subject: [PATCH] Update the FastApi tutorial (#2877) * 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. --- examples/apps/fastApi/main.py | 29 +---- examples/apps/fastApi/sliders/pn_app.py | 5 +- examples/apps/fastApi_multi_apps/main.py | 28 +---- .../apps/fastApi_multi_apps/sliders/pn_app.py | 5 +- .../fastApi_multi_apps/sliders2/pn_app.py | 5 +- .../fastApi_multi_apps/templates/app2.html | 1 + .../fastApi_multi_apps/templates/base.html | 1 + examples/user_guide/FastApi_Apps.ipynb | 114 ++++-------------- 8 files changed, 37 insertions(+), 151 deletions(-) diff --git a/examples/apps/fastApi/main.py b/examples/apps/fastApi/main.py index 025184c45c..3de7c5709d 100644 --- a/examples/apps/fastApi/main.py +++ b/examples/apps/fastApi/main.py @@ -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() \ No newline at end of file +pn.serve({'/app': createApp}, + port=5000, allow_websocket_origin=["127.0.0.1:8000"], + address="127.0.0.1", show=False) \ No newline at end of file diff --git a/examples/apps/fastApi/sliders/pn_app.py b/examples/apps/fastApi/sliders/pn_app.py index 6b0b74391e..fbc742c881 100644 --- a/examples/apps/fastApi/sliders/pn_app.py +++ b/examples/apps/fastApi/sliders/pn_app.py @@ -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) \ No newline at end of file + return pn.Row(sw.param, sw.plot).servable() \ No newline at end of file diff --git a/examples/apps/fastApi_multi_apps/main.py b/examples/apps/fastApi_multi_apps/main.py index 59e00e7a58..37d26fface 100644 --- a/examples/apps/fastApi_multi_apps/main.py +++ b/examples/apps/fastApi_multi_apps/main.py @@ -1,11 +1,7 @@ +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 @@ -13,14 +9,6 @@ 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): @@ -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() \ No newline at end of file +pn.serve({'/app': createApp, '/app2': createApp2}, + port=5000, allow_websocket_origin=["127.0.0.1:8000"], + address="127.0.0.1", show=False) \ No newline at end of file diff --git a/examples/apps/fastApi_multi_apps/sliders/pn_app.py b/examples/apps/fastApi_multi_apps/sliders/pn_app.py index 6b0b74391e..fbc742c881 100644 --- a/examples/apps/fastApi_multi_apps/sliders/pn_app.py +++ b/examples/apps/fastApi_multi_apps/sliders/pn_app.py @@ -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) \ No newline at end of file + return pn.Row(sw.param, sw.plot).servable() \ No newline at end of file diff --git a/examples/apps/fastApi_multi_apps/sliders2/pn_app.py b/examples/apps/fastApi_multi_apps/sliders2/pn_app.py index 1c3d79d440..6c9a9339db 100644 --- a/examples/apps/fastApi_multi_apps/sliders2/pn_app.py +++ b/examples/apps/fastApi_multi_apps/sliders2/pn_app.py @@ -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) \ No newline at end of file + return pn.Row(sw.param, sw.plot).servable() \ No newline at end of file diff --git a/examples/apps/fastApi_multi_apps/templates/app2.html b/examples/apps/fastApi_multi_apps/templates/app2.html index 618af12a67..f464353e5d 100644 --- a/examples/apps/fastApi_multi_apps/templates/app2.html +++ b/examples/apps/fastApi_multi_apps/templates/app2.html @@ -4,6 +4,7 @@ Panel in FastAPI: sliders +

App2

{{ script|safe }} \ No newline at end of file diff --git a/examples/apps/fastApi_multi_apps/templates/base.html b/examples/apps/fastApi_multi_apps/templates/base.html index 618af12a67..eb273c6bbd 100644 --- a/examples/apps/fastApi_multi_apps/templates/base.html +++ b/examples/apps/fastApi_multi_apps/templates/base.html @@ -4,6 +4,7 @@ Panel in FastAPI: sliders +

App1

{{ script|safe }} \ No newline at end of file diff --git a/examples/user_guide/FastApi_Apps.ipynb b/examples/user_guide/FastApi_Apps.ipynb index 85ba43adcb..5c5c9e01bc 100644 --- a/examples/user_guide/FastApi_Apps.ipynb +++ b/examples/user_guide/FastApi_Apps.ipynb @@ -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", @@ -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", @@ -81,25 +64,17 @@ "\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", @@ -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" ] }, @@ -234,46 +208,25 @@ "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", "```" ] }, @@ -281,22 +234,6 @@ "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", @@ -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", @@ -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", @@ -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",