Reputation: 3260
I have created a class-based dependency, similar to what is in the amazing FastAPI tutorial.
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.
Is there a way to use Depends()
, but pass the class initialization parameters via the POST payload instead of in the URL path?
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
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:
Upvotes: 2
Views: 12036
Reputation: 11287
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:
This also works with Pydantic models since they set the __signature__
attribute.
Upvotes: -1
Reputation: 32233
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"
}
Upvotes: 4