I have created a simple API using FastAPI, and I am trying to pass a URL to a FastAPI route as an arbitrary path
parameter.
from fastapi import FastAPI
app = FastAPI()
@app.post("/{path}")
def pred_image(path:str):
print("path",path)
return {'path':path}
When I test it, it doesn't work and throws an error. I am testing it this way:
http://127.0.0.1:8000/https://raw.githubusercontent.com/ultralytics/yolov5/master/data/images/zidane.jpg
You could simply use Starlette's path
convertor to capture arbitrary paths. As per Starlette documentation, path
returns the rest of the path, including any additional /
characters.
from fastapi import Request
@app.get('/{_:path}')
async def pred_image(request: Request):
return {'path': request.url.path[1:]}
or
@app.get('/{full_path:path}')
async def pred_image(full_path: str):
return {'path': full_path}
Test using the link below:
http://127.0.0.1:8000/https://raw.githubusercontent.com/ultralytics/yolov5/master/data/images/zidane.jpg
Please note that the URL above will be automatically encoded by the web browser (as URLs can only contain ASCII characters), meaning that before sending the request, any special characters will be converted to other reserved ones, using the %
sign followed by a hexadecimal pair. Hence, behind the scenes the request URL should look like this:
http://127.0.0.1:8000/https%3A%2F%2Fraw.githubusercontent.com%2Fultralytics%2Fyolov5%2Fmaster%2Fdata%2Fimages%2Fzidane.jpg
If, for instance, you would like to test the endpoint through other clients, such as using Python requests
lib, you should then encode the URL yourself before sending the request. You can do that using Python's urllib.parse.quote()
function, as shown below. Note that, the quote()
function considers /
characters safe by default, meaning that any /
characters won't be encoded. Hence, in this case, you should set the safe
parameter to ''
(i.e., empty string), in order to encode /
characters as well.
Test using Python requests
:
import requests
from urllib.parse import quote
base_url = 'http://127.0.0.1:8000/'
path_param = 'https://raw.githubusercontent.com/ultralytics/yolov5/master/data/images/zidane.jpg'
url = base_url + quote(path_param, safe='')
r = requests.get(url)
print(r.json())
Output:
{"path":"https://raw.githubusercontent.com/ultralytics/yolov5/master/data/images/zidane.jpg"}
Test using HTML <form>
:
If you would like to test the above by passing the URL through an HTML <form>
, instead of manually typing it after the base URL on your own, please have a look at Option 3 of this answer, which demonstrates how to convert a form <input>
element into a path parameter on <form>
submission. The encoding of the URL takes place behind the scences when the <form>
is submitted, likely using functions such as encodeURIComponent()
or encodeURI()
; hence, there is no need for you to apply any techniques, in order to quote the URL beforehand, such as in Python requests
earlier.
As @luk2302 mentioned in the comments section, your client (i.e., end user, javascript, etc) needs to encode the URL. The encoded URL, however, as provided by @luk2302 does not seem to work, leading to a "detail": "Not Found"
error. As it turns out, you would need to encode it twice to work. That is:
http://127.0.0.1:8000/https%253A%252F%252Fraw.githubusercontent.com%252Fultralytics%252Fyolov5%252Fmaster%252Fdata%252Fimages%252Fzidane.jpg
On the server side, you can decode the URL (twice) as follows:
from urllib.parse import unquote
@app.get('/{path}')
async def pred_image(path: str):
return {'path':unquote(unquote(path))}
Use a query parameter instead, as shown below:
@app.get('/')
async def pred_image(path: str):
return {'path': path}
Test using the link below:
http://127.0.0.1:8000/?path=https://raw.githubusercontent.com/ultralytics/yolov5/master/data/images/zidane.jpg
If, again, you would like to use Python requests
lib to test the endpoint above, have a look at the example below. Note that since the image URL is now being sent as part of the query string (i.e., as a query parameter), requests
will take care of the URL encoding; hence, there is no need for using the quote()
function this time.
Test using Python requests
:
import requests
base_url = 'http://127.0.0.1:8000/'
params = {'path': 'https://raw.githubusercontent.com/ultralytics/yolov5/master/data/images/zidane.jpg'}
r = requests.get(base_url, params=params)
print(r.json())
Since your endpoint seems to accept POST
requests, you might consider having the client send the image URL in the body of the request, instead of passing it as a path parameter. Please have a look at the answers here, here and here, as well as FastAPI's documentation, on how to do that.
If you are testing this by typing the aforementioned URLs into the address bar of a web browser, then keep using @app.get()
routes, as when you type a URL in the address bar of your web browser, it performs a GET
request. If, however, you need this to work with POST
requests, you will have to change the endpoint's decorator to @app.post()
(as shown in your question); otherwise, you will come across 405 "Method Not Allowed"
error (see here for more details on such errors).
Finally, the endpoints in the examples above have been defined with async def
, but depending on the operations that may take place inside the endpoints of your application, you may need to define them with normal def
, or use other solutions, when blocking operations need to be performed. Hence, I would suggest having a look at this answer to better understand these concepts and when to use async def
/def
.