About FastAPI Dependency - NFSandbox/sh_trade_backend GitHub Wiki

In this article, we will discuss about when to use FastAPI dependencies, and what kind of process should be encapsulated as a FastAPI dependency.

What Should Be A Dependency

In my personal view, a callable object, for example, a function or class with __call__(), should be written into a FastAPI dependency if it satisfys:

  • Will be reused frequently.
  • Parameters are "static" and won't be changed in most of the use cases. (We will explain this below)
  • Do not have unnecessary side effect, e.g.: Updating value in database.

Being Reused Frequently

This is easy to understand, since the reason of creating dependencies is to reused them in FastAPI conveniently.

Static Parameters

First let me explain what is "static" parameters, that actually illustrate a situation of the use cases of the callable.

If we say a callable's parameters are "static", we mean that in most of the use cases, the parameters of this callable could directly depend on other dependency, or depend on FastAPI endpoint data (e.g.: Query and Body user input when calling the endpoint, or Respone, Request in FastAPI)


For example, consider a function get_current_user_from_token, it may have the following pattern:

def get_current_user_from_token(
    token: Annotated[str, Depends(get_current_token_from_cookies)]
)

In this case, function get_current_user_from_token has a parameter token, and since the feature of this function is to get relavant info of current user, so in most of cases, the token info should nearly always be retrived from request Cookies based on the functionality of this function.

The idea behind this is we want all parameters of a dependency could depend on other sub-dependencies. And if the use cases is changing in different use cases, it will be hard to use a unified dependency to retrieve the parameters.


Now consider another example get_items_from_user. You could consider that this function will retrieve all items posted by a specific user in a second-hand trading system.

def get_items_from_user(user: User, ignore_hide: bool)

This process is also frequently used, but should we write it in FastAPI dependency pattern? Personally my answer is negative. Let's check out this function:

  • It is indeed being used frequently. There are lots of cases that we need to retrieve item posted by a user.
  • However, the parameters is not "static" across different use cases.

For example:

  • User Item Management: we want to show user the item he post, in this case, user should be current user. And the item being hidden by user should also visible in management page, so ignore_hidden = False
  • Other Users' Profile: When we go to some user's homepage, we want to see all items published by this user. In this case, user should be decided by which user's profile we are accessing, and we should also excluding the item being hidden.(ignore_hidden = True)

In this way, the parameters of this function user and ignore_hidden is hard to directly depend on other dependencies.

Limited Workaround With Parameterized Dependency

Some may argue that we still have another tools: Parameterized Dependency.

For those who are not familiar with this concept, please check out FastAPI - Advanced Dependency

Yes, in some cases it is indeed useful. Still using example above, the uncertainty of ignore_hidden parameter could be eliminated by using parameterized dependency.

# only for showing example of the concept, code not been tested in real env
class GetItemsFromUser:
    ignore_hidden: bool
    
    __init__(self, ignore_hidden: bool = False):
        self.ignore_hidden = ignore_hidden
    
    __call__(self, user):
        get_item_infO(user_id=user.id, ignore_hidden=self.ignore_hidden)

# the two instance above could all be used as FastAPI dependency
# since they have __call__() magic method
get_user_items_ignore_hidden = GetItemsFromUser(ignore_hidden = True)
get_user_items = GetItemsFromUser(ignore_hidden = False)

However, in our case, the issue of user parameter is still unsolved.

No Unnecessary Side Effect

This is also easy to understand. Since dependencies could depend on other dependencies, we will actually get a "dependency tree" when we going to call a dependency.

And now imagine at some place in this dependency tree, the dependency will produce some side-effect, then it would be really hard to debug if any error occurred related to such changed made by deps in deeper place of the deps tree.

Declarative Data Retrieving

In most of the time, we should consider FastAPI dependency a declarative tools to free us from manually retrieving info step by step, and instead we only need to tell the framework the final dependency I need in the end. And this is achieved by using param = Depends() or Annotation[type, Depends(other_deps)] to declare what return value of other dependencies we should used as the parameters of this dependency.

We are actually declaring a dependency tree.

Dependency Tree