FastAPI return image as JSON - python

I have an API for random tea photos but would like it to return the photo in JSON as a URL for discord bots and things. This is what I have so far:
def getRandomFile(path):
"""
Returns a random filename, chosen among the files of the given path.
"""
files = os.listdir(path)
index = random.randrange(0, len(files))
return files[index]
#app.get("/")
async def home():
return RedirectResponse("/docs")
#app.get("/tea")
def tea():
x = "teacuppics/{}".format(getRandomFile("teacuppics"))
return FileResponse(x)
How do I swap the reply of images to JSON reply?

You could use a static files directory to serve static files such as images. Include your teacuppics folder inside the static directory and mount a StaticFiles() instance in that directory. You can then use request.url_for() function to get the URL of a randomly selected image. Example below:
from fastapi.staticfiles import StaticFiles
from fastapi import FastAPI, Request
app = FastAPI()
app.mount("/static", StaticFiles(directory="static"), name="static")
#app.get('/tea')
def tea(request: Request):
img = "teacuppics/{}".format(getRandomFile("static/teacuppics"))
img_url = request.url_for('static', path=img)
return {'img_url': img_url}

You can use request.url_for what generate url according to host. (on localhost it will be 127.0.0.1, on hosting it will be www.your-page.com). Argument is name of function. It was solved here as well.
from fastapi import FastAPI, Request
from fastapi.responses import FileResponse
from fastapi.responses import JSONResponse
app = FastAPI(title='FASTAPI BLANK', version='1')
#app.get("/tea/{img}")
async def tea(img: str) -> FileResponse:
return FileResponse(img)
#app.get('/json_tea')
async def tea2(request: Request) -> JSONResponse:
rnd_pic = '11dfdvf.jpg'
pic_url = request.url_for('tea', img=rnd_pic)
return JSONResponse(content={'teacuppics': pic_url})

Related

FastAPI - adding route prefix to TestClient

I have a FastAPI app with a route prefix as /api/v1.
When I run the test it throws 404. I see this is because the TestClient is not able to find the route at /ping, and works perfectly when the route in the test case is changed to /api/v1/ping.
Is there a way in which I can avoid changing all the routes in all the test functions as per the prefix? This seems to be cumbersome as there are many test cases, and also because I dont want to have a hard-coded dependency of the route prefix in my test cases. Is there a way in which I can configure the prefix in the TestClient just as we did in app, and simply mention the route just as mentioned in the routes.py?
routes.py
from fastapi import APIRouter
router = APIRouter()
#router.get("/ping")
async def ping_check():
return {"msg": "pong"}
main.py
from fastapi import FastAPI
from routes import router
app = FastAPI()
app.include_router(prefix="/api/v1")
In the test file I have:
test.py
from main import app
from fastapi.testclient import TestClient
client = TestClient(app)
def test_ping():
response = client.get("/ping")
assert response.status_code == 200
assert response.json() == {"msg": "pong"}
Figured out a workaround for this.
The TestClient has an option to accept a base_url, which is then urljoined with the route path. So I appended the route prefix to this base_url.
source:
url = urljoin(self.base_url, url)
However, there is a catch to this - urljoin concatenates as expected only when the base_url ends with a / and the path does not start with a /. This SO answer explains it well.
This resulted in the below change:
test.py
from main import app, ROUTE_PREFIX
from fastapi.testclient import TestClient
client = TestClient(app)
client.base_url += ROUTE_PREFIX # adding prefix
client.base_url = client.base_url.rstrip("/") + "/" # making sure we have 1 and only 1 `/`
def test_ping():
response = client.get("ping") # notice the path no more begins with a `/`
assert response.status_code == 200
assert response.json() == {"msg": "pong"}
The above work-around (by Shod) worked for me, but I had to pass the APIRouter object instead of FastAPI object to the testclient. I was receiving a 404 error otherwise.
Below is a sample code for how it worked for me.
from fastapi import FastAPI, APIRouter
from fastapi.testclient import TestClient
app = FastAPI()
router = APIRouter(prefix="/sample")
app.include_router(router)
#router.post("/s1")
def read_main():
return {"msg": "Hello World"}
client = TestClient(router)
client.base_url += "/sample"
client.base_url = client.base_url.rstrip("/") + "/"
def test_main():
response = client.post("s1")
assert response.status_code == 200
assert response.json() == {"msg": "Hello World"}

