10 changed files with 341 additions and 0 deletions
After Width: | Height: | Size: 78 KiB |
After Width: | Height: | Size: 104 KiB |
@ -0,0 +1,91 @@ |
|||
Let's see a very simple example of the **Dependency Injection** system. |
|||
|
|||
It will be so simple that it is not very useful, for now. |
|||
|
|||
But this way we can focus on how the **Dependency Injection** system works. |
|||
|
|||
In the next chapters we'll extend it to see how can it be so useful. |
|||
|
|||
## Create a dependency, or "dependable" |
|||
|
|||
Let's first focus on the dependency. |
|||
|
|||
It is just a function that can take all the same parameters that a path operation function can take: |
|||
|
|||
```Python hl_lines="6 7" |
|||
{!./tutorial/src/dependencies/tutorial001.py!} |
|||
``` |
|||
|
|||
That's it. |
|||
|
|||
**2 lines**. |
|||
|
|||
And it has the same shape and structure that all your path operation functions. |
|||
|
|||
You can think of it as a path operation function without the "decorator" (the `@app.get("/some-path")`). |
|||
|
|||
And it can return anything you want. |
|||
|
|||
In this case, this dependency expects: |
|||
|
|||
* An optional query parameter `q` that is a `str`. |
|||
* An optional query parameter `skip` that is an `int`, and by default is `0`. |
|||
* An optional query parameter `limit` that is an `int`, and by default is `100`. |
|||
|
|||
And then it just returns a `dict` containing those values. |
|||
|
|||
## Import `Depends` |
|||
|
|||
```Python hl_lines="1" |
|||
{!./tutorial/src/dependencies/tutorial001.py!} |
|||
``` |
|||
|
|||
## Declare the dependency, in the "dependant" |
|||
|
|||
The same way you use `Body`, `Query`, etc. with your path operation function parameters, use `Depends` with a new parameter: |
|||
|
|||
```Python hl_lines="11" |
|||
{!./tutorial/src/dependencies/tutorial001.py!} |
|||
``` |
|||
|
|||
Although you use it in the parameters of your function too, `Depends` works a bit differently. |
|||
|
|||
You only give `Depends` a single parameter. |
|||
|
|||
This parameter must be a function with the same parameters that can be taken by a path operation function. |
|||
|
|||
Whenever a new request arrives, **FastAPI** will take care of: |
|||
|
|||
* Calling your dependency ("dependable") function with the correct parameters. |
|||
* Get the result from your function. |
|||
* Assign that result to the parameter in your path operation function. |
|||
|
|||
!!! note |
|||
Notice that you don't have to create a special class and pass it somewhere to **FastAPI** or anything similar. |
|||
|
|||
You just pass it to `Depends` and **FastAPI** knows how to do the rest. |
|||
|
|||
## To `async` or not to `async` |
|||
|
|||
As dependencies will also be called by **FastAPI** (the same as your path operation functions), the same rules apply while defining your functions. |
|||
|
|||
You can use `async def` or normal `def`. |
|||
|
|||
And you can declare dependencies with `async def` inside of normal `def` path operation functions, or `def` dependencies inside of `async def` path operation functions. |
|||
|
|||
It doesn't matter. **FastAPI** will know what to do. |
|||
|
|||
!!! note |
|||
If you don't if you should use `async def` or not, check the section about [`async` and `await` in the docs](async.md). |
|||
|
|||
## Integrated wiht OpenAPI |
|||
|
|||
All the request declarations, validations and requirements of your dependencies (and sub-dependencies) will be integrated in the same OpenAPI schema. |
|||
|
|||
So, the interactive docs will have all the information they need, while you keep all the flexibility of the dependencies: |
|||
|
|||
<img src="/img/tutorial/dependencies/image01.png"> |
|||
|
|||
## Recap |
|||
|
|||
Create Dependencies with **2 lines** of code. |
@ -0,0 +1,58 @@ |
|||
**FastAPI** has a very powerful but intuitive **<abbr title="also known as components, resources, providers, services, injectables">Dependency Injection</abbr>** system. |
|||
|
|||
It is designed to be very simple to use, and to make it very easy for any developer to integrate other components with **FastAPI**. |
|||
|
|||
## "Dependency Injection"? |
|||
|
|||
**"Dependency Injection"** means, in programming, that there is a way for your code (in this case, your path operation functions) to declare things that it requires to work and use. |
|||
|
|||
And then, that system (in this case **FastAPI**) will take care of doing whatever is needed to provide your code with that thing that it needs. |
|||
|
|||
If you look at it, path operation functions are declared to be used whenever a path and operation matches, and then **FastAPI** will take care of calling the function with the correct parameters and use the response. |
|||
|
|||
Actually, all (or most) of the web frameworks work in this same way. |
|||
|
|||
You never call those functions directly. The are called by your framework (in this case, **FastAPI**). |
|||
|
|||
With the Dependency Injection system, you can also tell **FastAPI** that your path operation function also "depends" on something else that should be executed before your path operation function, and **FastAPI** will take care of executing it and "injecting" the results. |
|||
|
|||
Other common terms for this same idea are: |
|||
|
|||
* resources |
|||
* providers |
|||
* services |
|||
* injectables |
|||
|
|||
## **FastAPI** plug-ins |
|||
|
|||
Integrations and "plug-in"s can be built using the **Dependency Injection** system. But in fact, there is actually **no need to create "plug-ins"**, as by using dependencies it's possible to declare an infinite number of integrations and interactions that become available to your path operation functions. |
|||
|
|||
And dependencies can be created in a very simple and intuitive way that allow you to just import the Python packages you need, and integrate them with your API functions in a couple of lines of code, _literally_. |
|||
|
|||
## **FastAPI** compatibility |
|||
|
|||
The simplicity of the dependency injection system makes **FastAPI** compatible with: |
|||
|
|||
* all the relational databases |
|||
* NoSQL databases |
|||
* external packages |
|||
* external APIs |
|||
* authentication and authorization systems |
|||
* API usage monitoring systems |
|||
* response data injection systems |
|||
* etc. |
|||
|
|||
|
|||
## Simple and Powerful |
|||
|
|||
Although the hierarchical dependency injection system is very simple to define and use, it's still very powerful. |
|||
|
|||
You can define dependencies that in turn can define dependencies themselves. |
|||
|
|||
In the end, a hierarchical tree of dependencies is built, and the **Dependency Injection** system takes care of solving all these dependencies for you (and your dependencies) and providing the results at each step. |
|||
|
|||
## Integrated with OpenAPI |
|||
|
|||
All these dependencies, while declaring their requirements, might have been adding parameters, validations, etc. to your path operations. |
|||
|
|||
**FastAPI** will take care of adding it all to the OpenAPI schema, so that it is shown in the interactive documentation systems. |
@ -0,0 +1,66 @@ |
|||
Before diving deeper into the **Dependency Injection** system, let's upgrade the previous example. |
|||
|
|||
## A `dict` from the previous example |
|||
|
|||
In the previous example, we where returning a `dict` from our dependency ("dependable"): |
|||
|
|||
```Python hl_lines="7" |
|||
{!./tutorial/src/dependencies/tutorial001.py!} |
|||
``` |
|||
|
|||
But then we get a `dict` in the parameter `commons` of the path operation function. |
|||
|
|||
And we know that `dict`s can't provide a lot of editor support because they can't know their keys and value types. |
|||
|
|||
## Create a Pydantic model |
|||
|
|||
But we are already using Pydantic models in other places and we have already seen all the benefits. |
|||
|
|||
Let's use them here too. |
|||
|
|||
Create a model for the common parameters (and don't pay attention to the rest, for now): |
|||
|
|||
```Python hl_lines="10 11 12 13" |
|||
{!./tutorial/src/dependencies/tutorial002.py!} |
|||
``` |
|||
|
|||
## Return a Pydantic model |
|||
|
|||
Now we can return a Pydantic model from the dependency ("dependable") with the same data as the dict before: |
|||
|
|||
```Python hl_lines="17" |
|||
{!./tutorial/src/dependencies/tutorial002.py!} |
|||
``` |
|||
|
|||
## Declare the Pydantic model |
|||
|
|||
We can now come back to the path operation function and declare the type of the `commons` parameter to be that Pydantic model: |
|||
|
|||
```Python |
|||
commons: CommonQueryParams = Depends(common_parameters) |
|||
``` |
|||
|
|||
It won't be interpreted as a JSON request `Body` because we are using `Depends`: |
|||
|
|||
```Python hl_lines="21" |
|||
{!./tutorial/src/dependencies/tutorial002.py!} |
|||
``` |
|||
|
|||
!!! info |
|||
In the case of dependencies with `Depends`, the type of the parameter is only to get editor support. |
|||
|
|||
Your dependencies won't be enforced to return a specific type of data. |
|||
|
|||
## Use the Pydantic model |
|||
|
|||
And now we can use that model in our code, with all the lovable editor support: |
|||
|
|||
```Python hl_lines="23 24 25" |
|||
{!./tutorial/src/dependencies/tutorial002.py!} |
|||
``` |
|||
|
|||
<img src="/img/tutorial/dependencies/image02.png"> |
|||
|
|||
## Recap |
|||
|
|||
By using Pydantic models in your dependencies too you can keep all the editor support that **FastAPI** is designed to support. |
@ -0,0 +1,12 @@ |
|||
from fastapi import Depends, FastAPI |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
async def common_parameters(q: str = None, skip: int = 0, limit: int = 100): |
|||
return {"q": q, "skip": skip, "limit": limit} |
|||
|
|||
|
|||
@app.get("/items/") |
|||
async def read_items(commons: dict = Depends(common_parameters)): |
|||
return commons |
@ -0,0 +1,27 @@ |
|||
from fastapi import Depends, FastAPI |
|||
from pydantic import BaseModel |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}] |
|||
|
|||
|
|||
class CommonQueryParams(BaseModel): |
|||
q: str = None |
|||
skip: int = None |
|||
limit: int = None |
|||
|
|||
|
|||
async def common_parameters(q: str = None, skip: int = 0, limit: int = 100): |
|||
return CommonQueryParams(q=q, skip=skip, limit=limit) |
|||
|
|||
|
|||
@app.get("/items/") |
|||
async def read_items(commons: CommonQueryParams = Depends(common_parameters)): |
|||
response = {} |
|||
if commons.q: |
|||
response.update({"q": commons.q}) |
|||
items = fake_items_db[commons.skip : commons.limit] |
|||
response.update({"items": items}) |
|||
return response |
@ -0,0 +1,34 @@ |
|||
from typing import List |
|||
|
|||
from fastapi import Cookie, Depends, FastAPI |
|||
from pydantic import BaseModel |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class InterestsTracker(BaseModel): |
|||
track_code: str |
|||
interests: List[str] |
|||
|
|||
|
|||
fake_tracked_users_db = { |
|||
"Foo": {"track_code": "Foo", "interests": ["sports", "movies"]}, |
|||
"Bar": {"track_code": "Bar", "interests": ["food", "shows"]}, |
|||
"Baz": {"track_code": "Baz", "interests": ["gaming", "virtual reality"]}, |
|||
} |
|||
|
|||
|
|||
async def get_tracked_interests(track_code: str = Cookie(None)): |
|||
if track_code in fake_tracked_users_db: |
|||
track_dict = fake_tracked_users_db[track_code] |
|||
track = InterestsTracker(**track_dict) |
|||
return track |
|||
return None |
|||
|
|||
|
|||
@app.get("/interests/") |
|||
async def read_interests( |
|||
tracked_interests: InterestsTracker = Depends(get_tracked_interests) |
|||
): |
|||
response = {"interests": tracked_interests.interests} |
|||
return response |
@ -0,0 +1,49 @@ |
|||
from random import choice |
|||
from typing import List |
|||
|
|||
from fastapi import Cookie, Depends, FastAPI |
|||
from pydantic import BaseModel |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class InterestsTracker(BaseModel): |
|||
track_code: str |
|||
interests: List[str] |
|||
|
|||
|
|||
fake_tracked_users_db = { |
|||
"Foo": {"track_code": "Foo", "interests": ["sports", "movies"]}, |
|||
"Bar": {"track_code": "Bar", "interests": ["food", "shows"]}, |
|||
"Baz": {"track_code": "Baz", "interests": ["gaming", "virtual reality"]}, |
|||
} |
|||
|
|||
|
|||
async def get_tracked_interests(track_code: str = Cookie(None)): |
|||
if track_code in fake_tracked_users_db: |
|||
track_dict = fake_tracked_users_db[track_code] |
|||
track = InterestsTracker(**track_dict) |
|||
return track |
|||
return None |
|||
|
|||
|
|||
class ComplexTracker: |
|||
def __init__(self, tracker: InterestsTracker = Depends(get_tracked_interests)): |
|||
self.tracker = tracker |
|||
|
|||
def random_interest(self): |
|||
""" |
|||
Get a random interest from the tracked ones for the current user. |
|||
If the user doesn't have tracked interests, return a random one from the ones available. |
|||
""" |
|||
if self.tracker.interests: |
|||
return choice(self.tracker.interests) |
|||
return choice( |
|||
["sports", "movies", "food", "shows", "gaming", "virtual reality"] |
|||
) |
|||
|
|||
|
|||
@app.get("/suggested-category") |
|||
async def read_suggested_category(tracker: ComplexTracker = Depends(None)): |
|||
response = {"category": tracker.random_interest()} |
|||
return response |
Loading…
Reference in new issue