FastAPI Study Diary (6) — Dependency Injection

콘텐츠

Today, I want to write about Dependency Injection (DI) in FastAPI, something I’ve studied recently. Even in previous sessions (like the third one), I was unconsciously using DI when receiving requests with subclasses of Pydantic’s BaseModel, but this time I decided to delve deeper into DI specifically in FastAPI. (From now on, I may refer to Dependency Injection simply as DI.)

FastAPI is designed with DI in mind, or rather, to facilitate DI.

Dependency Injection by Functions

This is a method where a function is created for injection, and through it, dependencies are injected.

An async function named pagination is prepared as shown below, which is set to accept two parameters: offset and limit. The return value is a tuple containing offset and limit.

In the path operation function, by passing the `pagination` function to the `Depends` function, there is a mechanism in place where the request goes through the `pagination` function, and the values for `offset` and `limit` are injected.

While I have already written about how to receive request data using Pydantic model classes in the last article, this method alone may not seem very advantageous, however,

By doing it this way, where the value of limit cannot be less than 10, or having the freedom to perform validations and transformations of values within this injection function, I found this to be the strength of this method.

Dependency Injection Using Callable Classes

You can achieve the same functionality using callable classes as shown below.

After initializing an object of the callable class, you can use it in the same way as function-based DI as shown below:

The advantage of using callable classes is that you can dynamically pass values to the initializer and then utilize them in the midst of dependency injection. In this case, a value named `minimum_limit` is passed to the initializer when creating an instance of the Pagination class and stored as an object variable, which is then used during dependency injection. This flexibility is one of the strengths of using callable classes.

Upon further investigation, it turns out that dependency injection using this class can be accomplished without the need for callable classes, achieving similar functionality. As shown below, a regular function (in this case, `get_offset_and_limit`) can be implemented in exactly the same way to create an instance of the Pagination class.

When configuring the path operation function, instead of passing the `pagination` object to the Depends function, you only need to pass the `pagination` object’s `get_offset_and_limit` function as shown below.

Having implemented up to this point, I feel that functions in Python have a strong positioning as ‘function pointers.’ Being raised on C language, it made me appreciate Python all over again.

Dependency Injection via Router

Another feature I discovered was the ability to configure dependency injection (DI) for each router, which seems particularly useful when you want to perform a common middleware-like process — such as validating information in the request headers (similar to middleware in Node.js) — across multiple APIs.

First, you set up a callable class to receive the DI as shown below. This `ApiTokenHeader` class allows for the API token to be set via its initializer.

In the DI process, inject the value of the header named ‘api-token’ found in the request headers into a variable named api_token. Note that header names can't contain underscores (_), but FastAPI automatically converts hyphens to underscores, so you can send a request with the header name 'api-token'. If this api_token matches the one set in the class initializer (self.api_token), then the process completes normally and the API's operation (the path operation function's process) continues. If it does not match, a 403 Forbidden error is returned.

To apply this DI callable class, pass it to the Depends() function when creating an APIRouter object, and then set it in a list as the dependencies, as shown below.

Following this setup, the DI callable class is applied to all APIs added to the router thereafter. This means that every API will now perform a check on the ‘api-token’ in the header. This is quite convenient as it eliminates the need to write token validation for the headers in each API individually. As demonstrated below, when the ‘api-token’ is properly set, the response will be returned successfully.

(For clarity, I’ve implemented a simple header token check this time. Please be aware that this does not guarantee sufficient security for the protection of actual APIs.)

By attaching the DI mechanism to each Router like this, it’s possible to add open APIs that do not require header checks to a separate APIRouter object as shown below, thereby differentiating security levels.

In this case, it is possible to easily create a distinction where `/public` is for open APIs and `/user` requires header authentication.

As demonstrated below, you can access APIs under `/public` without including a header and still receive a response.

On the other hand, if you access APIs under `/user` without header information or send the wrong token, you will receive an HTTP Status = 403 Forbidden in return.

Through this process, I have gained a fairly solid understanding of how to construct APIs necessary for a product using Dependency Injection (DI) with FastAPI.

Indeed, I didn’t always have a favorable impression of the term “Dependency Injection.” I’ve felt that introducing a Dependency Injection framework or notation like Dagger into Android apps could make the code more difficult to read and reduce the freedom of programming. Additionally, unless developed by a team of a certain level of quality, it could become a breeding ground for bugs and make debugging a nightmare. I was once tasked with salvaging an app that had been rigidly written with Dagger, where I had to painstakingly unravel each dependency and rectify the chaotic memory allocation.

Having implemented DI with FastAPI this time, I realized that Dependency Injection is incredibly convenient for REST APIs, where the beginning and end of sessions are clearly defined. I also understood why DI first gained popularity within the Java backend framework community.

Conversely, it was a thought-provoking experience to consider whether it is truly the optimal solution to develop installed mobile apps — where there is a vast array of user interactions and the lifecycle of apps or screens is quite dynamic and unpredictable — using Dependency Injection. I suppose the motive to centrally manage through DI arises because of the numerous interaction and lifecycle patterns, but it made me reconsider whether many people can use DI with a perfect image of how the centrally managed components will be used.

Stay tuned!

If you have any questions about this article or our blog, feel free to drop me an email at:
[email protected]

By the way, I work at Goldrush Computing, a mobile app and web development company based in Tokyo and Ho Chi Minh City, Vietnam. Our current focus is on creating web services and backend systems that specialize in processing data and documents utilizing the OpenAI API. If you’re on the lookout for an external team to bring your product to life, don’t hesitate to get in touch!

요약하다
The article discusses Dependency Injection (DI) in FastAPI, focusing on its implementation through functions and callable classes. It explains how to use DI for request handling and validation, emphasizing the flexibility and advantages of using callable classes. Additionally, it explores DI via routers for configuring dependencies at the router level, enabling common processes like header validation across multiple APIs. The article highlights the convenience and efficiency of DI in building REST APIs with FastAPI, contrasting it with experiences in Android app development with Dagger. It concludes by reflecting on the suitability of DI for various application types and the benefits it offers in managing dependencies in REST API development.