FastAPI Shared Dependencies - NFSandbox/sh_trade_backend GitHub Wiki

[!note] There is no official solution for shared dependencies that utilized the return value. This blog only discuss about the workaround on this issue.

Issues

I just faced a issue when working with one of my FastAPI project: What if I want to add a dependency to a whole sub-router of the app?

First, let's look into the basic pattern of FastAPI dependency: We use Depends to mark a param of the function to change it into dependency. Also we could provide the dependency function like: Depends(dep_func), in this case, the param value would be the result of the function.

def some_func(user: Annotated[User, Depends(get_user)])

What if, I want all sub router, for example /admin, to share a common dependency called admin_type()?

When We Don't Need Return Value

One case is that we don't need the value of the dependency function, we just want to make sure it being executed. For example, in order to protect the /admin sub router, we need to make sure all requests are sent by user with admin permission. Then we could create a dependency called is_admin(), and for this dependency:

  • Return value is not important
  • Raise error if request not sent by admin user

Then we could directly use the dependencies=[] params provided by FastAPI:

def is_admin():
    if not admin:
	    raise HTTPException(...)

auth_router = APIRouter(
	# this will make sure `is_admin()` runned before any endpoint being called in this sub router
    dependencies=[Depends(is_admin)]  
)

When We Need The Value

But, what if we need the return value of the shared dependencies? For example for admin_type() dependencies, we need to react to the request based on the type of the admin?

Currently there is no officially recommended way to do this. The discussion could be found on GitHub Discussions, where a user provided a sweet workaround:

AdminTypeDeps = Annotated[AdminType, Depends(admin_type)]

@admin_router.get('/info')
def get_admin_info(admin_type: AdminTypeDeps):
    # do sth

@admin_router.get('/operation')
def get_admin_info(admin_type: AdminTypeDeps):
    # do sth

Notice that:

  • We create a new type annoation AdminTypeDeps and reuse it.
  • We still need to add deps params in every function that uses it.

The advantage is that, when we want to change the behaviour or logic of this dependency, the only place we need to make changes of is the line:

AdminTypeDeps = Annotated[AdminType, Depends(admin_type)]

Refs