mirror of
https://github.com/fastapi/fastapi.git
synced 2025-12-24 06:39:31 -05:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c26f1760d4 | ||
|
|
e5fa4b0af6 | ||
|
|
a33c299fd7 | ||
|
|
6939621730 | ||
|
|
120ab08360 | ||
|
|
3f5521fdfb | ||
|
|
7244e4b612 | ||
|
|
d329745064 | ||
|
|
5f7fe926ab | ||
|
|
c8eea09664 | ||
|
|
5700d65188 | ||
|
|
46178a5347 | ||
|
|
bff5dbbf5d | ||
|
|
09cd7c47a1 | ||
|
|
e2fadcbc90 | ||
|
|
b3bb29afa8 | ||
|
|
c7db2ff858 |
@@ -240,7 +240,7 @@ It was one of the first extremely fast Python frameworks based on `asyncio`. It
|
||||
|
||||
Falcon is another high performance Python framework, it is designed to be minimal, and work as the foundation of other frameworks like Hug.
|
||||
|
||||
It uses the previous standard for Python web frameworks (WSGI) which is synchronous, so it can't handle Websockets and other use cases. Nevertheless, it also has a very good performance.
|
||||
It uses the previous standard for Python web frameworks (WSGI) which is synchronous, so it can't handle WebSockets and other use cases. Nevertheless, it also has a very good performance.
|
||||
|
||||
It is designed to have functions that receive two parameters, one "request" and one "response". Then you "read" parts from the request, and "write" parts to the response. Because of this design, it is not possible to declare request parameters and bodies with standard Python type hints as function parameters.
|
||||
|
||||
@@ -249,6 +249,10 @@ So, data validation, serialization, and documentation, have to be done in code,
|
||||
!!! check "Inspired **FastAPI** to"
|
||||
Find ways to get great performance.
|
||||
|
||||
Along with Hug (as Hug is based on Falcon) inspired **FastAPI** to declare a `response` parameter in functions.
|
||||
|
||||
Although in FastAPI it's optional, and is used mainly to set headers, cookies, and alternative status codes.
|
||||
|
||||
### <a href="https://moltenframework.com/" target="_blank">Molten</a>
|
||||
|
||||
I discovered Molten in the first stages of building **FastAPI**. And it has quite similar ideas:
|
||||
@@ -292,6 +296,7 @@ As it is based on the previous standard for synchronous Python web frameworks (W
|
||||
|
||||
Hug helped inspiring **FastAPI** to use Python type hints to declare parameters, and to generate a schema defining the API automatically.
|
||||
|
||||
Hug inspired **FastAPI** to declare a `response` parameter in functions to set headers and cookies.
|
||||
|
||||
### <a href="https://github.com/encode/apistar" target="_blank">APIStar</a> (<= 0.5)
|
||||
|
||||
|
||||
@@ -1,5 +1,43 @@
|
||||
## Latest changes
|
||||
|
||||
## 0.29.1
|
||||
|
||||
* Fix handling an empty-body request with a required body param. PR [#311](https://github.com/tiangolo/fastapi/pull/311).
|
||||
|
||||
* Fix broken link in docs: [Return a Response directly](https://fastapi.tiangolo.com/tutorial/response-directly/). PR [#306](https://github.com/tiangolo/fastapi/pull/306) by [@dmontagu](https://github.com/dmontagu).
|
||||
|
||||
* Fix docs discrepancy in docs for [Response Model](https://fastapi.tiangolo.com/tutorial/response-model/). PR [#288](https://github.com/tiangolo/fastapi/pull/288) by [@awiddersheim](https://github.com/awiddersheim).
|
||||
|
||||
## 0.29.0
|
||||
|
||||
* Add support for declaring a `Response` parameter:
|
||||
* This allows declaring:
|
||||
* [Response Cookies](https://fastapi.tiangolo.com/tutorial/response-cookies/).
|
||||
* [Response Headers](https://fastapi.tiangolo.com/tutorial/response-headers/).
|
||||
* An HTTP Status Code different than the default: [Response - Change Status Code](https://fastapi.tiangolo.com/tutorial/response-change-status-code/).
|
||||
* All of this while still being able to return arbitrary objects (`dict`, DB model, etc).
|
||||
* Update attribution to Hug, for inspiring the `response` parameter pattern.
|
||||
* PR [#294](https://github.com/tiangolo/fastapi/pull/294).
|
||||
|
||||
## 0.28.0
|
||||
|
||||
* Implement dependency cache per request.
|
||||
* This avoids calling each dependency multiple times for the same request.
|
||||
* This is useful while calling external services, performing costly computation, etc.
|
||||
* This also means that if a dependency was declared as a *path operation decorator* dependency, possibly at the router level (with `.include_router()`) and then it is declared again in a specific *path operation*, the dependency will be called only once.
|
||||
* The cache can be disabled per dependency declaration, using `use_cache=False` as in `Depends(your_dependency, use_cache=False)`.
|
||||
* Updated docs at: [Using the same dependency multiple times](https://fastapi.tiangolo.com/tutorial/dependencies/sub-dependencies/#using-the-same-dependency-multiple-times).
|
||||
* PR [#292](https://github.com/tiangolo/fastapi/pull/292).
|
||||
|
||||
* Implement dependency overrides for testing.
|
||||
* This allows using overrides/mocks of dependencies during tests.
|
||||
* New docs: [Testing Dependencies with Overrides](https://fastapi.tiangolo.com/tutorial/testing-dependencies/).
|
||||
* PR [#291](https://github.com/tiangolo/fastapi/pull/291).
|
||||
|
||||
## 0.27.2
|
||||
|
||||
* Fix path and query parameters receiving `dict` as a valid type. It should be mapped to a body payload. PR [#287](https://github.com/tiangolo/fastapi/pull/287). Updated docs at: [Query parameter list / multiple values with defaults: Using `list`](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#using-list).
|
||||
|
||||
## 0.27.1
|
||||
|
||||
* Fix `auto_error=False` handling in `HTTPBearer` security scheme. Do not `raise` when there's an incorrect `Authorization` header if `auto_error=False`. PR [#282](https://github.com/tiangolo/fastapi/pull/282).
|
||||
|
||||
55
docs/src/dependency_testing/tutorial001.py
Normal file
55
docs/src/dependency_testing/tutorial001.py
Normal file
@@ -0,0 +1,55 @@
|
||||
from fastapi import Depends, FastAPI
|
||||
from starlette.testclient import TestClient
|
||||
|
||||
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 {"message": "Hello Items!", "params": commons}
|
||||
|
||||
|
||||
@app.get("/users/")
|
||||
async def read_users(commons: dict = Depends(common_parameters)):
|
||||
return {"message": "Hello Users!", "params": commons}
|
||||
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
async def override_dependency(q: str = None):
|
||||
return {"q": q, "skip": 5, "limit": 10}
|
||||
|
||||
|
||||
app.dependency_overrides[common_parameters] = override_dependency
|
||||
|
||||
|
||||
def test_override_in_items():
|
||||
response = client.get("/items/")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"message": "Hello Items!",
|
||||
"params": {"q": None, "skip": 5, "limit": 10},
|
||||
}
|
||||
|
||||
|
||||
def test_override_in_items_with_q():
|
||||
response = client.get("/items/?q=foo")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"message": "Hello Items!",
|
||||
"params": {"q": "foo", "skip": 5, "limit": 10},
|
||||
}
|
||||
|
||||
|
||||
def test_override_in_items_with_params():
|
||||
response = client.get("/items/?q=foo&skip=100&limit=200")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"message": "Hello Items!",
|
||||
"params": {"q": "foo", "skip": 5, "limit": 10},
|
||||
}
|
||||
9
docs/src/query_params_str_validations/tutorial013.py
Normal file
9
docs/src/query_params_str_validations/tutorial013.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from fastapi import FastAPI, Query
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.get("/items/")
|
||||
async def read_items(q: list = Query(None)):
|
||||
query_items = {"q": q}
|
||||
return query_items
|
||||
15
docs/src/response_change_status_code/tutorial001.py
Normal file
15
docs/src/response_change_status_code/tutorial001.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from fastapi import FastAPI
|
||||
from starlette.responses import Response
|
||||
from starlette.status import HTTP_201_CREATED
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
tasks = {"foo": "Listen to the Bar Fighters"}
|
||||
|
||||
|
||||
@app.put("/get-or-create-task/{task_id}", status_code=200)
|
||||
def get_or_create_task(task_id: str, response: Response):
|
||||
if task_id not in tasks:
|
||||
tasks[task_id] = "This didn't exist before"
|
||||
response.status_code = HTTP_201_CREATED
|
||||
return tasks[task_id]
|
||||
10
docs/src/response_cookies/tutorial002.py
Normal file
10
docs/src/response_cookies/tutorial002.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from fastapi import FastAPI
|
||||
from starlette.responses import Response
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.post("/cookie-and-object/")
|
||||
def create_cookie(response: Response):
|
||||
response.set_cookie(key="fakesession", value="fake-cookie-session-value")
|
||||
return {"message": "Come to the dark side, we have cookies"}
|
||||
10
docs/src/response_headers/tutorial002.py
Normal file
10
docs/src/response_headers/tutorial002.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from fastapi import FastAPI
|
||||
from starlette.responses import Response
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.get("/headers-and-object/")
|
||||
def get_headers(response: Response):
|
||||
response.headers["X-Cat-Dog"] = "alone in the world"
|
||||
return {"message": "Hello World"}
|
||||
@@ -17,14 +17,12 @@ This is very useful when you need to:
|
||||
|
||||
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.
|
||||
@@ -151,7 +149,6 @@ The simplicity of the dependency injection system makes **FastAPI** compatible w
|
||||
* 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.
|
||||
|
||||
@@ -11,6 +11,7 @@ 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.
|
||||
@@ -43,6 +44,18 @@ Then we can use the dependency with:
|
||||
|
||||
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.
|
||||
|
||||
## Using the same dependency multiple times
|
||||
|
||||
If one of your dependencies is declared multiple times for the same *path operation*, for example, multiple dependencies have a common sub-dependency, **FastAPI** will know to call that sub-dependency only once per request.
|
||||
|
||||
And it will save the returned value in a <abbr title="A utility/system to store computed/generated values, to re-use them instead of computing them again.">"cache"</abbr> and pass it to all the "dependants" that need it in that specific request, instead of calling the dependency multiple times for the same request.
|
||||
|
||||
In an advanced scenario where you know you need the dependency to be called at every step (possibly multiple times) in the same request instead of using the "cached" value, you can set the parameter `use_cache=False` when using `Depends`:
|
||||
|
||||
```Python hl_lines="1"
|
||||
async def needy_dependency(fresh_value: str = Depends(get_value, use_cache=False)):
|
||||
return {"fresh_value": fresh_value}
|
||||
```
|
||||
|
||||
## Recap
|
||||
|
||||
@@ -54,7 +67,7 @@ But still, it is very powerful, and allows you to declare arbitrarily deeply nes
|
||||
|
||||
!!! 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.
|
||||
|
||||
@@ -183,6 +183,19 @@ the default of `q` will be: `["foo", "bar"]` and your response will be:
|
||||
}
|
||||
```
|
||||
|
||||
#### Using `list`
|
||||
|
||||
You can also use `list` directly instead of `List[str]`:
|
||||
|
||||
```Python hl_lines="7"
|
||||
{!./src/query_params_str_validations/tutorial013.py!}
|
||||
```
|
||||
|
||||
!!! note
|
||||
Have in mind that in this case, FastAPI won't check the contents of the list.
|
||||
|
||||
For example, `List[int]` would check (and document) that the contents of the list are integers. But `list` alone wouldn't.
|
||||
|
||||
## Declare more metadata
|
||||
|
||||
You can add more information about the parameter.
|
||||
|
||||
31
docs/tutorial/response-change-status-code.md
Normal file
31
docs/tutorial/response-change-status-code.md
Normal file
@@ -0,0 +1,31 @@
|
||||
You probably read before that you can set a <a href="https://fastapi.tiangolo.com/tutorial/response-status-code/" target="_blank">default Response Status Code</a>.
|
||||
|
||||
But in some cases you need to return a different status code than the default.
|
||||
|
||||
## Use case
|
||||
|
||||
For example, imagine that you want to return an HTTP status code of "OK" `200` by default.
|
||||
|
||||
But if the data didn't exist, you want to create it, and return an HTTP status code of "CREATED" `201`.
|
||||
|
||||
But you still want to be able to filter and convert the data you return with a `response_model`.
|
||||
|
||||
For those cases, you can use a `Response` parameter.
|
||||
|
||||
## Use a `Response` parameter
|
||||
|
||||
You can declare a parameter of type `Response` in your *path operation function* (as you can do for cookies and headers).
|
||||
|
||||
And then you can set the `status_code` in that *temporal* response object.
|
||||
|
||||
```Python hl_lines="2 11 14"
|
||||
{!./src/response_change_status_code/tutorial001.py!}
|
||||
```
|
||||
|
||||
And then you can return any object you need, as you normally would (a `dict`, a database model, etc).
|
||||
|
||||
And if you declared a `response_model`, it will still be used to filter and convert the object you returned.
|
||||
|
||||
**FastAPI** will use that *temporal* response to extract the status code (also cookies and headers), and will put them in the final response that contains the value you returned, filtered by any `response_model`.
|
||||
|
||||
You can also declare the `Response` parameter in dependencies, and set the status code in them. But have in mind that the last one to be set will win.
|
||||
@@ -1,4 +1,24 @@
|
||||
You can create (set) Cookies in your response.
|
||||
## Use a `Response` parameter
|
||||
|
||||
You can declare a parameter of type `Response` in your *path operation function*, the same way you can declare a `Request` parameter.
|
||||
|
||||
And then you can set headers in that *temporal* response object.
|
||||
|
||||
```Python hl_lines="2 8 9"
|
||||
{!./src/response_cookies/tutorial002.py!}
|
||||
```
|
||||
|
||||
And then you can return any object you need, as you normally would (a `dict`, a database model, etc).
|
||||
|
||||
And if you declared a `response_model`, it will still be used to filter and convert the object you returned.
|
||||
|
||||
**FastAPI** will use that *temporal* response to extract the cookies (also headers and status code), and will put them in the final response that contains the value you returned, filtered by any `response_model`.
|
||||
|
||||
You can also declare the `Response` parameter in dependencies, and set cookies (and headers) in them.
|
||||
|
||||
## Return a `Response` directly
|
||||
|
||||
You can also create cookies when returning a `Response` directly in your code.
|
||||
|
||||
To do that, you can create a response as described in <a href="https://fastapi.tiangolo.com/tutorial/response-directly/" target="_blank">Return a Response directly</a>.
|
||||
|
||||
@@ -8,6 +28,13 @@ Then set Cookies in it, and then return it:
|
||||
{!./src/response_cookies/tutorial001.py!}
|
||||
```
|
||||
|
||||
## More info
|
||||
!!! tip
|
||||
Have in mind that if you return a response directly instead of using the `Response` parameter, FastAPI will return it directly.
|
||||
|
||||
So, you will have to make sure your data is of the correct type. E.g. it is compatible with JSON, if you are returning a `JSONResponse`.
|
||||
|
||||
And also that you are not sending any data that should have been filtered by a `response_model`.
|
||||
|
||||
### More info
|
||||
|
||||
To see all the available parameters and options, check the <a href="https://www.starlette.io/responses/#set-cookie" target="_blank">documentation in Starlette</a>.
|
||||
|
||||
@@ -56,7 +56,7 @@ You could put your XML content in a string, put it in a Starlette Response, and
|
||||
|
||||
When you return a `Response` directly its data is not validated, converted (serialized), nor documented automatically.
|
||||
|
||||
But you can still <a href="tutorial/additional-responses/" target="_blank">document it</a>.
|
||||
But you can still <a href="https://fastapi.tiangolo.com/tutorial/additional-responses/" target="_blank">document it</a>.
|
||||
|
||||
In the next sections you will see how to use/declare these custom `Response`s while still having automatic data conversion, documentation, etc.
|
||||
|
||||
|
||||
@@ -1,4 +1,24 @@
|
||||
You can add headers to your response.
|
||||
## Use a `Response` parameter
|
||||
|
||||
You can declare a parameter of type `Response` in your *path operation function* (as you can do for cookies), the same way you can declare a `Request` parameter.
|
||||
|
||||
And then you can set headers in that *temporal* response object.
|
||||
|
||||
```Python hl_lines="2 8 9"
|
||||
{!./src/response_headers/tutorial002.py!}
|
||||
```
|
||||
|
||||
And then you can return any object you need, as you normally would (a `dict`, a database model, etc).
|
||||
|
||||
And if you declared a `response_model`, it will still be used to filter and convert the object you returned.
|
||||
|
||||
**FastAPI** will use that *temporal* response to extract the headers (also cookies and status code), and will put them in the final response that contains the value you returned, filtered by any `response_model`.
|
||||
|
||||
You can also declare the `Response` parameter in dependencies, and set headers (and cookies) in them.
|
||||
|
||||
## Return a `Response` directly
|
||||
|
||||
You can also add headers when you return a `Response` directly.
|
||||
|
||||
Create a response as described in <a href="https://fastapi.tiangolo.com/tutorial/response-directly/" target="_blank">Return a Response directly</a> and pass the headers as an additional parameter:
|
||||
|
||||
@@ -6,7 +26,8 @@ Create a response as described in <a href="https://fastapi.tiangolo.com/tutorial
|
||||
{!./src/response_headers/tutorial001.py!}
|
||||
```
|
||||
|
||||
!!! tip
|
||||
Have in mind that custom proprietary headers can be added <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers" target="_blank">using the 'X-' prefix</a>.
|
||||
## Custom Headers
|
||||
|
||||
But if you have custom headers that you want a client in a browser to be able to see, you need to add them to your <a href="https://fastapi.tiangolo.com/tutorial/cors/" target="_blank">CORS configurations</a>, using the parameter `expose_headers` documented in <a href="https://www.starlette.io/middleware/#corsmiddleware" target="_blank">Starlette's CORS docs</a>.
|
||||
Have in mind that custom proprietary headers can be added <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers" target="_blank">using the 'X-' prefix</a>.
|
||||
|
||||
But if you have custom headers that you want a client in a browser to be able to see, you need to add them to your <a href="https://fastapi.tiangolo.com/tutorial/cors/" target="_blank">CORS configurations</a>, using the parameter `expose_headers` documented in <a href="https://www.starlette.io/middleware/#corsmiddleware" target="_blank">Starlette's CORS docs</a>.
|
||||
|
||||
@@ -93,7 +93,7 @@ Your response model could have default values, like:
|
||||
```
|
||||
|
||||
* `description: str = None` has a default of `None`.
|
||||
* `tax: float = None` has a default of `None`.
|
||||
* `tax: float = 10.5` has a default of `10.5`.
|
||||
* `tags: List[str] = []` has a default of an empty list: `[]`.
|
||||
|
||||
but you might want to omit them from the result if they were not actually stored.
|
||||
|
||||
@@ -47,7 +47,6 @@ In short:
|
||||
!!! tip
|
||||
To know more about each status code and which code is for what, check the <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status" target="_blank"><abbr title="Mozilla Developer Network">MDN</abbr> documentation about HTTP status codes</a>.
|
||||
|
||||
|
||||
## Shortcut to remember the names
|
||||
|
||||
Let's see the previous example again:
|
||||
@@ -69,3 +68,7 @@ You can use the convenience variables from `starlette.status`.
|
||||
They are just a convenience, they hold the same number, but that way you can use the editor's autocomplete to find them:
|
||||
|
||||
<img src="/img/tutorial/response-status-code/image02.png">
|
||||
|
||||
## Changing the default
|
||||
|
||||
Later, in a more advanced part of the tutorial/user guide, you will see how to <a href="https://fastapi.tiangolo.com/tutorial/response-change-status-code/" target="_blank">return a different status code than the default</a> you are declaring here.
|
||||
|
||||
59
docs/tutorial/testing-dependencies.md
Normal file
59
docs/tutorial/testing-dependencies.md
Normal file
@@ -0,0 +1,59 @@
|
||||
## Overriding dependencies during testing
|
||||
|
||||
There are some scenarios where you might want to override a dependency during testing.
|
||||
|
||||
You don't want the original dependency to run (nor any of the sub-dependencies it might have).
|
||||
|
||||
Instead, you want to provide a different dependency that will be used only during tests (possibly only some specific tests), and will provide a value that can be used where the value of the original dependency was used.
|
||||
|
||||
### Use cases: external service
|
||||
|
||||
An example could be that you have an external authentication provider that you need to call.
|
||||
|
||||
You send it a token and it returns an authenticated user.
|
||||
|
||||
This provider might be charging you per request, and calling it might take some extra time than if you had a fixed mock user for tests.
|
||||
|
||||
You probably want to test the external provider once, but not necessarily call it for every test that runs.
|
||||
|
||||
In this case, you can override the dependency that calls that provider, and use a custom dependency that returns a mock user, only for your tests.
|
||||
|
||||
### Use case: testing database
|
||||
|
||||
Other example could be that you are using a specific database only for testing.
|
||||
|
||||
Your normal dependency would return a database session.
|
||||
|
||||
But then, after each test, you could want to rollback all the operations or remove data.
|
||||
|
||||
Or you could want to alter the data before the tests run, etc.
|
||||
|
||||
In this case, you could use a dependency override to return your *custom* database session instead of the one that would be used normally.
|
||||
|
||||
### Use the `app.dependency_overrides` attribute
|
||||
|
||||
For these cases, your **FastAPI** application has an attribute `app.dependency_overrides`, it is a simple `dict`.
|
||||
|
||||
To override a dependency for testing, you put as a key the original dependency (a function), and as the value, your dependency override (another function).
|
||||
|
||||
And then **FastAPI** will call that override instead of the original dependency.
|
||||
|
||||
```Python hl_lines="24 25 28"
|
||||
{!./src/dependency_testing/tutorial001.py!}
|
||||
```
|
||||
|
||||
!!! tip
|
||||
You can set a dependency override for a dependency used anywhere in your **FastAPI** application.
|
||||
|
||||
The original dependency could be used in a *path operation function*, a *path operation decorator* (when you don't use the return value), a `.include_router()` call, etc.
|
||||
|
||||
FastAPI will still be able to override it.
|
||||
|
||||
Then you can reset your overrides (remove them) by setting `app.dependency_overrides` to be an empty `dict`:
|
||||
|
||||
```Python
|
||||
app.dependency_overrides = {}
|
||||
```
|
||||
|
||||
!!! tip
|
||||
If you want to override a dependency only during some tests, you can set the override at the beginning of the test (inside the test function) and reset it at the end (at the end of the test function).
|
||||
@@ -1,6 +1,6 @@
|
||||
"""FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
|
||||
|
||||
__version__ = "0.27.1"
|
||||
__version__ = "0.29.1"
|
||||
|
||||
from starlette.background import BackgroundTasks
|
||||
|
||||
|
||||
@@ -38,7 +38,9 @@ class FastAPI(Starlette):
|
||||
**extra: Dict[str, Any],
|
||||
) -> None:
|
||||
self._debug = debug
|
||||
self.router: routing.APIRouter = routing.APIRouter(routes)
|
||||
self.router: routing.APIRouter = routing.APIRouter(
|
||||
routes, dependency_overrides_provider=self
|
||||
)
|
||||
self.exception_middleware = ExceptionMiddleware(self.router, debug=debug)
|
||||
self.error_middleware = ServerErrorMiddleware(
|
||||
self.exception_middleware, debug=debug
|
||||
@@ -53,6 +55,7 @@ class FastAPI(Starlette):
|
||||
self.redoc_url = redoc_url
|
||||
self.swagger_ui_oauth2_redirect_url = swagger_ui_oauth2_redirect_url
|
||||
self.extra = extra
|
||||
self.dependency_overrides: Dict[Callable, Callable] = {}
|
||||
|
||||
self.openapi_version = "3.0.2"
|
||||
|
||||
|
||||
@@ -27,9 +27,12 @@ class Dependant:
|
||||
call: Callable = None,
|
||||
request_param_name: str = None,
|
||||
websocket_param_name: str = None,
|
||||
response_param_name: str = None,
|
||||
background_tasks_param_name: str = None,
|
||||
security_scopes_param_name: str = None,
|
||||
security_scopes: List[str] = None,
|
||||
use_cache: bool = True,
|
||||
path: str = None,
|
||||
) -> None:
|
||||
self.path_params = path_params or []
|
||||
self.query_params = query_params or []
|
||||
@@ -40,8 +43,14 @@ class Dependant:
|
||||
self.security_requirements = security_schemes or []
|
||||
self.request_param_name = request_param_name
|
||||
self.websocket_param_name = websocket_param_name
|
||||
self.response_param_name = response_param_name
|
||||
self.background_tasks_param_name = background_tasks_param_name
|
||||
self.security_scopes = security_scopes
|
||||
self.security_scopes_param_name = security_scopes_param_name
|
||||
self.name = name
|
||||
self.call = call
|
||||
self.use_cache = use_cache
|
||||
# Store the path to be able to re-generate a dependable from it in overrides
|
||||
self.path = path
|
||||
# Save the cache key at creation to optimize performance
|
||||
self.cache_key = (self.call, tuple(sorted(set(self.security_scopes or []))))
|
||||
|
||||
@@ -31,6 +31,7 @@ from starlette.background import BackgroundTasks
|
||||
from starlette.concurrency import run_in_threadpool
|
||||
from starlette.datastructures import FormData, Headers, QueryParams, UploadFile
|
||||
from starlette.requests import Request
|
||||
from starlette.responses import Response
|
||||
from starlette.websockets import WebSocket
|
||||
|
||||
sequence_shapes = {
|
||||
@@ -95,7 +96,11 @@ def get_sub_dependant(
|
||||
security_scheme=dependency, scopes=use_scopes
|
||||
)
|
||||
sub_dependant = get_dependant(
|
||||
path=path, call=dependency, name=name, security_scopes=security_scopes
|
||||
path=path,
|
||||
call=dependency,
|
||||
name=name,
|
||||
security_scopes=security_scopes,
|
||||
use_cache=depends.use_cache,
|
||||
)
|
||||
if security_requirement:
|
||||
sub_dependant.security_requirements.append(security_requirement)
|
||||
@@ -111,6 +116,8 @@ def get_flat_dependant(dependant: Dependant) -> Dependant:
|
||||
cookie_params=dependant.cookie_params.copy(),
|
||||
body_params=dependant.body_params.copy(),
|
||||
security_schemes=dependant.security_requirements.copy(),
|
||||
use_cache=dependant.use_cache,
|
||||
path=dependant.path,
|
||||
)
|
||||
for sub_dependant in dependant.dependencies:
|
||||
flat_sub = get_flat_dependant(sub_dependant)
|
||||
@@ -127,12 +134,13 @@ def is_scalar_field(field: Field) -> bool:
|
||||
return (
|
||||
field.shape == Shape.SINGLETON
|
||||
and not lenient_issubclass(field.type_, BaseModel)
|
||||
and not lenient_issubclass(field.type_, sequence_types + (dict,))
|
||||
and not isinstance(field.schema, params.Body)
|
||||
)
|
||||
|
||||
|
||||
def is_scalar_sequence_field(field: Field) -> bool:
|
||||
if field.shape in sequence_shapes and not lenient_issubclass(
|
||||
if (field.shape in sequence_shapes) and not lenient_issubclass(
|
||||
field.type_, BaseModel
|
||||
):
|
||||
if field.sub_fields is not None:
|
||||
@@ -140,16 +148,23 @@ def is_scalar_sequence_field(field: Field) -> bool:
|
||||
if not is_scalar_field(sub_field):
|
||||
return False
|
||||
return True
|
||||
if lenient_issubclass(field.type_, sequence_types):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def get_dependant(
|
||||
*, path: str, call: Callable, name: str = None, security_scopes: List[str] = None
|
||||
*,
|
||||
path: str,
|
||||
call: Callable,
|
||||
name: str = None,
|
||||
security_scopes: List[str] = None,
|
||||
use_cache: bool = True,
|
||||
) -> Dependant:
|
||||
path_param_names = get_path_param_names(path)
|
||||
endpoint_signature = inspect.signature(call)
|
||||
signature_params = endpoint_signature.parameters
|
||||
dependant = Dependant(call=call, name=name)
|
||||
dependant = Dependant(call=call, name=name, path=path, use_cache=use_cache)
|
||||
for param_name, param in signature_params.items():
|
||||
if isinstance(param.default, params.Depends):
|
||||
sub_dependant = get_param_sub_dependant(
|
||||
@@ -198,6 +213,9 @@ def add_non_field_param_to_dependency(
|
||||
elif lenient_issubclass(param.annotation, WebSocket):
|
||||
dependant.websocket_param_name = param.name
|
||||
return True
|
||||
elif lenient_issubclass(param.annotation, Response):
|
||||
dependant.response_param_name = param.name
|
||||
return True
|
||||
elif lenient_issubclass(param.annotation, BackgroundTasks):
|
||||
dependant.background_tasks_param_name = param.name
|
||||
return True
|
||||
@@ -279,28 +297,78 @@ async def solve_dependencies(
|
||||
*,
|
||||
request: Union[Request, WebSocket],
|
||||
dependant: Dependant,
|
||||
body: Dict[str, Any] = None,
|
||||
body: Optional[Union[Dict[str, Any], FormData]] = None,
|
||||
background_tasks: BackgroundTasks = None,
|
||||
) -> Tuple[Dict[str, Any], List[ErrorWrapper], Optional[BackgroundTasks]]:
|
||||
response: Response = None,
|
||||
dependency_overrides_provider: Any = None,
|
||||
dependency_cache: Dict[Tuple[Callable, Tuple[str]], Any] = None,
|
||||
) -> Tuple[
|
||||
Dict[str, Any],
|
||||
List[ErrorWrapper],
|
||||
Optional[BackgroundTasks],
|
||||
Response,
|
||||
Dict[Tuple[Callable, Tuple[str]], Any],
|
||||
]:
|
||||
values: Dict[str, Any] = {}
|
||||
errors: List[ErrorWrapper] = []
|
||||
response = response or Response( # type: ignore
|
||||
content=None, status_code=None, headers=None, media_type=None, background=None
|
||||
)
|
||||
dependency_cache = dependency_cache or {}
|
||||
sub_dependant: Dependant
|
||||
for sub_dependant in dependant.dependencies:
|
||||
sub_values, sub_errors, background_tasks = await solve_dependencies(
|
||||
sub_dependant.call = cast(Callable, sub_dependant.call)
|
||||
sub_dependant.cache_key = cast(
|
||||
Tuple[Callable, Tuple[str]], sub_dependant.cache_key
|
||||
)
|
||||
call = sub_dependant.call
|
||||
use_sub_dependant = sub_dependant
|
||||
if (
|
||||
dependency_overrides_provider
|
||||
and dependency_overrides_provider.dependency_overrides
|
||||
):
|
||||
original_call = sub_dependant.call
|
||||
call = getattr(
|
||||
dependency_overrides_provider, "dependency_overrides", {}
|
||||
).get(original_call, original_call)
|
||||
use_path: str = sub_dependant.path # type: ignore
|
||||
use_sub_dependant = get_dependant(
|
||||
path=use_path,
|
||||
call=call,
|
||||
name=sub_dependant.name,
|
||||
security_scopes=sub_dependant.security_scopes,
|
||||
)
|
||||
|
||||
solved_result = await solve_dependencies(
|
||||
request=request,
|
||||
dependant=sub_dependant,
|
||||
dependant=use_sub_dependant,
|
||||
body=body,
|
||||
background_tasks=background_tasks,
|
||||
response=response,
|
||||
dependency_overrides_provider=dependency_overrides_provider,
|
||||
dependency_cache=dependency_cache,
|
||||
)
|
||||
sub_values, sub_errors, background_tasks, sub_response, sub_dependency_cache = (
|
||||
solved_result
|
||||
)
|
||||
sub_response = cast(Response, sub_response)
|
||||
response.headers.raw.extend(sub_response.headers.raw)
|
||||
if sub_response.status_code:
|
||||
response.status_code = sub_response.status_code
|
||||
dependency_cache.update(sub_dependency_cache)
|
||||
if sub_errors:
|
||||
errors.extend(sub_errors)
|
||||
continue
|
||||
assert sub_dependant.call is not None, "sub_dependant.call must be a function"
|
||||
if is_coroutine_callable(sub_dependant.call):
|
||||
solved = await sub_dependant.call(**sub_values)
|
||||
if sub_dependant.use_cache and sub_dependant.cache_key in dependency_cache:
|
||||
solved = dependency_cache[sub_dependant.cache_key]
|
||||
elif is_coroutine_callable(call):
|
||||
solved = await call(**sub_values)
|
||||
else:
|
||||
solved = await run_in_threadpool(sub_dependant.call, **sub_values)
|
||||
solved = await run_in_threadpool(call, **sub_values)
|
||||
if sub_dependant.name is not None:
|
||||
values[sub_dependant.name] = solved
|
||||
if sub_dependant.cache_key not in dependency_cache:
|
||||
dependency_cache[sub_dependant.cache_key] = solved
|
||||
path_values, path_errors = request_params_to_args(
|
||||
dependant.path_params, request.path_params
|
||||
)
|
||||
@@ -320,7 +388,7 @@ async def solve_dependencies(
|
||||
errors += path_errors + query_errors + header_errors + cookie_errors
|
||||
if dependant.body_params:
|
||||
body_values, body_errors = await request_body_to_args( # type: ignore # body_params checked above
|
||||
dependant.body_params, body
|
||||
required_params=dependant.body_params, received_body=body
|
||||
)
|
||||
values.update(body_values)
|
||||
errors.extend(body_errors)
|
||||
@@ -332,11 +400,13 @@ async def solve_dependencies(
|
||||
if background_tasks is None:
|
||||
background_tasks = BackgroundTasks()
|
||||
values[dependant.background_tasks_param_name] = background_tasks
|
||||
if dependant.response_param_name:
|
||||
values[dependant.response_param_name] = response
|
||||
if dependant.security_scopes_param_name:
|
||||
values[dependant.security_scopes_param_name] = SecurityScopes(
|
||||
scopes=dependant.security_scopes
|
||||
)
|
||||
return values, errors, background_tasks
|
||||
return values, errors, background_tasks, response, dependency_cache
|
||||
|
||||
|
||||
def request_params_to_args(
|
||||
@@ -346,7 +416,7 @@ def request_params_to_args(
|
||||
values = {}
|
||||
errors = []
|
||||
for field in required_params:
|
||||
if field.shape in sequence_shapes and isinstance(
|
||||
if is_scalar_sequence_field(field) and isinstance(
|
||||
received_params, (QueryParams, Headers)
|
||||
):
|
||||
value = received_params.getlist(field.alias) or field.default
|
||||
@@ -377,7 +447,8 @@ def request_params_to_args(
|
||||
|
||||
|
||||
async def request_body_to_args(
|
||||
required_params: List[Field], received_body: Dict[str, Any]
|
||||
required_params: List[Field],
|
||||
received_body: Optional[Union[Dict[str, Any], FormData]],
|
||||
) -> Tuple[Dict[str, Any], List[ErrorWrapper]]:
|
||||
values = {}
|
||||
errors = []
|
||||
@@ -387,10 +458,14 @@ async def request_body_to_args(
|
||||
if len(required_params) == 1 and not embed:
|
||||
received_body = {field.alias: received_body}
|
||||
for field in required_params:
|
||||
if field.shape in sequence_shapes and isinstance(received_body, FormData):
|
||||
value = received_body.getlist(field.alias)
|
||||
else:
|
||||
value = received_body.get(field.alias)
|
||||
value = None
|
||||
if received_body is not None:
|
||||
if field.shape in sequence_shapes and isinstance(
|
||||
received_body, FormData
|
||||
):
|
||||
value = received_body.getlist(field.alias)
|
||||
else:
|
||||
value = received_body.get(field.alias)
|
||||
if (
|
||||
value is None
|
||||
or (isinstance(field.schema, params.Form) and value == "")
|
||||
|
||||
@@ -238,11 +238,13 @@ def File( # noqa: N802
|
||||
)
|
||||
|
||||
|
||||
def Depends(dependency: Callable = None) -> Any: # noqa: N802
|
||||
return params.Depends(dependency=dependency)
|
||||
def Depends( # noqa: N802
|
||||
dependency: Callable = None, *, use_cache: bool = True
|
||||
) -> Any:
|
||||
return params.Depends(dependency=dependency, use_cache=use_cache)
|
||||
|
||||
|
||||
def Security( # noqa: N802
|
||||
dependency: Callable = None, scopes: Sequence[str] = None
|
||||
dependency: Callable = None, *, scopes: Sequence[str] = None, use_cache: bool = True
|
||||
) -> Any:
|
||||
return params.Security(dependency=dependency, scopes=scopes)
|
||||
return params.Security(dependency=dependency, scopes=scopes, use_cache=use_cache)
|
||||
|
||||
@@ -308,11 +308,18 @@ class File(Form):
|
||||
|
||||
|
||||
class Depends:
|
||||
def __init__(self, dependency: Callable = None):
|
||||
def __init__(self, dependency: Callable = None, *, use_cache: bool = True):
|
||||
self.dependency = dependency
|
||||
self.use_cache = use_cache
|
||||
|
||||
|
||||
class Security(Depends):
|
||||
def __init__(self, dependency: Callable = None, scopes: Sequence[str] = None):
|
||||
def __init__(
|
||||
self,
|
||||
dependency: Callable = None,
|
||||
*,
|
||||
scopes: Sequence[str] = None,
|
||||
use_cache: bool = True,
|
||||
):
|
||||
super().__init__(dependency=dependency, use_cache=use_cache)
|
||||
self.scopes = scopes or []
|
||||
super().__init__(dependency=dependency)
|
||||
|
||||
@@ -30,6 +30,7 @@ from starlette.routing import (
|
||||
websocket_session,
|
||||
)
|
||||
from starlette.status import WS_1008_POLICY_VIOLATION
|
||||
from starlette.types import ASGIApp
|
||||
from starlette.websockets import WebSocket
|
||||
|
||||
|
||||
@@ -80,6 +81,7 @@ def get_app(
|
||||
response_model_exclude: Set[str] = set(),
|
||||
response_model_by_alias: bool = True,
|
||||
response_model_skip_defaults: bool = False,
|
||||
dependency_overrides_provider: Any = None,
|
||||
) -> Callable:
|
||||
assert dependant.call is not None, "dependant.call must be a function"
|
||||
is_coroutine = asyncio.iscoroutinefunction(dependant.call)
|
||||
@@ -100,9 +102,13 @@ def get_app(
|
||||
raise HTTPException(
|
||||
status_code=400, detail="There was an error parsing the body"
|
||||
) from e
|
||||
values, errors, background_tasks = await solve_dependencies(
|
||||
request=request, dependant=dependant, body=body
|
||||
solved_result = await solve_dependencies(
|
||||
request=request,
|
||||
dependant=dependant,
|
||||
body=body,
|
||||
dependency_overrides_provider=dependency_overrides_provider,
|
||||
)
|
||||
values, errors, background_tasks, sub_response, _ = solved_result
|
||||
if errors:
|
||||
raise RequestValidationError(errors)
|
||||
else:
|
||||
@@ -123,20 +129,29 @@ def get_app(
|
||||
by_alias=response_model_by_alias,
|
||||
skip_defaults=response_model_skip_defaults,
|
||||
)
|
||||
return response_class(
|
||||
response = response_class(
|
||||
content=response_data,
|
||||
status_code=status_code,
|
||||
background=background_tasks,
|
||||
)
|
||||
response.headers.raw.extend(sub_response.headers.raw)
|
||||
if sub_response.status_code:
|
||||
response.status_code = sub_response.status_code
|
||||
return response
|
||||
|
||||
return app
|
||||
|
||||
|
||||
def get_websocket_app(dependant: Dependant) -> Callable:
|
||||
def get_websocket_app(
|
||||
dependant: Dependant, dependency_overrides_provider: Any = None
|
||||
) -> Callable:
|
||||
async def app(websocket: WebSocket) -> None:
|
||||
values, errors, _ = await solve_dependencies(
|
||||
request=websocket, dependant=dependant
|
||||
solved_result = await solve_dependencies(
|
||||
request=websocket,
|
||||
dependant=dependant,
|
||||
dependency_overrides_provider=dependency_overrides_provider,
|
||||
)
|
||||
values, errors, _, _2, _3 = solved_result
|
||||
if errors:
|
||||
await websocket.close(code=WS_1008_POLICY_VIOLATION)
|
||||
raise WebSocketRequestValidationError(errors)
|
||||
@@ -147,12 +162,24 @@ def get_websocket_app(dependant: Dependant) -> Callable:
|
||||
|
||||
|
||||
class APIWebSocketRoute(routing.WebSocketRoute):
|
||||
def __init__(self, path: str, endpoint: Callable, *, name: str = None) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
path: str,
|
||||
endpoint: Callable,
|
||||
*,
|
||||
name: str = None,
|
||||
dependency_overrides_provider: Any = None,
|
||||
) -> None:
|
||||
self.path = path
|
||||
self.endpoint = endpoint
|
||||
self.name = get_name(endpoint) if name is None else name
|
||||
self.dependant = get_dependant(path=path, call=self.endpoint)
|
||||
self.app = websocket_session(get_websocket_app(dependant=self.dependant))
|
||||
self.app = websocket_session(
|
||||
get_websocket_app(
|
||||
dependant=self.dependant,
|
||||
dependency_overrides_provider=dependency_overrides_provider,
|
||||
)
|
||||
)
|
||||
regex = "^" + path + "$"
|
||||
regex = re.sub("{([a-zA-Z_][a-zA-Z0-9_]*)}", r"(?P<\1>[^/]+)", regex)
|
||||
self.path_regex, self.path_format, self.param_convertors = compile_path(path)
|
||||
@@ -182,6 +209,7 @@ class APIRoute(routing.Route):
|
||||
response_model_skip_defaults: bool = False,
|
||||
include_in_schema: bool = True,
|
||||
response_class: Type[Response] = JSONResponse,
|
||||
dependency_overrides_provider: Any = None,
|
||||
) -> None:
|
||||
assert path.startswith("/"), "Routed paths must always start with '/'"
|
||||
self.path = path
|
||||
@@ -257,6 +285,7 @@ class APIRoute(routing.Route):
|
||||
get_parameterless_sub_dependant(depends=depends, path=self.path_format),
|
||||
)
|
||||
self.body_field = get_body_field(dependant=self.dependant, name=self.name)
|
||||
self.dependency_overrides_provider = dependency_overrides_provider
|
||||
self.app = request_response(
|
||||
get_app(
|
||||
dependant=self.dependant,
|
||||
@@ -268,11 +297,24 @@ class APIRoute(routing.Route):
|
||||
response_model_exclude=self.response_model_exclude,
|
||||
response_model_by_alias=self.response_model_by_alias,
|
||||
response_model_skip_defaults=self.response_model_skip_defaults,
|
||||
dependency_overrides_provider=self.dependency_overrides_provider,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class APIRouter(routing.Router):
|
||||
def __init__(
|
||||
self,
|
||||
routes: List[routing.BaseRoute] = None,
|
||||
redirect_slashes: bool = True,
|
||||
default: ASGIApp = None,
|
||||
dependency_overrides_provider: Any = None,
|
||||
) -> None:
|
||||
super().__init__(
|
||||
routes=routes, redirect_slashes=redirect_slashes, default=default
|
||||
)
|
||||
self.dependency_overrides_provider = dependency_overrides_provider
|
||||
|
||||
def add_api_route(
|
||||
self,
|
||||
path: str,
|
||||
@@ -318,6 +360,7 @@ class APIRouter(routing.Router):
|
||||
include_in_schema=include_in_schema,
|
||||
response_class=response_class,
|
||||
name=name,
|
||||
dependency_overrides_provider=self.dependency_overrides_provider,
|
||||
)
|
||||
self.routes.append(route)
|
||||
|
||||
|
||||
@@ -51,6 +51,7 @@ nav:
|
||||
- Additional Responses in OpenAPI: 'tutorial/additional-responses.md'
|
||||
- Response Cookies: 'tutorial/response-cookies.md'
|
||||
- Response Headers: 'tutorial/response-headers.md'
|
||||
- Response - Change Status Code: 'tutorial/response-change-status-code.md'
|
||||
- Dependencies:
|
||||
- First Steps: 'tutorial/dependencies/first-steps.md'
|
||||
- Classes as Dependencies: 'tutorial/dependencies/classes-as-dependencies.md'
|
||||
@@ -81,6 +82,7 @@ nav:
|
||||
- WebSockets: 'tutorial/websockets.md'
|
||||
- 'Events: startup - shutdown': 'tutorial/events.md'
|
||||
- Testing: 'tutorial/testing.md'
|
||||
- Testing Dependencies with Overrides: 'tutorial/testing-dependencies.md'
|
||||
- Debugging: 'tutorial/debugging.md'
|
||||
- Extending OpenAPI: 'tutorial/extending-openapi.md'
|
||||
- Concurrency and async / await: 'async.md'
|
||||
|
||||
6
scripts/format-imports.sh
Executable file
6
scripts/format-imports.sh
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/bin/sh -e
|
||||
set -x
|
||||
|
||||
# Sort imports one per line, so autoflake can remove unused imports
|
||||
isort --recursive --force-single-line-imports --thirdparty fastapi --apply fastapi tests docs/src
|
||||
sh ./scripts/format.sh
|
||||
68
tests/test_dependency_cache.py
Normal file
68
tests/test_dependency_cache.py
Normal file
@@ -0,0 +1,68 @@
|
||||
from fastapi import Depends, FastAPI
|
||||
from starlette.testclient import TestClient
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
counter_holder = {"counter": 0}
|
||||
|
||||
|
||||
async def dep_counter():
|
||||
counter_holder["counter"] += 1
|
||||
return counter_holder["counter"]
|
||||
|
||||
|
||||
async def super_dep(count: int = Depends(dep_counter)):
|
||||
return count
|
||||
|
||||
|
||||
@app.get("/counter/")
|
||||
async def get_counter(count: int = Depends(dep_counter)):
|
||||
return {"counter": count}
|
||||
|
||||
|
||||
@app.get("/sub-counter/")
|
||||
async def get_sub_counter(
|
||||
subcount: int = Depends(super_dep), count: int = Depends(dep_counter)
|
||||
):
|
||||
return {"counter": count, "subcounter": subcount}
|
||||
|
||||
|
||||
@app.get("/sub-counter-no-cache/")
|
||||
async def get_sub_counter_no_cache(
|
||||
subcount: int = Depends(super_dep),
|
||||
count: int = Depends(dep_counter, use_cache=False),
|
||||
):
|
||||
return {"counter": count, "subcounter": subcount}
|
||||
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
def test_normal_counter():
|
||||
counter_holder["counter"] = 0
|
||||
response = client.get("/counter/")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"counter": 1}
|
||||
response = client.get("/counter/")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"counter": 2}
|
||||
|
||||
|
||||
def test_sub_counter():
|
||||
counter_holder["counter"] = 0
|
||||
response = client.get("/sub-counter/")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"counter": 1, "subcounter": 1}
|
||||
response = client.get("/sub-counter/")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"counter": 2, "subcounter": 2}
|
||||
|
||||
|
||||
def test_sub_counter_no_cache():
|
||||
counter_holder["counter"] = 0
|
||||
response = client.get("/sub-counter-no-cache/")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"counter": 2, "subcounter": 1}
|
||||
response = client.get("/sub-counter-no-cache/")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"counter": 4, "subcounter": 3}
|
||||
313
tests/test_dependency_overrides.py
Normal file
313
tests/test_dependency_overrides.py
Normal file
@@ -0,0 +1,313 @@
|
||||
import pytest
|
||||
from fastapi import APIRouter, Depends, FastAPI
|
||||
from starlette.testclient import TestClient
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
async def common_parameters(q: str, skip: int = 0, limit: int = 100):
|
||||
return {"q": q, "skip": skip, "limit": limit}
|
||||
|
||||
|
||||
@app.get("/main-depends/")
|
||||
async def main_depends(commons: dict = Depends(common_parameters)):
|
||||
return {"in": "main-depends", "params": commons}
|
||||
|
||||
|
||||
@app.get("/decorator-depends/", dependencies=[Depends(common_parameters)])
|
||||
async def decorator_depends():
|
||||
return {"in": "decorator-depends"}
|
||||
|
||||
|
||||
@router.get("/router-depends/")
|
||||
async def router_depends(commons: dict = Depends(common_parameters)):
|
||||
return {"in": "router-depends", "params": commons}
|
||||
|
||||
|
||||
@router.get("/router-decorator-depends/", dependencies=[Depends(common_parameters)])
|
||||
async def router_decorator_depends():
|
||||
return {"in": "router-decorator-depends"}
|
||||
|
||||
|
||||
app.include_router(router)
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
async def overrider_dependency_simple(q: str = None):
|
||||
return {"q": q, "skip": 5, "limit": 10}
|
||||
|
||||
|
||||
async def overrider_sub_dependency(k: str):
|
||||
return {"k": k}
|
||||
|
||||
|
||||
async def overrider_dependency_with_sub(msg: dict = Depends(overrider_sub_dependency)):
|
||||
return msg
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"url,status_code,expected",
|
||||
[
|
||||
(
|
||||
"/main-depends/",
|
||||
422,
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"loc": ["query", "q"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
}
|
||||
]
|
||||
},
|
||||
),
|
||||
(
|
||||
"/main-depends/?q=foo",
|
||||
200,
|
||||
{"in": "main-depends", "params": {"q": "foo", "skip": 0, "limit": 100}},
|
||||
),
|
||||
(
|
||||
"/main-depends/?q=foo&skip=100&limit=200",
|
||||
200,
|
||||
{"in": "main-depends", "params": {"q": "foo", "skip": 100, "limit": 200}},
|
||||
),
|
||||
(
|
||||
"/decorator-depends/",
|
||||
422,
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"loc": ["query", "q"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
}
|
||||
]
|
||||
},
|
||||
),
|
||||
("/decorator-depends/?q=foo", 200, {"in": "decorator-depends"}),
|
||||
(
|
||||
"/decorator-depends/?q=foo&skip=100&limit=200",
|
||||
200,
|
||||
{"in": "decorator-depends"},
|
||||
),
|
||||
(
|
||||
"/router-depends/",
|
||||
422,
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"loc": ["query", "q"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
}
|
||||
]
|
||||
},
|
||||
),
|
||||
(
|
||||
"/router-depends/?q=foo",
|
||||
200,
|
||||
{"in": "router-depends", "params": {"q": "foo", "skip": 0, "limit": 100}},
|
||||
),
|
||||
(
|
||||
"/router-depends/?q=foo&skip=100&limit=200",
|
||||
200,
|
||||
{"in": "router-depends", "params": {"q": "foo", "skip": 100, "limit": 200}},
|
||||
),
|
||||
(
|
||||
"/router-decorator-depends/",
|
||||
422,
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"loc": ["query", "q"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
}
|
||||
]
|
||||
},
|
||||
),
|
||||
("/router-decorator-depends/?q=foo", 200, {"in": "router-decorator-depends"}),
|
||||
(
|
||||
"/router-decorator-depends/?q=foo&skip=100&limit=200",
|
||||
200,
|
||||
{"in": "router-decorator-depends"},
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_normal_app(url, status_code, expected):
|
||||
response = client.get(url)
|
||||
assert response.status_code == status_code
|
||||
assert response.json() == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"url,status_code,expected",
|
||||
[
|
||||
(
|
||||
"/main-depends/",
|
||||
200,
|
||||
{"in": "main-depends", "params": {"q": None, "skip": 5, "limit": 10}},
|
||||
),
|
||||
(
|
||||
"/main-depends/?q=foo",
|
||||
200,
|
||||
{"in": "main-depends", "params": {"q": "foo", "skip": 5, "limit": 10}},
|
||||
),
|
||||
(
|
||||
"/main-depends/?q=foo&skip=100&limit=200",
|
||||
200,
|
||||
{"in": "main-depends", "params": {"q": "foo", "skip": 5, "limit": 10}},
|
||||
),
|
||||
("/decorator-depends/", 200, {"in": "decorator-depends"}),
|
||||
(
|
||||
"/router-depends/",
|
||||
200,
|
||||
{"in": "router-depends", "params": {"q": None, "skip": 5, "limit": 10}},
|
||||
),
|
||||
(
|
||||
"/router-depends/?q=foo",
|
||||
200,
|
||||
{"in": "router-depends", "params": {"q": "foo", "skip": 5, "limit": 10}},
|
||||
),
|
||||
(
|
||||
"/router-depends/?q=foo&skip=100&limit=200",
|
||||
200,
|
||||
{"in": "router-depends", "params": {"q": "foo", "skip": 5, "limit": 10}},
|
||||
),
|
||||
("/router-decorator-depends/", 200, {"in": "router-decorator-depends"}),
|
||||
],
|
||||
)
|
||||
def test_override_simple(url, status_code, expected):
|
||||
app.dependency_overrides[common_parameters] = overrider_dependency_simple
|
||||
response = client.get(url)
|
||||
assert response.status_code == status_code
|
||||
assert response.json() == expected
|
||||
app.dependency_overrides = {}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"url,status_code,expected",
|
||||
[
|
||||
(
|
||||
"/main-depends/",
|
||||
422,
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"loc": ["query", "k"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
}
|
||||
]
|
||||
},
|
||||
),
|
||||
(
|
||||
"/main-depends/?q=foo",
|
||||
422,
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"loc": ["query", "k"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
}
|
||||
]
|
||||
},
|
||||
),
|
||||
("/main-depends/?k=bar", 200, {"in": "main-depends", "params": {"k": "bar"}}),
|
||||
(
|
||||
"/decorator-depends/",
|
||||
422,
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"loc": ["query", "k"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
}
|
||||
]
|
||||
},
|
||||
),
|
||||
(
|
||||
"/decorator-depends/?q=foo",
|
||||
422,
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"loc": ["query", "k"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
}
|
||||
]
|
||||
},
|
||||
),
|
||||
("/decorator-depends/?k=bar", 200, {"in": "decorator-depends"}),
|
||||
(
|
||||
"/router-depends/",
|
||||
422,
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"loc": ["query", "k"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
}
|
||||
]
|
||||
},
|
||||
),
|
||||
(
|
||||
"/router-depends/?q=foo",
|
||||
422,
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"loc": ["query", "k"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
}
|
||||
]
|
||||
},
|
||||
),
|
||||
(
|
||||
"/router-depends/?k=bar",
|
||||
200,
|
||||
{"in": "router-depends", "params": {"k": "bar"}},
|
||||
),
|
||||
(
|
||||
"/router-decorator-depends/",
|
||||
422,
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"loc": ["query", "k"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
}
|
||||
]
|
||||
},
|
||||
),
|
||||
(
|
||||
"/router-decorator-depends/?q=foo",
|
||||
422,
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"loc": ["query", "k"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
}
|
||||
]
|
||||
},
|
||||
),
|
||||
("/router-decorator-depends/?k=bar", 200, {"in": "router-decorator-depends"}),
|
||||
],
|
||||
)
|
||||
def test_override_with_sub(url, status_code, expected):
|
||||
app.dependency_overrides[common_parameters] = overrider_dependency_with_sub
|
||||
response = client.get(url)
|
||||
assert response.status_code == status_code
|
||||
assert response.json() == expected
|
||||
app.dependency_overrides = {}
|
||||
77
tests/test_invalid_path_param.py
Normal file
77
tests/test_invalid_path_param.py
Normal file
@@ -0,0 +1,77 @@
|
||||
from typing import Dict, List, Tuple
|
||||
|
||||
import pytest
|
||||
from fastapi import FastAPI
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
def test_invalid_sequence():
|
||||
with pytest.raises(AssertionError):
|
||||
app = FastAPI()
|
||||
|
||||
class Item(BaseModel):
|
||||
title: str
|
||||
|
||||
@app.get("/items/{id}")
|
||||
def read_items(id: List[Item]):
|
||||
pass # pragma: no cover
|
||||
|
||||
|
||||
def test_invalid_tuple():
|
||||
with pytest.raises(AssertionError):
|
||||
app = FastAPI()
|
||||
|
||||
class Item(BaseModel):
|
||||
title: str
|
||||
|
||||
@app.get("/items/{id}")
|
||||
def read_items(id: Tuple[Item, Item]):
|
||||
pass # pragma: no cover
|
||||
|
||||
|
||||
def test_invalid_dict():
|
||||
with pytest.raises(AssertionError):
|
||||
app = FastAPI()
|
||||
|
||||
class Item(BaseModel):
|
||||
title: str
|
||||
|
||||
@app.get("/items/{id}")
|
||||
def read_items(id: Dict[str, Item]):
|
||||
pass # pragma: no cover
|
||||
|
||||
|
||||
def test_invalid_simple_list():
|
||||
with pytest.raises(AssertionError):
|
||||
app = FastAPI()
|
||||
|
||||
@app.get("/items/{id}")
|
||||
def read_items(id: list):
|
||||
pass # pragma: no cover
|
||||
|
||||
|
||||
def test_invalid_simple_tuple():
|
||||
with pytest.raises(AssertionError):
|
||||
app = FastAPI()
|
||||
|
||||
@app.get("/items/{id}")
|
||||
def read_items(id: tuple):
|
||||
pass # pragma: no cover
|
||||
|
||||
|
||||
def test_invalid_simple_set():
|
||||
with pytest.raises(AssertionError):
|
||||
app = FastAPI()
|
||||
|
||||
@app.get("/items/{id}")
|
||||
def read_items(id: set):
|
||||
pass # pragma: no cover
|
||||
|
||||
|
||||
def test_invalid_simple_dict():
|
||||
with pytest.raises(AssertionError):
|
||||
app = FastAPI()
|
||||
|
||||
@app.get("/items/{id}")
|
||||
def read_items(id: dict):
|
||||
pass # pragma: no cover
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import List, Tuple
|
||||
from typing import Dict, List, Tuple
|
||||
|
||||
import pytest
|
||||
from fastapi import FastAPI, Query
|
||||
@@ -27,3 +27,27 @@ def test_invalid_tuple():
|
||||
@app.get("/items/")
|
||||
def read_items(q: Tuple[Item, Item] = Query(None)):
|
||||
pass # pragma: no cover
|
||||
|
||||
|
||||
def test_invalid_dict():
|
||||
with pytest.raises(AssertionError):
|
||||
app = FastAPI()
|
||||
|
||||
class Item(BaseModel):
|
||||
title: str
|
||||
|
||||
@app.get("/items/")
|
||||
def read_items(q: Dict[str, Item] = Query(None)):
|
||||
pass # pragma: no cover
|
||||
|
||||
|
||||
def test_invalid_simple_dict():
|
||||
with pytest.raises(AssertionError):
|
||||
app = FastAPI()
|
||||
|
||||
class Item(BaseModel):
|
||||
title: str
|
||||
|
||||
@app.get("/items/")
|
||||
def read_items(q: dict = Query(None)):
|
||||
pass # pragma: no cover
|
||||
|
||||
27
tests/test_response_change_status_code.py
Normal file
27
tests/test_response_change_status_code.py
Normal file
@@ -0,0 +1,27 @@
|
||||
from fastapi import Depends, FastAPI
|
||||
from starlette.responses import Response
|
||||
from starlette.testclient import TestClient
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
async def response_status_setter(response: Response):
|
||||
response.status_code = 201
|
||||
|
||||
|
||||
async def parent_dep(result=Depends(response_status_setter)):
|
||||
return result
|
||||
|
||||
|
||||
@app.get("/", dependencies=[Depends(parent_dep)])
|
||||
async def get_main():
|
||||
return {"msg": "Hello World"}
|
||||
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
def test_dependency_set_status_code():
|
||||
response = client.get("/")
|
||||
assert response.status_code == 201
|
||||
assert response.json() == {"msg": "Hello World"}
|
||||
@@ -0,0 +1,172 @@
|
||||
import pytest
|
||||
from starlette.testclient import TestClient
|
||||
|
||||
from body_multiple_params.tutorial003 import app
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
openapi_schema = {
|
||||
"openapi": "3.0.2",
|
||||
"info": {"title": "Fast API", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/items/{item_id}": {
|
||||
"put": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Update Item",
|
||||
"operationId": "update_item_items__item_id__put",
|
||||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "Item_Id", "type": "integer"},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {"$ref": "#/components/schemas/Body_update_item"}
|
||||
}
|
||||
},
|
||||
"required": True,
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"Item": {
|
||||
"title": "Item",
|
||||
"required": ["name", "price"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"title": "Name", "type": "string"},
|
||||
"price": {"title": "Price", "type": "number"},
|
||||
"description": {"title": "Description", "type": "string"},
|
||||
"tax": {"title": "Tax", "type": "number"},
|
||||
},
|
||||
},
|
||||
"User": {
|
||||
"title": "User",
|
||||
"required": ["username"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"username": {"title": "Username", "type": "string"},
|
||||
"full_name": {"title": "Full_Name", "type": "string"},
|
||||
},
|
||||
},
|
||||
"Body_update_item": {
|
||||
"title": "Body_update_item",
|
||||
"required": ["item", "user", "importance"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"item": {"$ref": "#/components/schemas/Item"},
|
||||
"user": {"$ref": "#/components/schemas/User"},
|
||||
"importance": {"title": "Importance", "type": "integer"},
|
||||
},
|
||||
},
|
||||
"ValidationError": {
|
||||
"title": "ValidationError",
|
||||
"required": ["loc", "msg", "type"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"loc": {
|
||||
"title": "Location",
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
},
|
||||
"msg": {"title": "Message", "type": "string"},
|
||||
"type": {"title": "Error Type", "type": "string"},
|
||||
},
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def test_openapi_schema():
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == openapi_schema
|
||||
|
||||
|
||||
# Test required and embedded body parameters with no bodies sent
|
||||
@pytest.mark.parametrize(
|
||||
"path,body,expected_status,expected_response",
|
||||
[
|
||||
(
|
||||
"/items/5",
|
||||
{
|
||||
"importance": 2,
|
||||
"item": {"name": "Foo", "price": 50.5},
|
||||
"user": {"username": "Dave"},
|
||||
},
|
||||
200,
|
||||
{
|
||||
"item_id": 5,
|
||||
"importance": 2,
|
||||
"item": {
|
||||
"name": "Foo",
|
||||
"price": 50.5,
|
||||
"description": None,
|
||||
"tax": None,
|
||||
},
|
||||
"user": {"username": "Dave", "full_name": None},
|
||||
},
|
||||
),
|
||||
(
|
||||
"/items/5",
|
||||
None,
|
||||
422,
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"loc": ["body", "item"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
},
|
||||
{
|
||||
"loc": ["body", "user"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
},
|
||||
{
|
||||
"loc": ["body", "importance"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
},
|
||||
]
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_post_body(path, body, expected_status, expected_response):
|
||||
response = client.put(path, json=body)
|
||||
assert response.status_code == expected_status
|
||||
assert response.json() == expected_response
|
||||
@@ -86,3 +86,10 @@ def test_multi_query_values():
|
||||
response = client.get(url)
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"q": ["foo", "bar"]}
|
||||
|
||||
|
||||
def test_query_no_values():
|
||||
url = "/items/"
|
||||
response = client.get(url)
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"q": None}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
from starlette.testclient import TestClient
|
||||
|
||||
from query_params_str_validations.tutorial013 import app
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
openapi_schema = {
|
||||
"openapi": "3.0.2",
|
||||
"info": {"title": "Fast API", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/items/": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Read Items",
|
||||
"operationId": "read_items_items__get",
|
||||
"parameters": [
|
||||
{
|
||||
"required": False,
|
||||
"schema": {"title": "Q", "type": "array"},
|
||||
"name": "q",
|
||||
"in": "query",
|
||||
}
|
||||
],
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"ValidationError": {
|
||||
"title": "ValidationError",
|
||||
"required": ["loc", "msg", "type"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"loc": {
|
||||
"title": "Location",
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
},
|
||||
"msg": {"title": "Message", "type": "string"},
|
||||
"type": {"title": "Error Type", "type": "string"},
|
||||
},
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def test_openapi_schema():
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == openapi_schema
|
||||
|
||||
|
||||
def test_multi_query_values():
|
||||
url = "/items/?q=foo&q=bar"
|
||||
response = client.get(url)
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"q": ["foo", "bar"]}
|
||||
|
||||
|
||||
def test_query_no_values():
|
||||
url = "/items/"
|
||||
response = client.get(url)
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"q": None}
|
||||
@@ -0,0 +1,15 @@
|
||||
from starlette.testclient import TestClient
|
||||
|
||||
from response_change_status_code.tutorial001 import app
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
def test_path_operation():
|
||||
response = client.put("/get-or-create-task/foo")
|
||||
print(response.content)
|
||||
assert response.status_code == 200
|
||||
assert response.json() == "Listen to the Bar Fighters"
|
||||
response = client.put("/get-or-create-task/bar")
|
||||
assert response.status_code == 201
|
||||
assert response.json() == "This didn't exist before"
|
||||
@@ -0,0 +1,12 @@
|
||||
from starlette.testclient import TestClient
|
||||
|
||||
from response_cookies.tutorial002 import app
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
def test_path_operation():
|
||||
response = client.post("/cookie-and-object/")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"message": "Come to the dark side, we have cookies"}
|
||||
assert response.cookies["fakesession"] == "fake-cookie-session-value"
|
||||
@@ -0,0 +1,12 @@
|
||||
from starlette.testclient import TestClient
|
||||
|
||||
from response_headers.tutorial002 import app
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
def test_path_operation():
|
||||
response = client.get("/headers-and-object/")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"message": "Hello World"}
|
||||
assert response.headers["X-Cat-Dog"] == "alone in the world"
|
||||
@@ -0,0 +1,56 @@
|
||||
from dependency_testing.tutorial001 import (
|
||||
app,
|
||||
client,
|
||||
test_override_in_items,
|
||||
test_override_in_items_with_params,
|
||||
test_override_in_items_with_q,
|
||||
)
|
||||
|
||||
|
||||
def test_override_in_items_run():
|
||||
test_override_in_items()
|
||||
|
||||
|
||||
def test_override_in_items_with_q_run():
|
||||
test_override_in_items_with_q()
|
||||
|
||||
|
||||
def test_override_in_items_with_params_run():
|
||||
test_override_in_items_with_params()
|
||||
|
||||
|
||||
def test_override_in_users():
|
||||
response = client.get("/users/")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"message": "Hello Users!",
|
||||
"params": {"q": None, "skip": 5, "limit": 10},
|
||||
}
|
||||
|
||||
|
||||
def test_override_in_users_with_q():
|
||||
response = client.get("/users/?q=foo")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"message": "Hello Users!",
|
||||
"params": {"q": "foo", "skip": 5, "limit": 10},
|
||||
}
|
||||
|
||||
|
||||
def test_override_in_users_with_params():
|
||||
response = client.get("/users/?q=foo&skip=100&limit=200")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"message": "Hello Users!",
|
||||
"params": {"q": "foo", "skip": 5, "limit": 10},
|
||||
}
|
||||
|
||||
|
||||
def test_normal_app():
|
||||
app.dependency_overrides = None
|
||||
response = client.get("/items/?q=foo&skip=100&limit=200")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"message": "Hello Items!",
|
||||
"params": {"q": "foo", "skip": 100, "limit": 200},
|
||||
}
|
||||
Reference in New Issue
Block a user