pythonrequestfastapi

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'


Solution

  • 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],
        }