FastAPI: can I use Depends() for parameters in a POST, too?

Question:

Overview

I have created a class-based dependency, similar to what is in the amazing FastAPI tutorial.

Problem

It works, except that the parameters in the dependency (the Depends() portion) are passed as query parameters, meaning that they are part of the URI/URL. I am using the class-based dependency as a means to simplify access to an Azure Datalake, so that the parameters in the Depends are at least somewhat secret. So I would prefer for them to be in the POST portion.

Question

Is there a way to use Depends(), but pass the class initialization parameters via the POST payload instead of in the URL path?

Details

As an example:

The dependency class (just the initialization, which captures the dependency parameters):

class DatalakeConnection(object):
    """Using FastAPI's `Depends` Dependency Injection, this class can have all
    elements needed to connect to a data lake."""

    def __init__(
        self,
        dir: str = my_typical_folder,
        container: str = storage_container.value,
    ):
        service_client = DataLakeServiceClient(
            account_url=storage_uri,
            credential=storage_credential,
        )
        self.file_system_client = service_client.get_file_system_client(
            file_system=container
        )
        self.directory_client = self.file_system_client.get_directory_client(dir)
        self.file_client = None

The FastAPI path function:

@app.post("/datalake")  # I have no response model yet, but will add one
def predictions_from_datalake(
    query: schemas.Query, conn: DatalakeConnection = Depends()
):
    core_df = conn.read_excel(query.file_of_interest) # I create a DataFrame from reading Excel files

Summary

As I said, this works, but the dir and container needed to initialize the class are forced into URL query parameters, but I would like for them to be key-value pairs in the request body of the POST:

Image of the Swagger UI showing the dir as a query parameter instead of part of the POST body

Asked By: Mike Williamson

||

Answers:

You can declare them just like path operation body parameters. More info here Singular values in body:

class DatalakeConnection(object):
    """Using FastAPI's `Depends` Dependency Injection, this class can have all
    elements needed to connect to a data lake."""

    def __init__(
            self,
            dir: str = Body("dir_default"),
            container: str = Body("container_default"),
    ):
        pass

Example of request body:

{
  "dir": "string",
  "container": "string"
}
Answered By: alex_noname

If you want to use Depends with an existing class without updating the defaults on that class, you can create a function with the right signature and pass that to Depends.

def _body_dependify(model_cls):
    """
    Hack around fastapi not supporting Body(...) parameters in dependencies unless
    you specify them in the function signature.
    """
    import functools
    import inspect
    from collections import OrderedDict

    signature = inspect.signature(model_cls)
    signature = signature.replace(return_annotation=model_cls)
    parameters = OrderedDict(signature.parameters)
    for parameter_name in list(parameters):
        parameter = parameters[parameter_name]
        if parameter.default is inspect.Parameter.empty:
            parameter = parameter.replace(default=Body())
        else:
            parameter = parameter.replace(default=Body(parameter.default))
        parameters[parameter_name] = parameter
    signature = signature.replace(parameters=parameters.values())

    @functools.wraps(model_cls)
    def build(*args, **kwargs):
        return model_cls(*args, **kwargs)

    build.__signature__ = signature
    return Depends(build)

Then in your endpoint, you can do:

@app.post("/datalake")  # I have no response model yet, but will add one
def predictions_from_datalake(
    query: schemas.Query, conn: DatalakeConnection = _body_dependify(DatalakeConnection)
):
    core_df = conn.read_excel(query.file_of_interest) # I create a DataFrame from reading Excel files

In the /docs page, the schema looks like this:

screenshot of swagger /docs page from fastapi

This also works with Pydantic models since they set the __signature__ attribute.

Answered By: Lucas Wiman