Unable to override dependency in FastAPI/FastAPi-Utils

This may be a newbie question. I am not able to override the greetings message in this simple 2 files FastAPI project. Could you please tell me what I might have done wrong? Thanks a lot for your help.
greetings_service.py
from fastapi import Depends
from fastapi_utils.cbv import cbv
from fastapi_utils.inferring_router import InferringRouter
router = InferringRouter()
def get_msg():
return "Original Message"
#cbv(router)
class GreetingsService:
#router.get("/")
async def greet(self, msg: str = Depends(get_msg)):
return f"Hello from FastAPI {msg}"
main.py
from fastapi import FastAPI
from starlette.testclient import TestClient
import greetings_service
app = FastAPI()
app.include_router(greetings_service.router)
def get_new_msg():
return "New Message"
//Tried this, doesn't work
#app.dependency_overrides["get_msg"] = get_new_msg()
//These 2 lines doesn't work too
app.dependency_overrides["get_msg"] = get_new_msg()
greetings_service.router.dependency_overrides_provider = app
client = TestClient(app)
res = client.get("/")
print(res.content) #"Hello from FastAPI Original Message" :(
The issue is with this:
app.dependency_overrides["get_msg"] = get_new_msg()
You are passing the dependency as string instead of the actual dependency.
Something like this would work:
from fastapi import FastAPI
from starlette.testclient import TestClient
import greetings_service
app = FastAPI()
app.include_router(greetings_service.router)
def get_new_msg():
return "New Message"
app.dependency_overrides[greetings_service.get_msg] = get_new_msg
client = TestClient(app)
res = client.get("/")
print(res.content)

How to send a file (docx, doc, pdf or json) to fastapi and predict on it without UI (i.e., HTML)?

If you know how to send a file to FastAPI server and access it in /predict endpoint for prediction using my models please help me out.
I have deployed the model using /predict endpoint and done uvicorn main:app and it's deployed but the only thing is input that is a document is in my local pc so how can I sent it to FastAPI?
I have went through the documentation of FastAPI and I have found this example code there, but the challenge is that this code creates an UI for uploading file which is not what I"m looking for.
from typing import Optional
from fastapi import FastAPI
from fastapi import FastAPI, File, UploadFile
from pydantic import BaseModel
from typing import List
from fastapi.responses import HTMLResponse
app = FastAPI()
class User(BaseModel):
user_name: dict
#app.post("/files/")
async def create_files(files: List[bytes] = File(...)):
return {"file_sizes": [len(file) for file in files]}
#app.post("/uploadfiles/")
async def create_upload_files(files: List[UploadFile] = File(...)):
return {"filenames": [file.filename for file in files]}
#app.get("/")
async def main():
content = """
<body>
<form action="/files/" enctype="multipart/form-data" method="post">
<input name="files" type="file" multiple>
<input type="submit">
</form>
</body>
"""
return HTMLResponse(content=content)
FASTAPI CODE
This will be your endpoint.
from fastapi import FastAPI, UploadFile, File
app = FastAPI()
#app.post("/file")
async def upload_file(file: UploadFile = File(...)):
# Do here your stuff with the file
return {"filename": file.filename}
JAVASCRIPT CODE
This is your javascript code (assuming you are using javascript for uploading the file)
form = new FormData();
form.append("file", myFile);
let response = await fetch('/file', {
method: 'POST',
body: form
});
let result = await response.json();
EDIT: Python file upload
I'm using httpx, but technically it should be fully compatible with requests.
import httpx
# Create a dict with a key that has the same name as your file parameter and the file in binary form (the "b" in "rb")
f = {'file': open('foo.png', 'rb')}
r = httpx.post("your_url/file", files=f)
You can see more configurations/examples on the official documentation of httpx at https://www.python-httpx.org/quickstart/#sending-multipart-file-uploads.
Again, I did not test the code, since I'm tight with time at the moment.
END OF EDIT
Be aware that the parameter name of the file, MUST match the one used for sending the file.
In case, there is also another answer from me on how to test it with POSTMAN. See How to send file to fastapi endpoint using postman
NOTE
I did not test the code as I don't have time right now. In case, there is also a link with a previous answer of mine that works (unless FASTAPI introduced breaking changes).

How to access route dependencies in custom APIRoute class?

