mirror of
https://github.com/rjNemo/fastapi
synced 2026-06-12 05:26:45 +00:00
📝 Add fist Dependency Injection docs
This commit is contained in:
parent
f6667ecfb0
commit
c9758e15a1
10 changed files with 341 additions and 0 deletions
BIN
docs/img/tutorial/dependencies/image01.png
Normal file
BIN
docs/img/tutorial/dependencies/image01.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 78 KiB |
BIN
docs/img/tutorial/dependencies/image02.png
Normal file
BIN
docs/img/tutorial/dependencies/image02.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 104 KiB |
91
docs/tutorial/dependencies/first-steps.md
Normal file
91
docs/tutorial/dependencies/first-steps.md
Normal file
|
|
@ -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.
|
||||||
58
docs/tutorial/dependencies/intro.md
Normal file
58
docs/tutorial/dependencies/intro.md
Normal file
|
|
@ -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.
|
||||||
66
docs/tutorial/dependencies/second-steps.md
Normal file
66
docs/tutorial/dependencies/second-steps.md
Normal file
|
|
@ -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.
|
||||||
12
docs/tutorial/src/dependencies/tutorial001.py
Normal file
12
docs/tutorial/src/dependencies/tutorial001.py
Normal file
|
|
@ -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
|
||||||
27
docs/tutorial/src/dependencies/tutorial002.py
Normal file
27
docs/tutorial/src/dependencies/tutorial002.py
Normal file
|
|
@ -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
|
||||||
34
docs/tutorial/src/dependencies/tutorial003.py
Normal file
34
docs/tutorial/src/dependencies/tutorial003.py
Normal file
|
|
@ -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
|
||||||
49
docs/tutorial/src/dependencies/tutorial004.py
Normal file
49
docs/tutorial/src/dependencies/tutorial004.py
Normal file
|
|
@ -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
|
||||||
|
|
@ -37,6 +37,10 @@ nav:
|
||||||
- Path Operation Configuration: 'tutorial/path-operation-configuration.md'
|
- Path Operation Configuration: 'tutorial/path-operation-configuration.md'
|
||||||
- Path Operation Advanced Configuration: 'tutorial/path-operation-advanced-configuration.md'
|
- Path Operation Advanced Configuration: 'tutorial/path-operation-advanced-configuration.md'
|
||||||
- Custom Response: 'tutorial/custom-response.md'
|
- Custom Response: 'tutorial/custom-response.md'
|
||||||
|
- Dependencies:
|
||||||
|
- Dependencies Intro: 'tutorial/dependencies/intro.md'
|
||||||
|
- First Steps: 'tutorial/dependencies/first-steps.md'
|
||||||
|
- Second Steps: 'tutorial/dependencies/second-steps.md'
|
||||||
- Concurrency and async / await: 'async.md'
|
- Concurrency and async / await: 'async.md'
|
||||||
- Deployment: 'deployment.md'
|
- Deployment: 'deployment.md'
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue