Browse Source

📝 Update and add docs for dependencies

pull/11/head
Sebastián Ramírez 6 years ago
parent
commit
b79c13baed
  1. 16
      docs/src/dependencies/tutorial002.py
  2. 39
      docs/src/dependencies/tutorial003.py
  3. 54
      docs/src/dependencies/tutorial004.py
  4. 20
      docs/src/dependencies/tutorial005.py
  5. 21
      docs/src/dependencies/tutorial006.py
  6. 71
      docs/tutorial/dependencies/advanced-dependencies.md
  7. 177
      docs/tutorial/dependencies/classes-as-dependencies.md
  8. 2
      docs/tutorial/dependencies/first-steps-functions.md
  9. 72
      docs/tutorial/dependencies/second-steps.md
  10. 60
      docs/tutorial/dependencies/sub-dependencies.md
  11. 10
      mkdocs.yml

16
docs/src/dependencies/tutorial002.py

@ -1,5 +1,4 @@
from fastapi import Depends, FastAPI
from pydantic import BaseModel
app = FastAPI()
@ -7,18 +6,15 @@ 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)
class CommonQueryParams:
def __init__(self, q: str = None, skip: int = 0, limit: int = 100):
self.q = q
self.skip = skip
self.limit = limit
@app.get("/items/")
async def read_items(commons: CommonQueryParams = Depends(common_parameters)):
async def read_items(commons: CommonQueryParams = Depends(CommonQueryParams)):
response = {}
if commons.q:
response.update({"q": commons.q})

39
docs/src/dependencies/tutorial003.py

@ -1,34 +1,23 @@
from typing import List
from fastapi import Cookie, Depends, FastAPI
from pydantic import BaseModel
from fastapi import Depends, FastAPI
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"]},
}
fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]
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 CommonQueryParams:
def __init__(self, q: str = None, skip: int = 0, limit: int = 100):
self.q = q
self.skip = skip
self.limit = limit
@app.get("/interests/")
async def read_interests(
tracked_interests: InterestsTracker = Depends(get_tracked_interests)
):
response = {"interests": tracked_interests.interests}
@app.get("/items/")
async def read_items(commons=Depends(CommonQueryParams)):
response = {}
if commons.q:
response.update({"q": commons.q})
items = fake_items_db[commons.skip : commons.limit]
response.update({"items": items})
return response

54
docs/src/dependencies/tutorial004.py

@ -1,49 +1,23 @@
from random import choice
from typing import List
from fastapi import Cookie, Depends, FastAPI
from pydantic import BaseModel
from fastapi import Depends, FastAPI
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
fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]
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"]
)
class CommonQueryParams:
def __init__(self, q: str = None, skip: int = 0, limit: int = 100):
self.q = q
self.skip = skip
self.limit = limit
@app.get("/suggested-category")
async def read_suggested_category(tracker: ComplexTracker = Depends(None)):
response = {"category": tracker.random_interest()}
@app.get("/items/")
async def read_items(commons: CommonQueryParams = Depends()):
response = {}
if commons.q:
response.update({"q": commons.q})
items = fake_items_db[commons.skip : commons.limit]
response.update({"items": items})
return response

20
docs/src/dependencies/tutorial005.py

@ -0,0 +1,20 @@
from fastapi import Cookie, Depends, FastAPI
app = FastAPI()
def query_extractor(q: str = None):
return q
def query_or_cookie_extractor(
q: str = Depends(query_extractor), last_query: str = Cookie(None)
):
if not q:
return last_query
return q
@app.get("/items/")
async def read_query(query_or_default: str = Depends(query_or_cookie_extractor)):
return {"q_or_cookie": query_or_default}

21
docs/src/dependencies/tutorial006.py

@ -0,0 +1,21 @@
from fastapi import Depends, FastAPI
app = FastAPI()
class FixedContentQueryChecker:
def __init__(self, fixed_content: str):
self.fixed_content = fixed_content
def __call__(self, q: str = ""):
if q:
return self.fixed_content in q
return False
checker = FixedContentQueryChecker("bar")
@app.get("/query-checker/")
async def read_query_check(fixed_content_included: bool = Depends(checker)):
return {"fixed_content_in_query": fixed_content_included}

71
docs/tutorial/dependencies/advanced-dependencies.md

