]> git.ipfire.org Git - thirdparty/fastapi/fastapi.git/commitdiff
:memo: Update and add docs for dependencies
authorSebastián Ramírez <tiangolo@gmail.com>
Sun, 23 Dec 2018 17:21:37 +0000 (21:21 +0400)
committerSebastián Ramírez <tiangolo@gmail.com>
Sun, 23 Dec 2018 17:21:37 +0000 (21:21 +0400)
docs/src/dependencies/tutorial002.py
docs/src/dependencies/tutorial003.py
docs/src/dependencies/tutorial004.py
docs/src/dependencies/tutorial005.py [new file with mode: 0644]
docs/src/dependencies/tutorial006.py [new file with mode: 0644]
docs/tutorial/dependencies/advanced-dependencies.md [new file with mode: 0644]
docs/tutorial/dependencies/classes-as-dependencies.md [new file with mode: 0644]
docs/tutorial/dependencies/first-steps-functions.md [moved from docs/tutorial/dependencies/first-steps.md with 98% similarity]
docs/tutorial/dependencies/second-steps.md [deleted file]
docs/tutorial/dependencies/sub-dependencies.md [new file with mode: 0644]
mkdocs.yml

index 82a51634eb1e38a719fd16688921d1f374049497..3afa89706abd04f99614ab485fb4c16bb62b96e8 100644 (file)
@@ -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})
index e015f958528a93423739c73c0cb31dd1788bc393..b5816285caae2a79a8e4ceae6b3ac1c821c99385 100644 (file)
@@ -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
index 3697b170ae23fe9ae242eb9f54d2dacf7fca79e8..8b1e00e6b60b23f476aea24140a6ab4ee6e7ca94 100644 (file)
@@ -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
diff --git a/docs/src/dependencies/tutorial005.py b/docs/src/dependencies/tutorial005.py
new file mode 100644 (file)
index 0000000..36b3123
--- /dev/null
@@ -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}
diff --git a/docs/src/dependencies/tutorial006.py b/docs/src/dependencies/tutorial006.py
new file mode 100644 (file)
index 0000000..5d22f68
--- /dev/null
@@ -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}
diff --git a/docs/tutorial/dependencies/advanced-dependencies.md b/docs/tutorial/dependencies/advanced-dependencies.md
new file mode 100644 (file)
index 0000000..d63170d
--- /dev/null
@@ -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.
diff --git a/docs/tutorial/dependencies/classes-as-dependencies.md b/docs/tutorial/dependencies/classes-as-dependencies.md
new file mode 100644 (file)
index 0000000..9f3e51c
--- /dev/null
@@ -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.
similarity index 98%
rename from docs/tutorial/dependencies/first-steps.md
rename to docs/tutorial/dependencies/first-steps-functions.md
index b9370c0bf70df9f0c82aa5bf4ca9f1b86190a722..f736a9163637413c40740969397e907d04fd4dd9 100644 (file)
@@ -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.
 
diff --git a/docs/tutorial/dependencies/second-steps.md b/docs/tutorial/dependencies/second-steps.md
deleted file mode 100644 (file)
index aa71972..0000000
+++ /dev/null
@@ -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.
\ No newline at end of file
diff --git a/docs/tutorial/dependencies/sub-dependencies.md b/docs/tutorial/dependencies/sub-dependencies.md
new file mode 100644 (file)
index 0000000..7f96674
--- /dev/null
@@ -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.
index 3baf86c3a3b57b04abe29877e3eeeded7bd046af..6dc5e9926659ed12eed3c3192e08caca0f7f5459 100644 (file)
@@ -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'