pythonurlfastapistarlettepath-parameter

How to pass URL as a path parameter to a FastAPI route?


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

Solution

  • 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}')
    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.

    Option 2

    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))}  
    

    Option 3

    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())
    

    Option 4

    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.


    Note:

    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.