mirror of
https://github.com/rio-labs/rio.git
synced 2026-02-10 23:59:10 -06:00
working fastapi app support
This commit is contained in:
@@ -326,15 +326,26 @@ class FastapiServer(fastapi.FastAPI, AbstractAppServer):
|
||||
|
||||
# The route that serves the index.html will be registered later, so that
|
||||
# it has a lower priority than user-created routes.
|
||||
#
|
||||
# This keeps track of whether the fallback route has already been
|
||||
# registered.
|
||||
self._index_hmtl_route_registered = False
|
||||
|
||||
async def __call__(self, scope, receive, send) -> None:
|
||||
# Because this is a single page application, all other routes should
|
||||
# serve the index page. The session will determine which components
|
||||
# should be shown.
|
||||
self.add_api_route(
|
||||
"/{initial_route_str:path}", self._serve_index, methods=["GET"]
|
||||
)
|
||||
#
|
||||
# This route is registered last, so that it has the lowest priority.
|
||||
# This allows the user to add custom routes that take precedence.
|
||||
if not self._index_hmtl_route_registered:
|
||||
self._index_hmtl_route_registered = True
|
||||
|
||||
self.add_api_route(
|
||||
"/{initial_route_str:path}", self._serve_index, methods=["GET"]
|
||||
)
|
||||
|
||||
# Delegate to FastAPI
|
||||
return await super().__call__(scope, receive, send)
|
||||
|
||||
@contextlib.asynccontextmanager
|
||||
|
||||
@@ -253,7 +253,7 @@ def load_user_app(
|
||||
if isinstance(var, rio.app_server.fastapi_server.FastapiServer):
|
||||
as_fastapi_apps.append((var_name, var))
|
||||
|
||||
if isinstance(var, rio.App):
|
||||
elif isinstance(var, rio.App):
|
||||
rio_apps.append((var_name, var))
|
||||
|
||||
# Prepare the main file name
|
||||
@@ -262,10 +262,12 @@ def load_user_app(
|
||||
else:
|
||||
main_file_reference = f"The file `{Path(app_module.__file__).relative_to(proj.project_directory)}`"
|
||||
|
||||
print(as_fastapi_apps, rio_apps)
|
||||
|
||||
# Which type of app do we have?
|
||||
#
|
||||
# Case: FastAPI app
|
||||
if len(as_fastapi_apps) > 1:
|
||||
if len(as_fastapi_apps) > 0:
|
||||
app_list = as_fastapi_apps
|
||||
app_server = as_fastapi_apps[0][1]
|
||||
app_instance = app_server.app
|
||||
|
||||
@@ -5,6 +5,7 @@ import typing as t
|
||||
import revel
|
||||
import uvicorn
|
||||
import uvicorn.lifespan.on
|
||||
from starlette.types import Receive, Scope, Send
|
||||
|
||||
import rio
|
||||
import rio.app_server.fastapi_server
|
||||
@@ -44,27 +45,47 @@ class UvicornWorker:
|
||||
# either a Rio app or a FastAPI app (derived from a Rio app).
|
||||
self.app_server = app_server
|
||||
|
||||
def _create_and_store_app_server(self) -> None:
|
||||
app_server = self.app._as_fastapi(
|
||||
debug_mode=self.debug_mode,
|
||||
running_in_window=self.run_in_window,
|
||||
internal_on_app_start=lambda: self.on_server_is_ready_or_failed.set_result(
|
||||
None
|
||||
),
|
||||
base_url=self.base_url,
|
||||
)
|
||||
assert isinstance(
|
||||
app_server, rio.app_server.fastapi_server.FastapiServer
|
||||
)
|
||||
self.app_server = app_server
|
||||
|
||||
async def run(self) -> None:
|
||||
rio.cli._logger.debug("Uvicorn worker is starting")
|
||||
|
||||
# Set up a uvicorn server, but don't start it yet
|
||||
# Create the app server
|
||||
if self.app_server is None:
|
||||
app_server = self.app._as_fastapi(
|
||||
debug_mode=self.debug_mode,
|
||||
running_in_window=self.run_in_window,
|
||||
internal_on_app_start=lambda: self.on_server_is_ready_or_failed.set_result(
|
||||
None
|
||||
),
|
||||
base_url=self.base_url,
|
||||
)
|
||||
assert isinstance(
|
||||
app_server, rio.app_server.fastapi_server.FastapiServer
|
||||
)
|
||||
self.app_server = app_server
|
||||
del app_server
|
||||
self._create_and_store_app_server()
|
||||
assert self.app_server is not None
|
||||
|
||||
# Instead of using the ASGI app directly, create a transparent shim that
|
||||
# redirect's to the worker's currently stored app server. This allows
|
||||
# replacing the app server at will because the shim always remains the
|
||||
# same.
|
||||
#
|
||||
# ASGI is a bitch about function signatures. This function cannot be a
|
||||
# simple method, because the added `self` parameter seems to confused
|
||||
# whoever the caller is. Hence the nested function.
|
||||
async def _asgi_shim(
|
||||
scope: Scope,
|
||||
receive: Receive,
|
||||
send: Send,
|
||||
) -> None:
|
||||
assert self.app_server is not None
|
||||
await self.app_server(scope, receive, send)
|
||||
|
||||
# Set up a uvicorn server, but don't start it yet
|
||||
config = uvicorn.Config(
|
||||
self.app_server,
|
||||
app=_asgi_shim,
|
||||
log_config=None, # Prevent uvicorn from configuring global logging
|
||||
log_level="error" if self.quiet else "info",
|
||||
timeout_graceful_shutdown=1, # Without a timeout the server sometimes deadlocks
|
||||
@@ -135,7 +156,14 @@ class UvicornWorker:
|
||||
worker must already be running for this to work.
|
||||
"""
|
||||
assert self.app_server is not None
|
||||
rio.cli._logger.debug("Replacing the app in the server")
|
||||
self.app_server.app = app
|
||||
|
||||
# TODO: What to do with the new server here?
|
||||
# Store the new app
|
||||
self.app = app
|
||||
|
||||
# And create a new app server. This is necessary, because the mounted
|
||||
# sub-apps may have changed. This ensures they're up to date.
|
||||
self._create_and_store_app_server()
|
||||
|
||||
# There is no need to inject the new app or server anywhere. Since
|
||||
# uvicorn was fed a shim function instead of the app directly, any
|
||||
# requests will automatically be redirected to the new server instance.
|
||||
|
||||
Reference in New Issue
Block a user