I want to access a route-level dependency (cache) from a custom APIRoute class. The API is defined using a router and a custom APIRoute class.
APIRoute class
from typing import Callable
from fastapi import Request, Response, Depends
from fastapi.routing import APIRoute
class RecordRequestResponseRoute(APIRoute):
def get_route_handler(self) -> Callable:
original_route_handler = super().get_route_handler()
async def custom_route_handler(request: Request) -> Response:
# need to access cache here
response: Response = await original_route_handler(request)
return response
return custom_route_handler
Router API
from fastapi import Response, Depends, APIRouter, BackgroundTasks
import fastapi_plugins
import aioredis
router = APIRouter(route_class=RecordRequestResponseRoute)
#router.get("/users", tags=["users"])
async def match_frame(background_tasks: BackgroundTasks,
cache: aioredis.Redis = Depends(fastapi_plugins.depends_redis)):
return {"success": True, "data": []}
I need to access cache in RecordRequestResponseRoute class. I tried using sub dependencies but that did not help. What will be the correct way to do it?
I've been facing the same issue for some time and the best implementation I've came to is using fastapi-utils and class-based routing. Thus we can set up our dependencies as attributes of the class and create any method there. And call it like decorator or just inside route function.
Also you can efficiently choose which route will be changed by "request.url.path.split("/")[-1]". The string returns you the route. But it can also be done via decorator or you just run this func inside proper route.
Hope it will be useful!
Code example:
# some_route.py
from typing import List
from fastapi import APIRouter, Request, Depends, status, Response
from fastapi_utils.cbv import cbv
from fastapi_utils.inferring_router import InferringRouter
from schemas import UserInfoToken
from utils import get_userinfo
from logger_repo import log_action
router = InferringRouter(tags=["some_tag"])
#cbv(router)
class HardwareRouterClass:
userinfo: UserInfoToken = Depends(get_userinfo)
async def user_action_logger(self, request: Request) -> Response:
user_action = request.url.path.split("/")[-1]
if user_action == "route_1":
data = await request.json()
await log_action(
user_email=self.userinfo.email,
device=data["some_key"]
)
#router.get("/health")
async def get_health(self):
return "OK"
#router.post("/route_1")
async def post_route(
self, request: Request, data: ReadersBody
) -> ReadersResponse:
await self.user_action_logger(request)
return await your_func(data)

Send a csv file to fastAPI and get a new file back

I have this very simple code that gets a dictionary as imput and returns back a dictionary as well:
app = FastAPI()
class User(BaseModel):
user_name: dict
#app.post("/")
def main(user: User):
## some processing
return user
I'm calling this with the following python code:
import requests
import json
import pandas as pd
df = pd.read_csv("myfile.csv")
data = {}
data["user_name"] = df.to_dict()
headers = {'content-type': 'application/json'}
url = 'http://localhost:8000/'
resp = requests.post(url,data=json.dumps(data), headers=headers )
resp
Now, I want to do something similar but instead of sending the data with a request from python
I want to upload a local file send it to the API and get a processed .csv file back.
Now I have the following code to upload a file:
from typing import Optional
from fastapi import FastAPI
from fastapi import FastAPI, File, UploadFile
from pydantic import BaseModel
from typing import List
from fastapi.responses import HTMLResponse
app = FastAPI()
class User(BaseModel):
user_name: dict
#app.post("/files/")
async def create_files(files: List[bytes] = File(...)):
return {"file_sizes": [len(file) for file in files]}
#app.post("/uploadfiles/")
async def create_upload_files(files: List[UploadFile] = File(...)):
return {"filenames": [file.filename for file in files]}
#app.get("/")
async def main():
content = """
<body>
<form action="/files/" enctype="multipart/form-data" method="post">
<input name="files" type="file" multiple>
<input type="submit">
</form>
</body>
"""
return HTMLResponse(content=content)
This allows me to select and load a file:
But when I click on send, I'm redirected to: http://localhost:8000/uploadfiles/ and get the following error:
{"detail":"Method Not Allowed"}
How can I send the file for procesing to the API and get a file back as a response?
I copied your code and I got the same error. After going through the FastAPI documentation for requesting files, it is mentioned that
To receive uploaded files, first install python-multipart.
E.g. pip install python-multipart.
This is because uploaded files are sent as "form data".
After installing the library python-multipart, the code worked perfectly.
References:
https://andrew-d.github.io/python-multipart/

Categories