@ -0,0 +1,71 @@
!!! danger
This is, more or less, an "advanced" chapter.
If you are just starting with **FastAPI** you might want to skip this chapter and come back to it later.
## Parameterized dependencies
All the dependencies we have seen are a fixed function or class.
But there could be cases where you want to be able to set parameters on the dependency, without having to declare many different functions or classes.
Let's imagine that we want to have a dependency that checks if the query parameter `q` contains some fixed content.
But we want to be able to parameterize that fixed content.
## A "callable" instance
In Python there's a way to make an instance of a class a "callable".
Not the class itself (which is already a callable), but an instance of that class.
To do that, we declare a method `__call__`:
```Python hl_lines="10"
{!./src/dependencies/tutorial006.py!}
```
## Parameterize the instance
And now, we can use `__init__` to declare the parameters of the instance that we can use to "parameterize" the dependency:
```Python hl_lines="7"
{!./src/dependencies/tutorial006.py!}
```
In this case, **FastAPI** won't ever touch or care about `__init__`, we will use it directly in our code.
## Create an instance
We could create an instance of this class with:
```Python hl_lines="16"
{!./src/dependencies/tutorial006.py!}
```
And that way we are able to "parameterize" our dependency, that now has `"bar"` inside of it, as the attribute `checker.fixed_content`.
## Use the instance as a dependency
Then, we could use this `checker` in a `Depends(checker)`, instead of `Depends(FixedContentQueryChecker)`, because the dependency is the instance, `checker`, not the class itself.
And when solving the dependency, **FastAPI** will call this `checker` like:
```Python
checker(q="somequery")
```
...and pass whatever that returns as the value of the dependency in our path operation function as the parameter `fixed_content_included`:
```Python hl_lines="20"
{!./src/dependencies/tutorial006.py!}
```
!!! tip
All this might seem contrived. And it might not be very clear how is it useful yet.
These examples are intentionally simple, but show how it all works.
In the chapters about security, you will be using utility functions that are implemented in this same way.
If you understood all this, you already know how those utility tools for security work underneath.

177
docs/tutorial/dependencies/classes-as-dependencies.md

