-
-
Notifications
You must be signed in to change notification settings - Fork 0
Study uvicorn-browser
#15
Comments
This prompted me to think about arel vs uvicorn-browser. I never thought we could implement browser reload at the ASGI server level. That's pretty interesting. As I understand,
Arel has dedicated path reloading options, which allows reloading the page when something other than Python files changes (eg a Jinja template, a JS script, etc). But this also brings a problem. If I'm also running uvicorn-browser doesn't use WebSocket, instead it relies on So, arel has a problem (what to do when the ASGI server reloads, which closes any open connections? [right?*]), uvicorn-browser has another (it's an ASGI server-specific implementation, not agnostic)... Is there a way to solve both of these problems to have some sort of "ultimate ASGI browser reload" solution? Edit: one solution might be to simply augment arel's JS script to retry upon WebSocket disconnects, after all... |
Hold on Florimond, let me type, stop editing! hahaha |
First, thanks for coming here. I'm always happy to read your messages. 🙏 Yesterday, I was playing with This is where I stopped: FastAPI Docs Reloadfrom __future__ import annotations
from pathlib import Path
import arel
from fastapi import FastAPI
from fastapi.openapi.docs import (
get_redoc_html,
get_swagger_ui_html,
get_swagger_ui_oauth2_redirect_html,
)
from fastapi.openapi.utils import get_openapi
from starlette.requests import Request
from starlette.responses import HTMLResponse
from starlette.routing import WebSocketRoute
def script(url: str) -> str:
return f"""
<script>
function connect() {{
var ws = new WebSocket('{url}');
ws.onopen = function() {{
}};
ws.onmessage = function(e) {{
window.location.reload();
}};
ws.onclose = function(e) {{
console.log('Socket is closed. Reconnect will be attempted in 0.1 second.', e.reason);
window.location.reload();
setTimeout(function() {{
connect();
}}, 100);
}};
ws.onerror = function(err) {{
console.error('Socket encountered error: ', err.message, 'Closing socket');
ws.close();
}};
}}
connect();
</script>
"""
def reload_docs_ui(app: FastAPI, paths: list[Path]) -> None:
# if app.docs_url or app.redoc_url:
# raise RuntimeError(
# "You cannot use `reload_docs_ui` when you have `docs_url` or `redoc_url`.\n"
# "On your `FastAPI` instance, set `FastAPI(docs_url=None, redoc_url=None)`."
# )
hot_reload = arel.HotReload(paths=[arel.Path(str(path)) for path in paths])
hot_reload_route = WebSocketRoute("/hot-reload", hot_reload, name="hot-reload")
app.router.routes.append(hot_reload_route)
app.add_event_handler("startup", hot_reload.startup)
app.add_event_handler("shutdown", hot_reload.shutdown)
def custom_openapi():
return get_openapi(
title=app.title,
version=app.version,
openapi_version=app.openapi_version,
description=app.description,
terms_of_service=app.terms_of_service,
contact=app.contact,
license_info=app.license_info,
routes=app.routes,
tags=app.openapi_tags,
servers=app.servers,
)
app.openapi = custom_openapi
if app.openapi_url and app.docs_url:
async def swagger_ui_html(req: Request) -> HTMLResponse:
root_path = req.scope.get("root_path", "").rstrip("/")
openapi_url = root_path + app.openapi_url
oauth2_redirect_url = app.swagger_ui_oauth2_redirect_url
if oauth2_redirect_url:
oauth2_redirect_url = root_path + oauth2_redirect_url
html_response = get_swagger_ui_html(
openapi_url=openapi_url,
title=app.title + " - Swagger UI",
oauth2_redirect_url=oauth2_redirect_url,
init_oauth=app.swagger_ui_init_oauth,
swagger_ui_parameters=app.swagger_ui_parameters,
)
return HTMLResponse(
html_response.body.decode(html_response.charset)
+ script(req.url_for("hot-reload"))
)
app.add_route("/potato", swagger_ui_html, include_in_schema=False)
if app.swagger_ui_oauth2_redirect_url:
async def swagger_ui_redirect(req: Request) -> HTMLResponse:
return get_swagger_ui_oauth2_redirect_html()
app.add_route(
app.swagger_ui_oauth2_redirect_url,
swagger_ui_redirect,
include_in_schema=False,
)
if app.openapi_url and app.redoc_url:
async def redoc_html(req: Request) -> HTMLResponse:
root_path = req.scope.get("root_path", "").rstrip("/")
openapi_url = root_path + app.openapi_url
return get_redoc_html(openapi_url=openapi_url, title=app.title + " - ReDoc")
app.add_route(app.redoc_url, redoc_html, include_in_schema=False) Application: from pathlib import Path
from fastapi import FastAPI
from fastapi_docs_reload import reload_docs_ui
app = FastAPI()
# @app.get("/")
# def home():
# ...
reload_docs_ui(app, [Path.cwd()]) Using |
It's kind of a draft, so if you try it, you see that I've hard coded the "/potato" endpoint. |
Can we create a middleware to inject the script when Like, to use with another ASGI frameworks. |
@Kludex Ah, well yes there's another limitation with A middleware that injects the |
I've implemented
uvicorn-browser
some months ago, but the implementation is too naive. Should I improve it, and release it here as well?The text was updated successfully, but these errors were encountered: