How to pass URL as a path parameter to a FastAPI route?
Asked Answered
B

2

3

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
Basicity answered 29/6, 2022 at 12:19 Comment(5)
The path needs to be url encoded.German
do i need to encode before passing or after passingBasicity
before... http://127.0.0.1:8000/https%3A%2F%2Fraw.githubusercontent.com%2Fultralytics%2Fyolov5%2Fmaster%2Fdata%2Fimages%2Fzidane.jpg would be the right url.German
let say i am providing an API which require a user to pass a path. so the end user has to first encode it?Basicity
the client, whether that is and end user, a browser, javascript, does not matter.German
O
10

Option 1

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}')
def pred_image(request: Request):
    return {'path': request.url.path[1:]}

or

@app.get('/{full_path:path}')
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 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.parese.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, please have a look at Option 3 of this answer, which demonstrates how to convert the form <input> element into a path parameter on <form> submission.

Option 2

As @luk2302 mentioned in the comments section, your client (i.e., either 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 server side, you can decode the URL (twice) as follows:

from urllib.parse import unquote 

@app.get('/{path}')
def pred_image(path: str):
    return {'path':unquote(unquote(path))}  

Option 3

Use a query parameter instead, as shown below:

@app.get('/')
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())

Option 4

Since your endpoint seems to accept POST requests, you might consider having the client sending 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.


Note:

If you are testing this through typing the aforementioned URLs into the address bar of a browser, then keep using @app.get() routes, as when you type a URL in the address bar of your 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 would come accross 405 "Method Not Allowed" error (see here for more details on such errors).

Onieonion answered 30/6, 2022 at 11:55 Comment(0)
D
2
from fastapi import FastAPI, Request

app = FastAPI()
@app.get("/proxy/{p:path}")
def read_item(p: str, req: Request):
    query = req.url.query
    if query:
        p += '?' + query
    return p

test url http://127.0.0.1/proxy/http://www.google.com/p/q?a=100&b=200

img req:Request

Deathly answered 24/11, 2022 at 3:46 Comment(1)
Please consider adding some explanation to the source code explaining how it solves the problem. For reference, look at the accepted answer.Professoriate

© 2022 - 2024 — McMap. All rights reserved.