Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to capture arbitrary paths at one route in FastAPI?

I'm serving React app from FastAPI by mounting

app.mount("/static", StaticFiles(directory="static"), name="static")

@app.route('/session')
async def renderReactApp(request: Request):
    return templates.TemplateResponse("index.html", {"request": request})

by this React app get served and React routing also works fine at client side but as soon as client reloads on a route which is not defined on server but used in React app FastAPI return not found to fix this I did something as below.

  • @app.route('/network')
  • @app.route('/gat')
  • @app.route('/session')

async def renderReactApp(request: Request):
    return templates.TemplateResponse("index.html", {"request": request})

but it seems weird and wrong to me as I need to add every route at the back-end as well as at frontend.

I'm sure there must be something like Flask @flask_app.add_url_rule('/<path:path>', 'index', index) in FastAPI which will server all arbitrary path

like image 461
Suhas More Avatar asked Jul 24 '20 07:07

Suhas More


People also ask

What is path operation?

Operations on a Path defines the mechanisms available for interacting with the API when it is called using that Path. An operation can be of various pre-defined types; for example, a POST operation is most commonly used for creating new resources, while a GET operation is used to retrieve a resource.

What are FastAPI tags?

Tags are key/value string pairs that are both indexed and searchable.


2 Answers

Since FastAPI is based on Starlette, you can use what they call "converters" with your route parameters, using type path in this case, which "returns the rest of the path, including any additional / characers."

See https://www.starlette.io/routing/#path-parameters for reference.

If your react (or vue or ...) app is using a base path, you can do something like this, which assigns anything after /my-app/ to the rest_of_path variable:

@app.get("/my-app/{rest_of_path:path}")
async def serve_my_app(request: Request, rest_of_path: str):
    print("rest_of_path: "+rest_of_path)
    return templates.TemplateResponse("index.html", {"request": request})

If you are not using a unique base path like /my-app/ (which seems to be your use case), you can still accomplish this with a catch-all route, which should go after any other routes so that it doesn't overwrite them:

@app.route("/{full_path:path}")
async def catch_all(request: Request, full_path: str):
    print("full_path: "+full_path)
    return templates.TemplateResponse("index.html", {"request": request})

(In fact you would want to use this catch-all regardless in order to catch the difference between requests for /my-app/ and /my-app)

like image 124
csum Avatar answered Oct 02 '22 19:10

csum


As @mecampbellsoup pointed out: there are usually other static files that need to be served with an application like this.

Hopefully this comes in handy to someone else:

import os
from typing import Tuple

from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles

app = FastAPI()


class SinglePageApplication(StaticFiles):
    """Acts similar to the bripkens/connect-history-api-fallback
    NPM package."""

    def __init__(self, directory: os.PathLike, index='index.html') -> None:
        self.index = index

        # set html=True to resolve the index even when no
        # the base path is passed in
        super().__init__(directory=directory, packages=None, html=True, check_dir=True)

    async def lookup_path(self, path: str) -> Tuple[str, os.stat_result]:
        """Returns the index file when no match is found.

        Args:
            path (str): Resource path.

        Returns:
            [tuple[str, os.stat_result]]: Always retuens a full path and stat result.
        """
        full_path, stat_result = await super().lookup_path(path)

        # if a file cannot be found
        if stat_result is None:
            return await super().lookup_path(self.index)

        return (full_path, stat_result)



app.mount(
    path='/',
    app=SinglePageApplication(directory='path/to/dist'),
    name='SPA'
)

These modifications make the StaticFiles mount act similar to the connect-history-api-fallback NPM package.

like image 22
Noah Cardoza Avatar answered Oct 02 '22 19:10

Noah Cardoza