Compare commits

...

5 Commits

Author SHA1 Message Date
Sebastián Ramírez
e5b341c7dd 🔖 Bump version after fix for constrained bytes 2019-01-05 17:38:59 +04:00
Sebastián Ramírez
577c5a84db 🐛 Fix constrained bytes, from defaults in Pydantic
#2
2019-01-05 17:30:27 +04:00
Sebastián Ramírez
a5cfee434d 📝 Update docs for dependencies 2019-01-05 17:19:41 +04:00
Sebastián Ramírez
9a8349bf96 📝 Improve explanation of dependencies 2019-01-01 19:27:02 +04:00
Sebastián Ramírez
a59408f68c ✏️ Fix typo in dependencies docs 2018-12-30 22:18:45 +04:00
13 changed files with 270 additions and 168 deletions

View File

@@ -10,3 +10,8 @@ async def common_parameters(q: str = None, skip: int = 0, limit: int = 100):
@app.get("/items/")
async def read_items(commons: dict = Depends(common_parameters)):
return commons
@app.get("/users/")
async def read_users(commons: dict = Depends(common_parameters)):
return commons

View File

@@ -25,6 +25,8 @@ To do that, we declare a method `__call__`:
{!./src/dependencies/tutorial006.py!}
```
In this case, this `__call__` is what **FastAPI** will use to check for additional parameters and sub-dependencies, and this is what will be called to pass a value to the parameter in your *path operation function* later.
## Parameterize the instance
And now, we can use `__init__` to declare the parameters of the instance that we can use to "parameterize" the dependency:

View File

@@ -10,7 +10,7 @@ In the previous example, we where returning a `dict` from our dependency ("depen
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.
And we know that editors can't provide a lot of support (like completion) for `dict`s, because they can't know their keys and value types.
We can do better...
@@ -24,7 +24,7 @@ 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:
So, if you have an object `something` (that might _not_ be a function) and you can "call" it (execute it) like:
```Python
something()
@@ -42,6 +42,21 @@ then it is a "callable".
You might notice that to create an instance of a Python class, you use that same syntax.
For example:
```Python
class Cat:
def __init__(self, name: str):
self.name = name
fluffy = Cat(name="Mr Fluffy")
```
In this case, `fluffy` is an instance of the class `Cat`.
And to create `fluffy`, you are "calling" `Cat`.
So, a Python class is also a **callable**.
Then, in **FastAPI**, you could use a Python class as a dependency.
@@ -50,7 +65,7 @@ What FastAPI actually checks is that it is a "callable" (function, class or anyt
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 parameters.
That also applies to callables with no parameters at all. The same as it would be for path operation functions with no parameters.
Then, we can change the dependency "dependable" `common_parameters` from above to the class `CommonQueryParameters`:

View File

@@ -1,91 +0,0 @@
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"
{!./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" (without 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"
{!./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"
{!./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 know, check the _"In a hurry?"_ section about <a href="https://fastapi.tiangolo.com/async/#in-a-hurry" target="_blank">`async` and `await` in the docs</a>.
## 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.

View File

@@ -0,0 +1,167 @@
**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: "dependencies".
And then, that system (in this case **FastAPI**) will take care of doing whatever is needed to provide your code with those needed dependencies ("inject" the dependencies).
This is very useful when you need to:
* Have shared logic (the same code logic again and again).
* Share database connections.
* Enforce security, authentication, role requirements, etc.
* etc.
All these, while minimizing code repetition.
## First Steps
Let's see a very simple example. 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.
### 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"
{!./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" (without 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"
{!./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 16"
{!./src/dependencies/tutorial001.py!}
```
Although you use `Depends` in the parameters of your function the same way you use `Body`, `Query`, etc, `Depends` works a bit differently.
You only give `Depends` a single parameter.
This parameter must be something like a function.
And that function takes parameters in the same way that path operation functions do.
!!! tip
You'll see what other "things", apart from functions, can be used as dependencies in the next chapter.
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** to "register" it 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, etc.
It doesn't matter. **FastAPI** will know what to do.
!!! note
If you don't know, check the _"In a hurry?"_ section about <a href="https://fastapi.tiangolo.com/async/#in-a-hurry" target="_blank">`async` and `await` in the docs</a>.
## Integrated with 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 from these dependencies too:
<img src="/img/tutorial/dependencies/image01.png">
## Simple usage
If you look at it, *path operation functions* are declared to be used whenever a *path* and *operation* matches, and then **FastAPI** takes 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. They 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 of "dependency injection" are:
* resources
* providers
* services
* injectables
* components
## **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_.
You will see examples of this in the next chapters, about relational and NoSQL databases, security, etc.
## **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 (injecting) the results at each step.
## Integrated with **OpenAPI**
All these dependencies, while declaring their requirements, add 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.

View File

@@ -1,58 +0,0 @@
**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.

View File

@@ -1,6 +1,6 @@
"""FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
__version__ = "0.1.16"
__version__ = "0.1.17"
from .applications import FastAPI
from .routing import APIRouter

View File

@@ -9,8 +9,8 @@ from uuid import UUID
from fastapi import params
from fastapi.dependencies.models import Dependant, SecurityRequirement
from fastapi.security.base import SecurityBase
from fastapi.utils import get_path_param_names
from pydantic import BaseConfig, Schema, create_model
from fastapi.utils import UnconstrainedConfig, get_path_param_names
from pydantic import Schema, create_model
from pydantic.error_wrappers import ErrorWrapper
from pydantic.errors import MissingError
from pydantic.fields import Field, Required, Shape
@@ -163,7 +163,7 @@ def add_param_to_fields(
default=None if required else default_value,
alias=alias,
required=required,
model_config=BaseConfig(),
model_config=UnconstrainedConfig,
class_validators=[],
schema=schema,
)
@@ -197,7 +197,7 @@ def add_param_to_body_fields(*, param: inspect.Parameter, dependant: Dependant)
default=None if required else default_value,
alias=schema.alias or param.name,
required=required,
model_config=BaseConfig,
model_config=UnconstrainedConfig,
class_validators=[],
schema=schema,
)
@@ -281,7 +281,7 @@ def request_params_to_args(
ErrorWrapper(
MissingError(),
loc=(schema.in_.value, field.alias),
config=BaseConfig,
config=UnconstrainedConfig,
)
)
else:
@@ -315,7 +315,9 @@ async def request_body_to_args(
if field.required:
errors.append(
ErrorWrapper(
MissingError(), loc=("body", field.alias), config=BaseConfig
MissingError(),
loc=("body", field.alias),
config=UnconstrainedConfig,
)
)
else:
@@ -356,7 +358,7 @@ def get_body_field(*, dependant: Dependant, name: str) -> Field:
type_=BodyModel,
default=None,
required=required,
model_config=BaseConfig,
model_config=UnconstrainedConfig,
class_validators=[],
alias="body",
schema=BodySchema(None),

View File

@@ -7,7 +7,8 @@ from fastapi import params
from fastapi.dependencies.models import Dependant
from fastapi.dependencies.utils import get_body_field, get_dependant, solve_dependencies
from fastapi.encoders import jsonable_encoder
from pydantic import BaseConfig, BaseModel, Schema
from fastapi.utils import UnconstrainedConfig
from pydantic import BaseModel, Schema
from pydantic.error_wrappers import ErrorWrapper, ValidationError
from pydantic.fields import Field
from pydantic.utils import lenient_issubclass
@@ -130,7 +131,7 @@ class APIRoute(routing.Route):
class_validators=[],
default=None,
required=False,
model_config=BaseConfig(),
model_config=UnconstrainedConfig,
schema=Schema(None),
)
else:

View File

@@ -3,12 +3,17 @@ from typing import Any, Dict, List, Sequence, Set, Type
from fastapi import routing
from fastapi.openapi.constants import REF_PREFIX
from pydantic import BaseModel
from pydantic import BaseConfig, BaseModel
from pydantic.fields import Field
from pydantic.schema import get_flat_models_from_fields, model_process_schema
from starlette.routing import BaseRoute
class UnconstrainedConfig(BaseConfig):
min_anystr_length = None
max_anystr_length = None
def get_flat_models_from_routes(
routes: Sequence[Type[BaseRoute]]
) -> Set[Type[BaseModel]]:

View File

@@ -23,8 +23,8 @@ nav:
- Path Parameters: 'tutorial/path-params.md'
- Query Parameters: 'tutorial/query-params.md'
- Request Body: 'tutorial/body.md'
- Query Parameters - String Validations: 'tutorial/query-params-str-validations.md'
- Path Parameters - Numeric Validations: 'tutorial/path-params-numeric-validations.md'
- Query Parameters and String Validations: 'tutorial/query-params-str-validations.md'
- Path Parameters and Numeric Validations: 'tutorial/path-params-numeric-validations.md'
- Body - Multiple Parameters: 'tutorial/body-multiple-params.md'
- Body - Schema: 'tutorial/body-schema.md'
- Body - Nested Models: 'tutorial/body-nested-models.md'
@@ -40,8 +40,7 @@ nav:
- Path Operation Advanced Configuration: 'tutorial/path-operation-advanced-configuration.md'
- Custom Response: 'tutorial/custom-response.md'
- Dependencies:
- Dependencies Intro: 'tutorial/dependencies/intro.md'
- First Steps - Functions: 'tutorial/dependencies/first-steps-functions.md'
- First Steps: 'tutorial/dependencies/first-steps.md'
- Classes as Dependencies: 'tutorial/dependencies/classes-as-dependencies.md'
- Sub-dependencies: 'tutorial/dependencies/sub-dependencies.md'
- Advanced Dependencies: 'tutorial/dependencies/advanced-dependencies.md'

View File

@@ -50,7 +50,49 @@ openapi_schema = {
},
],
}
}
},
"/users/": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Read Users Get",
"operationId": "read_users_users__get",
"parameters": [
{
"required": False,
"schema": {"title": "Q", "type": "string"},
"name": "q",
"in": "query",
},
{
"required": False,
"schema": {"title": "Skip", "type": "integer", "default": 0},
"name": "skip",
"in": "query",
},
{
"required": False,
"schema": {"title": "Limit", "type": "integer", "default": 100},
"name": "limit",
"in": "query",
},
],
}
},
},
"components": {
"schemas": {
@@ -97,6 +139,7 @@ def test_openapi_schema():
("/items?q=foo", 200, {"q": "foo", "skip": 0, "limit": 100}),
("/items?q=foo&skip=5", 200, {"q": "foo", "skip": 5, "limit": 100}),
("/items?q=foo&skip=5&limit=30", 200, {"q": "foo", "skip": 5, "limit": 30}),
("/users", 200, {"q": None, "skip": 0, "limit": 100}),
("/openapi.json", 200, openapi_schema),
],
)

View File

@@ -119,3 +119,15 @@ def test_post_file(tmpdir):
response = client.post("/files/", files={"file": open(path, "rb")})
assert response.status_code == 200
assert response.json() == {"file_size": 14}
def test_post_large_file(tmpdir):
default_pydantic_max_size = 2 ** 16
path = os.path.join(tmpdir, "test.txt")
with open(path, "wb") as file:
file.write(b"x" * (default_pydantic_max_size + 1))
client = TestClient(app)
response = client.post("/files/", files={"file": open(path, "rb")})
assert response.status_code == 200
assert response.json() == {"file_size": default_pydantic_max_size + 1}