pythonswaggermarkdownfastapiopenapi

Insert local image in the FastAPI automatic documentation


Introduction

FastAPI can autogenerate your documentation when you are using FastAPI to create an API.

I am trying to insert an image in the description (markdown) of one of my endpoints, but I can't do it when the image is located in the local hardrive.

I have tried to insert it directly (view the end of this post), but it doesn't work.

I have tried to create an endpoint to serve the images, in this case it only works if the IP of the address is my public IP. It doesn't work if I put localhost or 127.0.0.1. I think I am missing something here.

Minimal example

Installation:

$ pip install fastapi
$ pip install "uvicorn[standard]"

File: main.py

from fastapi import FastAPI
from fastapi.responses import FileResponse

app = FastAPI()

@app.get("/")
def read_root():
    return {"Hello": "World"}

@app.get("/my-endpoint")
def example_function():
    """
    Documentation for my enpoint. Insert some images

    1) This online image works:

    ![This image works](https://upload.wikimedia.org/wikipedia/commons/0/08/STockholmspanorama_1928b.jpg)
    
    2) This local image doesn't work:
    
    ![This image doesn't work](/home/test01/example-photo.jpg)

    3) This local image served by the api works if the link is to the public IP:

    ![This image works](http://10.0.0.15:8000/img/example-photo.jpg)

    4) This local image served by the api doesn't work because when specified as localhost:

    ![This image doesn't work](http://127.0.0.1:8000/img/example-photo.jpg)

    ![This image doesn't work](http://localhost:8000/img/example-photo.jpg)

    """
    return {"This is my endpoint"}


# An endpoint to serve images for documentation
@app.get("/img/example-photo.jpg")
async def read_image():
    return FileResponse("example-photo.jpg")

Execute the API with the following command:

$ uvicorn main:app --reload --host 0.0.0.0 --port 8000

You can access the automatic documentation at:

http://<you-ip>:8000/docs

Result of the example

Result of the automatic documentation

Extra

In my real case the folder structure is the following:

/myProject/
|
|---/docs/
|     |---/img/
|           |---example-photo.jpg
|
|---/src/
       |---/myApp/
              |----main.py

If I try to insert the image directly it doesn't show anything.

![This image does not work](../../docs/img/example-photo.jpg)

Solution

  • Option 1

    The local image should work just fine; all you had to do is to move example-photo.jpg to the directory where your main.py file lies, as return FileResponse("example-photo.jpg") is looking for that image under the directory where the app has been started.

    However, using your approach, you would need an endpoint for each image. Alternatively, you could declare a path parameter, so that you can pass any filename referring to an image in your project. You should also create a folder, e.g., images (in the example below, this should be lying in the same directory as the main.py file), where all the relevant images are kept; otherwise, a user could gain unauthorized access to sensitive information, by passing a filename pointing, for example, to your Python files and/or other sensitive data in the main directory that must be kept out of reach from all outsiders. Example:

    from fastapi import FastAPI
    from fastapi.responses import FileResponse
    import os
    
    app = FastAPI()
    
    @app.get("/")
    def main():
        """
        1) This online image works:
    
        ![This image works](https://upload.wikimedia.org/wikipedia/commons/0/08/STockholmspanorama_1928b.jpg)
    
        2) This local image works:
    
        ![This image works](http://127.0.0.1:8000/img/example-photo.jpg)
        
        3) This local image works:
    
        ![This image works](http://localhost:8000/img/example-photo.jpg)
        """
        return "success"
    
    
    # An endpoint to serve images for documentation
    @app.get("/img/{filename}")
    def get_img(filename: str):
        filepath = os.path.join('images/', os.path.basename(filename))
        return FileResponse(filepath)
    

    Option 2 (Recommended Solution)

    A more elegant solution is to mount a StaticFiles instance to a specific directory, e.g., static (it does not necessarily need to lie in the same directory as your main.py file)—as described in this answer—which would allow you to serve static files automatically from that directory. Any path that starts with /static (you can choose a different path name if you wish) will be handled by it. The directory="static" below refers to the name of the directory that contains your static files/images. Example:

    from fastapi import FastAPI
    from fastapi.responses import FileResponse
    from fastapi.staticfiles import StaticFiles
    
    app = FastAPI()
    app.mount("/static", StaticFiles(directory="static"), name="static")
    
    @app.get("/")
    def main():
        """
        1) This online image works:
    
        ![This image works](https://upload.wikimedia.org/wikipedia/commons/0/08/STockholmspanorama_1928b.jpg)
    
        2) This local image works:
    
        ![This image works](http://127.0.0.1:8000/static/example-photo.jpg)
        
        3) This local image works:
    
        ![This image works](http://localhost:8000/static/example-photo.jpg)
        """
        return "success"