@ -0,0 +1,177 @@
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"
{!./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.
We can do better...
## What makes a dependency
Up to now you have seen dependencies declared as functions.
But that's not the only way to declare dependencies (although it would probably be the more common).
The key factor is that a dependency should be a "callable".
A "**callable**" in Python is anything that Python can "call" like a function.
So, if you have an object `something` (that might _not_ be a function) and you can do:
```Python
something()
```
or
```Python
something(some_argument, some_keyword_argument="foo")
```
then it is a "callable".
## Classes as dependencies
You might notice that to create an instance of a Python class, you use that same syntax.
So, a Python class is also a **callable**.
Then, in **FastAPI**, you could use a Python class as a dependency.
What FastAPI actually checks is that it is a "callable" (function, class or anything else) and the parameters defined.
If you pass a "callable" as a dependency in **FastAPI**, it will analyze the parameters for that "callable", and process them in the same way as the parameters for a path operation function. Including sub-dependencies.
That also applies to callables with no parameters at all. The same as would be for path operation functions with no parameteres.
Then, we can change the dependency "dependable" `common_parameters` from above to the class `CommonQueryParameters`:
```Python hl_lines="9 10 11 12 13"
{!./src/dependencies/tutorial002.py!}
```
Pay attention to the `__init__` method used to create the instance of the class:
```Python hl_lines="10"
{!./src/dependencies/tutorial002.py!}
```
...it has the same parameters as our previous `common_parameters`:
```Python hl_lines="6"
{!./src/dependencies/tutorial001.py!}
```
Those parameters are what **FastAPI** will use to "solve" the dependency.
In both cases, it will have:
* an optional `q` query parameter.
* a `skip` query parameter, with a default of `0`.
* a `limit` query parameter, with a default of `100`.
In both cases the data will be converted, validated, documented on the OpenAPI schema, etc.
## Use it
Now you can declare your dependency using this class.
And as when **FastAPI** calls that class the value that will be passed as `commons` to your function will be an "instance" of the class, you can declare that parameter `commons` to be of type of the class, `CommonQueryParams`.
```Python hl_lines="17"
{!./src/dependencies/tutorial002.py!}
```
## Type annotation vs `Depends`
In the code above, you are declaring `commons` as:
```Python
commons: CommonQueryParams = Depends(CommonQueryParams)
```
The last `CommonQueryParams`, in:
```Python
... = Depends(CommonQueryParams)
```
...is what **FastAPI** will actually use to know what is the dependency.
From it is that FastAPI will extract the declared parameters and that is what FastAPI will actually call.
---
In this case, the first `CommonQueryParams`, in:
```Python
commons: CommonQueryParams ...
```
...doesn't have any special meaning for **FastAPI**. FastAPI won't use it for data conversion, validation, etc. (as it is using the `= Depends(CommonQueryParams)` for that).
You could actually write just:
```Python
commons = Depends(CommonQueryParams)
```
..as in:
```Python hl_lines="17"
{!./src/dependencies/tutorial003.py!}
```
But declaring the type is encouraged as that way your editor will know what will be passed as the parameter `commons`, and then it can help you with code completion, type checks, etc:
```Python hl_lines="19 20 21"
{!./src/dependencies/tutorial002.py!}
```
## Shortcut
But you see that we are having some code repetition here, writing `CommonQueryParams` twice:
```Python
commons: CommonQueryParams = Depends(CommonQueryParams)
```
**FastAPI** provides a shortcut for these cases, in where the dependency is *specifically* a class that **FastAPI** will "call" to create an instance of the class itself.
For those specific cases, you can do the following:
Instead of writing:
```Python
commons: CommonQueryParams = Depends(CommonQueryParams)
```
...you write:
```Python
commons: CommonQueryParams = Depends()
```
So, you can declare the dependency as the type of the variable, and use `Depends()` as the "default" value, without any parameter, instead of having to write the full class *again* inside of `Depends(CommonQueryParams)`.
So, the same example would look like:
```Python hl_lines="17"
{!./src/dependencies/tutorial004.py!}
```
...and **FastAPI** will know what to do.
!!! tip
If all that seems more confusing than helpful, disregard it, you don't *need* it.
It is just a shortcut. Because **FastAPI** cares about helping you minimize code repetition.

2
docs/tutorial/dependencies/first-steps.md → docs/tutorial/dependencies/first-steps-functions.md

@ -22,7 +22,7 @@ That's it.
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")`).
You can think of it as a path operation function without the "decorator" (without the `@app.get("/some-path")`).
And it can return anything you want.

72
docs/tutorial/dependencies/second-steps.md

@ -1,72 +0,0 @@
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"
{!./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="11 12 13 14"
{!./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"
{!./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"
{!./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"
{!./src/dependencies/tutorial002.py!}
```
<img src="/img/tutorial/dependencies/image02.png">
## Trees of hierarchical dependencies
With the **Dependency Injection** system you can build arbitrarily deep trees of hierarchical dependencies (also known as dependency graphs) by having dependencies that also have dependencies themselves.
You will see examples of these dependency trees in the next chapters about security.
## Recap
By using Pydantic models in your dependencies too you can keep all the editor support that **FastAPI** is designed to support.

60
docs/tutorial/dependencies/sub-dependencies.md

@ -0,0 +1,60 @@
You can create dependencies that have sub-dependencies.
They can be as "deep" as you need them to be.
**FastAPI** will take care of solving them.
### First dependency "dependable"
You could create a first dependency ("dependable") like:
```Python hl_lines="6 7"
{!./src/dependencies/tutorial005.py!}
```
It declares an optional query parameter `q` as a `str`, and then it just returns it.
This is quite simple (not very useful), but will help us focus on how the sub-dependencies work.
### Second dependency, "dependable" and "dependant"
Then you can create another dependency function (a "dependable") that at the same time declares a dependency of its own (so it is a "dependant" too):
```Python hl_lines="11"
{!./src/dependencies/tutorial005.py!}
```
Let's focus on the parameters declared:
* Even though this function is a dependency ("dependable") itself, it also declares another dependency (it "depends" on something else).
* It depends on the `query_extractor`, and assigns the value returned by it to the parameter `q`.
* It also declares an optional `last_query` cookie, as a `str`.
* Let's imagine that if the user didn't provide any query `q`, we just use the last query used, that we had saved to a cookie before.
### Use the dependency
Then we can use the dependency with:
```Python hl_lines="19"
{!./src/dependencies/tutorial005.py!}
```
!!! info
Notice that we are only declaring one dependency in the path operation function, the `query_or_cookie_extractor`.
But **FastAPI** will know that it has to solve `query_extractor` first, to pass the results of that to `query_or_cookie_extractor` while calling it.
## Recap
Apart from all the fancy words used here, the **Dependency Injection** system is quite simple.
Just functions that look the same as the path operation functions.
But still, it is very powerful, and allows you to declare arbitrarily deeply nested dependency "graphs" (trees).
!!! tip
All this might not seem as useful with these simple examples.
But you will see how useful it is in the chapters about **security**.
And you will also see the amounts of code it will save you.

10
mkdocs.yml

@ -41,15 +41,17 @@ nav:
- 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'
- SQL (Relational) Databases: 'tutorial/sql-databases.md'
- NoSQL (Distributed / Big Data) Databases: 'tutorial/nosql-databases.md'
- First Steps - Functions: 'tutorial/dependencies/first-steps-functions.md'
- Classes as Dependencies: 'tutorial/dependencies/classes-as-dependencies.md'
- Sub-dependencies: 'tutorial/dependencies/sub-dependencies.md'
- Advanced Dependencies: 'tutorial/dependencies/advanced-dependencies.md'
- Security:
- Security Intro: 'tutorial/security/intro.md'
- First Steps: 'tutorial/security/first-steps.md'
- Simple OAuth2 with Password and Bearer: 'tutorial/security/simple-oauth2.md'
- OAuth2 with Password (and hashing), Bearer with JWT tokens: 'tutorial/security/oauth2-jwt.md'
- SQL (Relational) Databases: 'tutorial/sql-databases.md'
- NoSQL (Distributed / Big Data) Databases: 'tutorial/nosql-databases.md'
- Bigger Applications - Multiple Files: 'tutorial/bigger-applications.md'
- Application Configuration: 'tutorial/application-configuration.md'
- Extra Starlette options: 'tutorial/extra-starlette.md'

Loading…
Cancel
Save