some_programmer
some_programmer

Reputation: 3528

How to call an API endpoint from a different API endpoint in the same FastAPI application?

(I did find the following question on SO, but it didn't help me: Is it possible to have an api call another api, having them both in same application?)

I am making an app using Fastapi with the following folder structure

enter image description here

main.py is the entry point to the app

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

from app.api.v1 import lines, upload
from app.core.config import settings

app = FastAPI(
    title=settings.PROJECT_NAME,
    version=0.1,
    openapi_url=f'{settings.API_V1_STR}/openapi.json',
    root_path=settings.ROOT_PATH
)

app.add_middleware(
    CORSMiddleware,
    allow_origins=settings.BACKEND_CORS_ORIGINS,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

app.include_router(upload.router, prefix=settings.API_V1_STR)
app.include_router(lines.router, prefix=settings.API_V1_STR)

In the lines.py, I have 2 GET endpoints:

Since the output of the second GET endpoint should be the reversed string of the output of the first GET endpoint, I tried doing the following steps mentioned here

The codes:

import random

from fastapi import APIRouter, Request
from starlette.responses import RedirectResponse

router = APIRouter(
    prefix="/get-info",
    tags=["Get Information"],
    responses={
        200: {'description': 'Success'},
        400: {'description': 'Bad Request'},
        403: {'description': 'Forbidden'},
        500: {'description': 'Internal Server Error'}
    }
)


@router.get('/one-random-line')
def get_one_random_line(request: Request):
    lines = open('netflix_list.txt').read().splitlines()
    if request.headers.get('accept') in ['application/json', 'application/xml']:
        random_line = random.choice(lines)
    else:
        random_line = 'This is an example'
    return {'line': random_line}


@router.get('/one-random-line-backwards')
def get_one_random_line_backwards():
    url = router.url_path_for('get_one_random_line')
    response = RedirectResponse(url=url)
    return {'message': response[::-1]}

When I do this, I get the following error:

TypeError: 'RedirectResponse' object is not subscriptable

When I change the return of the second GET endpoint to return {'message': response}, I get the following output

enter image description here

What is the mistake I am doing?

Example:

If the output of /one-random-line endpoint is 'Maverick', then the output of /one-random-line-backwards should be 'kcirevam'

Upvotes: 4

Views: 8805

Answers (3)

JarroVGIT
JarroVGIT

Reputation: 5269

You can just call any endpoint from your code directly as a function call, you don't have to deal with RedirectResponse() or anything. Below is an example of how this would look like and will run as is:

from fastapi import FastAPI, Request

app = FastAPI()


@app.get("/one-random-line")
async def get_one_random_line(request: Request):
    # implement your own logic here, this will only return a static line
    return {"line": "This is an example"}


@app.get("/one-random-line-backwards")
async def get_one_random_line_backwards(request: Request):
    # You don't have to do fancy http stuff, just call your endpoint:
    one_line = await get_one_random_line(request)
    return {"line": one_line["line"][::-1]}


if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

Using curl we get the following result:

% curl localhost:8000/one-random-line          
{"line":"This is an example"}%     
% curl localhost:8000/one-random-line-backwards
{"line":"elpmaxe na si sihT"}%  

Upvotes: 6

Oleg Popov
Oleg Popov

Reputation: 1

Actually, it's possible to call an API endpoint from another endpoint of this API. You just need to call it from another thread. So, create a function for the call, and use something like this from inside the endpoint:

thread = threading.Thread(target=request_foo, args=(arg1, arg2))
thread.start()

Just know it's a bad practice. It's better to create shared code in the external file and use it from any endpoint you want.

Upvotes: -1

MatsLindh
MatsLindh

Reputation: 52792

Refactor your code to have the common part as a function you call - you'd usually have this in a module external to your controller.

# this function could live as LineService.get_random_line for example
# its responsibility is to fetch a random line from a file
def get_random_line(path="netflix_list.txt"):
    lines = open(path).read().splitlines()
    return random.choice(lines)


# this function encodes the rule that "if the accepted response is json or xml
# we do the random value, otherwise we return a default value"
def get_random_or_default_line_for_accept_value(accept, path="netflix_list.txt", default_value="This is an example"):
    if accept not in ("application/json", "application/xml"):
        return default_value

    return get_random_line(path=path)


@router.get('/one-random-line')
def get_one_random_line(request: Request):
    return {
        "line": get_random_or_default_line_for_accept_value(
            accept=request.headers.get('accept'),
        ),
    }


@router.get('/one-random-line-backwards')
def get_one_random_line_backwards(request: Request):
    return {
        "line": get_random_or_default_line_for_accept_value(
            accept=request.headers.get('accept'),
        )[::-1],
    }

Upvotes: 1

Related Questions