Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unable to view components when the app is mount in another app. #134

Closed
pbrochar opened this issue Dec 28, 2023 · 7 comments
Closed

Unable to view components when the app is mount in another app. #134

pbrochar opened this issue Dec 28, 2023 · 7 comments

Comments

@pbrochar
Copy link

If I use one FastAPI app which contains all the FastUI routes and I mount this app in another FastAPI app, I can't access to any components in frontend.
What I'm doing wrong ?
I tried to use root_path parameter of FastAPI but with no success

To reproduce:

from datetime import date

from fastapi import FastAPI, HTTPException
from fastapi.responses import HTMLResponse
from fastui import FastUI, AnyComponent, prebuilt_html, components as c
from fastui.components.display import DisplayMode, DisplayLookup
from fastui.events import GoToEvent, BackEvent
from pydantic import BaseModel, Field
import uvicorn

app = FastAPI()


class User(BaseModel):
    id: int
    name: str
    dob: date = Field(title="Date of Birth")


# define some users
users = [
    User(id=1, name="John", dob=date(1990, 1, 1)),
    User(id=2, name="Jack", dob=date(1991, 1, 1)),
    User(id=3, name="Jill", dob=date(1992, 1, 1)),
    User(id=4, name="Jane", dob=date(1993, 1, 1)),
]


@app.get("/api/", response_model=FastUI, response_model_exclude_none=True)
def users_table() -> list[AnyComponent]:
    """
    Show a table of four users, `/api` is the endpoint the frontend will connect to
    when a user visits `/` to fetch components to render.
    """
    return [
        c.Page(  # Page provides a basic container for components
            components=[
                c.Heading(text="Users", level=2),  # renders `<h2>Users</h2>`
                c.Table[
                    User
                ](  # c.Table is a generic component parameterized with the model used for rows
                    data=users,
                    # define two columns for the table
                    columns=[
                        # the first is the users, name rendered as a link to their profile
                        DisplayLookup(
                            field="name", on_click=GoToEvent(url="/user/{id}/")
                        ),
                        # the second is the date of birth, rendered as a date
                        DisplayLookup(field="dob", mode=DisplayMode.date),
                    ],
                ),
            ]
        ),
    ]


@app.get(
    "/api/user/{user_id}/", response_model=FastUI, response_model_exclude_none=True
)
def user_profile(user_id: int) -> list[AnyComponent]:
    """
    User profile page, the frontend will fetch this when the user visits `/user/{id}/`.
    """
    try:
        user = next(u for u in users if u.id == user_id)
    except StopIteration:
        raise HTTPException(status_code=404, detail="User not found")
    return [
        c.Page(
            components=[
                c.Heading(text=user.name, level=2),
                c.Link(components=[c.Text(text="Back")], on_click=BackEvent()),
                c.Details(data=user),
            ]
        ),
    ]


@app.get("/{path:path}")
async def html_landing() -> HTMLResponse:
    """Simple HTML page which serves the React app, comes last as it matches all paths."""
    return HTMLResponse(prebuilt_html(title="FastUI Demo"))


if __name__ == "__main__":
    other_app = FastAPI()
    other_app.mount("/foo", app)
    uvicorn.run(other_app, port=8200)
@hasansezertasan
Copy link
Contributor

I have tried the "mount" feature from FastAPI with a simpler example.

from fastapi import FastAPI
from fastapi.responses import HTMLResponse
from fastui import FastUI, AnyComponent, prebuilt_html, components as c

subapp = FastAPI()

@subapp.get("/api/", response_model=FastUI, response_model_exclude_none=True)
def index() -> list[AnyComponent]:
    return [
        c.Page(
            components=[
                c.Heading(text="Hello World!", level=2),
            ]
        ),
    ]


@subapp.get("/{path:path}")
async def html_landing() -> HTMLResponse:
    """Simple HTML page which serves the React app, comes last as it matches all paths."""
    return HTMLResponse(prebuilt_html(title="FastUI Demo"))


app = FastAPI()
app.mount("/sub-app", subapp)

When I navigate to the http://localhost:8000/sub-app/, this screen welcomes me:

image

prebuilt_html is successfully served and FastUI JavaScript is loaded.

In this example, the backend doesn't specify or tell any URL but check out the URL that FastUI JavaScript constructed for the /api route: No page found at /api/sub-app/. Which is not correct. It should look at: /sub-app/api.

I think we should specify the /api route when we start the application. Users might want to serve /api related stuff somewhere else, in another name like: /logic or something like that. What I propose here is something like this:

@subapp.get("/{path:path}")
async def html_landing() -> HTMLResponse:
    """Simple HTML page which serves the React app, comes last as it matches all paths."""
    return HTMLResponse(prebuilt_html(title="FastUI Demo", entry_point="/sub-app/logic"))

And we can put the "entry_point" value in the html body, like this:

<body entry_point={entry_point}>

FastUI JavaScript can look for the "body" and take the entry_point from there. How does that sound @samuelcolvin?

@dconathan
Copy link

👍 to this… we definitely need the option to mount a fastui app inside of another app if that’s not possible today!

@geospatial-jeff
Copy link

geospatial-jeff commented Jan 20, 2024

👍 agree that an application mount is the cleanest way to mount the ui onto an existing application, this would have made it a lot easier to add the ui to my existing fastapi application.

@samuelcolvin
Copy link
Member

I agree this would be good. PR welcome to support it, otherwise I'll get to it soon.

@samuelcolvin
Copy link
Member

Fixed by #176 I believe, I need to try it more, but I think it's done.

@davidjb99
Copy link

davidjb99 commented Apr 25, 2024

@samuelcolvin I don't think #176 fixed this, when mounting a sub app I still get a Page not found No page found at /api/admin/. error

The example by @hasansezertasan also does not work (#134 (comment))

Is the setup for a sub app different?

from fastapi import FastAPI
from fastapi.responses import HTMLResponse
from fastui import FastUI, AnyComponent, prebuilt_html, components as c

subapp = FastAPI()


@subapp.get("/api/", response_model=FastUI, response_model_exclude_none=True)
def index() -> list[AnyComponent]:
    return [
        c.Page(
            components=[
                c.Heading(text="Hello World!", level=2),
            ]
        ),
    ]


@subapp.get("/{path:path}")
async def html_landing() -> HTMLResponse:
    """Simple HTML page which serves the React app, comes last as it matches all paths."""
    return HTMLResponse(prebuilt_html(title="FastUI Demo"))


app = FastAPI()
app.mount("/admin", subapp)

@leos-code
Copy link

@davidjb99

# Copyright 2024 Hasan Sezer Taşan <[email protected]>
# Copyright (C) 2024 <[email protected]>
from __future__ import annotations

from fastapi import FastAPI
from fastapi.responses import HTMLResponse
from fastui import AnyComponent, FastUI, prebuilt_html
from fastui import components as c

fooapp = FastAPI()


@fooapp.get("/api/", response_model=FastUI, response_model_exclude_none=True)
def foo_page() -> list[AnyComponent]:
    return [c.Heading(text="Sub Application Foo API!")]


@fooapp.get("/{path:path}")
def foo_root() -> HTMLResponse:
    """Simple HTML page which serves the React app, comes last as it matches all paths."""
    return HTMLResponse(
        prebuilt_html(
            title="Sub Application Foo",
            api_root_url="/foo/api",
            api_path_strip="/foo",
        )
    )


barapp = FastAPI()


@barapp.get("/api/v1/", response_model=FastUI, response_model_exclude_none=True)
def bar_page() -> list[AnyComponent]:
    return [c.Heading(text="Sub Application Bar API!")]


@barapp.get("/{path:path}")
def bar_root() -> HTMLResponse:
    """Simple HTML page which serves the React app, comes last as it matches all paths."""
    return HTMLResponse(
        prebuilt_html(
            title="Sub Application Bar",
            api_root_url="/bar/api/v1",
            api_path_strip="/bar",
        )
    )


app = FastAPI()
app.mount("/foo", fooapp)
app.mount("/bar", barapp)

try this code,look out api_path_strip params

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants