Compare commits

..

21 Commits

Author SHA1 Message Date
Sebastián Ramírez
d91b2b3ee8 🔖 Release version 0.48.0 2020-02-04 05:43:07 +01:00
Sebastián Ramírez
1623b26855 📝 Update release notes 2020-02-04 05:42:12 +01:00
Sebastián Ramírez
9573130630 Lint first in tests, to error out faster (#948) 2020-02-04 05:41:42 +01:00
Sebastián Ramírez
2e0a102565 🔇 Log email-validator not installed only when used (#946) 2020-02-04 05:31:01 +01:00
Sebastián Ramírez
636ce6b3f7 📝 Update Peewee docs, simplify with double dependency with yield (#947) 2020-02-04 05:28:19 +01:00
नवुले पवन कुमार राव
1fd1e8733a 📝 Add link: Create and Deploy FastAPI app to Heroku (#942) 2020-02-04 05:25:00 +01:00
raphaelauv
44b45caf65 📝 Update Sanic description as it is now ASGI too 🎉 (#932) 2020-02-04 05:14:07 +01:00
Maciej Marzęta
e6da96fbb7 ✏️ Fix typos (#920) 2020-02-04 05:04:32 +01:00
David Montague
c425509d57 🐛 Fix body parsing (#918) 2020-02-04 05:01:59 +01:00
adursun
d8451f75a4 ✏️ Fix typo (#916) 2020-02-04 04:57:18 +01:00
李冬冬
a448bd63bd 🐛 Allow Any type for enums in OpenAPI (#906) 2020-02-04 04:37:47 +01:00
Ben
27fb2b358c 📝 Add article to external links (#901) 2020-02-04 04:25:52 +01:00
Timothée Mazzucotelli
68723d5291 📝 Add note about Body parameters without Pydantic (#900) 2020-02-04 04:17:20 +01:00
Andy Smith
70bdade23b 🐛 Fix Pydantic field clone logic with validators (#899) 2020-02-04 04:03:51 +01:00
linchiwei123
4f964939a1 🐛 Fix middleware docs link (#893) 2020-02-04 03:27:10 +01:00
Sebastián Ramírez
55afb70b37 📝 Update release notes 2020-01-18 19:08:24 +01:00
Sebastián Ramírez
b307d38897 ♻️ Update default API title from "Fast API" to "FastAPI" for consistency (#890) 2020-01-18 19:07:42 +01:00
Sebastián Ramírez
a085898309 🔖 Release version 0.47.1 2020-01-18 18:06:47 +01:00
Sebastián Ramírez
3b40c557ce 📝 Update release notes 2020-01-18 18:06:20 +01:00
Sebastián Ramírez
75a07f24bf 🔒 Fix clone field implementation to handle sub-models in response_model (#889) 2020-01-18 18:03:51 +01:00
Sebastián Ramírez
7cea84b74c 🐛 Fix FastAPI serialization of Pydantic ORM mode blocking the event loop (#888) 2020-01-18 17:32:57 +01:00
126 changed files with 420 additions and 219 deletions

View File

@@ -319,7 +319,7 @@ Coming back to the previous code example, **FastAPI** will:
* Without the `None` it would be required (as is the body in the case with `PUT`).
* For `PUT` requests to `/items/{item_id}`, Read the body as JSON:
* Check that it has a required attribute `name` that should be a `str`.
* Check that is has a required attribute `price` that has to be a `float`.
* Check that it has a required attribute `price` that has to be a `float`.
* Check that it has an optional attribute `is_offer`, that should be a `bool`, if present.
* All this would also work for deeply nested JSON objects.
* Convert from and to JSON automatically.

View File

@@ -257,7 +257,7 @@ And now in the file `sql_app/main.py` let's integrate and use all the other part
In a very simplistic way create the database tables:
```Python hl_lines="10 11 12"
```Python hl_lines="9 10 11"
{!./src/sql_databases_peewee/sql_app/main.py!}
```
@@ -265,7 +265,7 @@ In a very simplistic way create the database tables:
Create a dependency that will connect the database right at the beginning of a request and disconnect it at the end:
```Python hl_lines="19 20 21 22 23 24 25"
```Python hl_lines="23 24 25 26 27 28 29"
{!./src/sql_databases_peewee/sql_app/main.py!}
```
@@ -273,58 +273,52 @@ Here we have an empty `yield` because we are actually not using the database obj
It is connecting to the database and storing the connection data in an internal variable that is independent for each request (using the `contextvars` tricks from above).
Because the database connection is potentially I/O blocking, this dependency is created with a normal `def` function.
And then, in each *path operation function* that needs to access the database we add it as a dependency.
But we are not using the value given by this dependency (it actually doesn't give any value, as it has an empty `yield`). So, we don't add it to the *path operation function* but to the *path operation decorator* in the `dependencies` parameter:
```Python hl_lines="36 44 51 63 69 76"
```Python hl_lines="32 40 47 59 65 72"
{!./src/sql_databases_peewee/sql_app/main.py!}
```
### Context Variable Middleware
### Context variable sub-dependency
For all the `contextvars` parts to work, we need to make sure there's a new "context" each time there's a new request, so that we have a specific context variable Peewee can use to save its state (database connection, transactions, etc).
For all the `contextvars` parts to work, we need to make sure we have an independent value in the `ContextVar` for each request that uses the database, and that value will be used as the database state (connection, transactions, etc) for the whole request.
For that, we need to create a middleware.
For that, we need to create another `async` dependency `reset_db_state()` that is used as a sub-dependency in `get_db()`. It will set the value for the context variable (with just a default `dict`) that will be used as the database state for the whole request. And then the dependency `get_db()` will store in it the database state (connection, transactions, etc).
Right before the request, we are going to reset the database state. We will "set" a value to the context variable and then we will ask the Peewee database state to "reset" (this will create the default values it uses).
And then the rest of the request is processed with that new context variable we just set, all automatically and more or less "magically".
For the **next request**, as we will reset that context variable again in the middleware, that new request will have its own database state (connection, transactions, etc).
```Python hl_lines="28 29 30 31 32 33"
```Python hl_lines="18 19 20"
{!./src/sql_databases_peewee/sql_app/main.py!}
```
For the **next request**, as we will reset that context variable again in the `async` dependency `reset_db_state()` and then create a new connection in the `get_db()` dependency, that new request will have its own database state (connection, transactions, etc).
!!! tip
As FastAPI is an async framework, one request could start being processed, and before finishing, another request could be received and start processing as well, and it all could be processed in the same thread.
But context variables are aware of these async features, so, a Peewee database state set in the middleware will keep its own data throughout the entire request.
But context variables are aware of these async features, so, a Peewee database state set in the `async` dependency `reset_db_state()` will keep its own data throughout the entire request.
And at the same time, the other concurrent request will have its own database state that will be independent for the whole request.
#### Peewee Proxy
If you are using a <a href="http://docs.peewee-orm.com/en/latest/peewee/database.html#dynamically-defining-a-database" class="external-link" target="_blank">Peewee Proxy</a>, the actual database is at `db.obj`.
So, you would reset it with:
```Python hl_lines="3 4"
@app.middleware("http")
async def reset_db_middleware(request: Request, call_next):
async def reset_db_state():
database.db.obj._state._state.set(db_state_default.copy())
database.db.obj._state.reset()
response = await call_next(request)
return response
```
### Create your **FastAPI** *path operations*
Now, finally, here's the standard **FastAPI** *path operations* code.
```Python hl_lines="36 37 38 39 40 41 44 45 46 47 50 51 52 53 54 55 56 57 60 61 62 63 64 65 66 69 70 71 72 75 76 77 78 79 80 81 82 83"
```Python hl_lines="32 33 34 35 36 37 40 41 42 43 46 47 48 49 50 51 52 53 56 57 58 59 60 61 62 65 66 67 68 71 72 73 74 75 76 77 78 79"
{!./src/sql_databases_peewee/sql_app/main.py!}
```
@@ -364,15 +358,13 @@ If you want to check how Peewee would break your app if used without modificatio
# db._state = PeeweeConnectionState()
```
And in the file `sql_app/main.py` file, comment the middleware:
And in the file `sql_app/main.py` file, comment the body of the `async` dependency `reset_db_state()` and replace it with a `pass`:
```Python
# @app.middleware("http")
# async def reset_db_middleware(request: Request, call_next):
async def reset_db_state():
# database.db._state._state.set(db_state_default.copy())
# database.db._state.reset()
# response = await call_next(request)
# return response
pass
```
Then run your app with Uvicorn:
@@ -391,11 +383,11 @@ The tabs will wait for a bit and then some of them will show `Internal Server Er
### What happens
The first tab will make your app create a connection to the database and wait for some seconds before replying back and closing the connection.
The first tab will make your app create a connection to the database and wait for some seconds before replying back and closing the database connection.
Then, for the request in the next tab, your app will wait for one second less, and so on.
This means that it will end up finishing some of the last tabs' requests than some of the previous ones.
This means that it will end up finishing some of the last tabs' requests earlier than some of the previous ones.
Then one the last requests that wait less seconds will try to open a database connection, but as one of those previous requests for the other tabs will probably be handled in the same thread as the first one, it will have the same database connection that is already open, and Peewee will throw an error and you will see it in the terminal, and the response will have an `Internal Server Error`.
@@ -413,15 +405,12 @@ Now go back to the file `sql_app/database.py`, and uncomment the line:
db._state = PeeweeConnectionState()
```
And in the file `sql_app/main.py` file, uncomment the middleware:
And in the file `sql_app/main.py` file, uncomment the body of the `async` dependency `reset_db_state()`:
```Python
@app.middleware("http")
async def reset_db_middleware(request: Request, call_next):
async def reset_db_state():
database.db._state._state.set(db_state_default.copy())
database.db._state.reset()
response = await call_next(request)
return response
```
Terminate your running app and start it again.
@@ -477,11 +466,11 @@ Repeat the same process with the 10 tabs. This time all of them will wait and yo
Peewee uses <a href="https://docs.python.org/3/library/threading.html#thread-local-data" class="external-link" target="_blank">`threading.local`</a> by default to store it's database "state" data (connection, transactions, etc).
`threading.local` creates a value exclusive to the current thread, but an async framework would run all the "tasks" (e.g. requests) in the same thread, and possibly not in order.
`threading.local` creates a value exclusive to the current thread, but an async framework would run all the code (e.g. for each request) in the same thread, and possibly not in order.
On top of that, an async framework could run some sync code in a threadpool (using `asyncio.run_in_executor`), but belonging to the same "task" (e.g. to the same request).
On top of that, an async framework could run some sync code in a threadpool (using `asyncio.run_in_executor`), but belonging to the same request.
This means that, with Peewee's current implementation, multiple tasks could be using the same `threading.local` variable and end up sharing the same connection and data, and at the same time, if they execute sync IO-blocking code in a threadpool (as with normal `def` functions in FastAPI, in *path operations* and dependencies), that code won't have access to the database state variables, even while it's part of the same "task" (request) and it should be able to get access to that.
This means that, with Peewee's current implementation, multiple tasks could be using the same `threading.local` variable and end up sharing the same connection and data (that they shouldn't), and at the same time, if they execute sync I/O-blocking code in a threadpool (as with normal `def` functions in FastAPI, in *path operations* and dependencies), that code won't have access to the database state variables, even while it's part of the same request and it should be able to get access to the same database state.
### Context variables
@@ -489,46 +478,44 @@ Python 3.7 has <a href="https://docs.python.org/3/library/contextvars.html" clas
There are several things to have in mind.
The `ContextVar` has to be created at the top of the module, like `some_var = ContextVar("some_var", default="default value")`.
The `ContextVar` has to be created at the top of the module, like:
To set a value used in the current "context" (e.g. for the current request) use `some_var.set("new value")`.
```Python
some_var = ContextVar("some_var", default="default value")
```
To get a value anywhere inside of the context (e.g. in any part handling the current request) use `some_var.get()`.
To set a value used in the current "context" (e.g. for the current request) use:
### Set context variables in middleware
```Python
some_var.set("new value")
```
If some part of the async code sets the value with `some_var.set("updated in function")` (e.g. the middleware), the rest of the code in it will see that new value.
To get a value anywhere inside of the context (e.g. in any part handling the current request) use:
And if it calls any other function with `await some_function()` (e.g. `response = await call_next(request)` in our middleware) that internal `some_function()` (or `response = await call_next(request)` in our example) and everything it calls inside, will see that same new value `"updated in function"`.
```Python
some_var.get()
```
So, in our case, if we set the Peewee state variable in the middleware and then call `response = await call_next(request)` all the rest of the internal code in our app (that is called by `call_next()`) will see this value we set in the middleware and will be able to reuse it.
### Set context variables in the `async` dependency `reset_db_state()`
But if the value is set in an internal function (e.g. in `get_db()`) that value will be seen only by that internal function and any code it calls, not by the parent function nor by any sibling function. So, we can't set the Peewee database state in `get_db()`, or the *path operation functions* wouldn't see the new Peewee database state for that "context".
If some part of the async code sets the value with `some_var.set("updated in function")` (e.g. like the `async` dependency), the rest of the code in it and the code that goes after (including code inside of `async` functions called with `await`) will see that new value.
### But `get_db` is an async context manager
So, in our case, if we set the Peewee state variable (with a default `dict`) in the `async` dependency, all the rest of the internal code in our app will see this value and will be able to reuse it for the whole request.
You might be thinking that `get_db()` is actually not used as a function, it's converted to a context manager.
And the context variable would be set again for the next request, even if they are concurrent.
So the *path operation function* is part of it.
### Set database state in the dependency `get_db()`
But the code after the `yield`, in the `finally` is not executed in the same "context".
As `get_db()` is a normal `def` function, **FastAPI** will make it run in a threadpool, with a *copy* of the "context", holding the same value for the context variable (the `dict` with the reset database state). Then it can add database state to that `dict`, like the connection, etc.
So, if you reset the state in `get_db()`, the *path operation function* would see the database connection set there. But the `finally` block would not see the same context variable value, and so, as the database object would not have the same context variable for its state, it would not have the same connection, so you couldn't close it in the `finally` in `get_db()` after the request is done.
But if the value of the context variable (the default `dict`) was set in that normal `def` function, it would create a new value that would stay only in that thread of the threadpool, and the rest of the code (like the *path operation functions*) wouldn't have access to it. In `get_db()` we can only set values in the `dict`, but not the entire `dict` itself.
In the middleware we are setting the Peewee state to a context variable that holds a `dict`. So, it's set for every new request.
So, we need to have the `async` dependency `reset_db_state()` to set the `dict` in the context variable. That way, all the code has access to the same `dict` for the database state for a single request.
And as the database state variables are stored inside of that `dict` instead of new context variables, when Peewee sets the new database state (connection, transactions, etc) in any part of the internal code, underneath, all that will be set as keys in that `dict`. But the `dict` would still be the same we set in the middleware. That's what allows the `get_db()` dependency to make Peewee create a new connection (that is stored in that `dict`) and allows the `finally` block to still have access to the same connection.
### Connect and disconnect in the dependency `get_db()`
Because the context variable is set outside all that, in the middleware.
Then the next question would be, why not just connect and disconnect the database in the `async` dependency itself, instead of in `get_db()`?
### Connect and disconnect in dependency
The `async` dependency has to be `async` for the context variable to be preserved for the rest of the request, but creating and closing the database connection is potentially blocking, so it could degrade performance if it was there.
Then the next question would be, why not just connect and disconnect the database in the middleware itself, instead of `get_db()`?
First, the middleware has to be `async`, and creating and closing the database connection is potentially blocking, so it could degrade performance.
But more importantly, the middleware returns a `response`, and this `response` is actually an awaitable function that will do all the work in your code, including background tasks.
If you closed the connection in the middleware right before returning the `response`, some of your code would not have the chance to use the database connection set in the context variable.
Because some other code will call that `response` with `await response(...)`. And inside of that `await response(...)` is that, for example, background tasks are run. But if the connection was already closed before `response` is awaited, then it won't be able to access it.
So we also need the normal `def` dependency `get_db()`.

View File

@@ -229,7 +229,7 @@ It was one of the first extremely fast Python frameworks based on `asyncio`. It
!!! note "Technical Details"
It used <a href="https://github.com/MagicStack/uvloop" class="external-link" target="_blank">`uvloop`</a> instead of the default Python `asyncio` loop. That's what made it so fast.
It <a href="https://github.com/huge-success/sanic/issues/761" class="external-link" target="_blank">still doesn't implement the ASGI spec for Python asynchronous web development</a>, but it clearly inspired Uvicorn and Starlette, that are currently faster than Sanic in open benchmarks.
It clearly inspired Uvicorn and Starlette, that are currently faster than Sanic in open benchmarks.
!!! check "Inspired **FastAPI** to"
Find a way to have a crazy performance.

View File

@@ -144,7 +144,7 @@ You go with your crush to get parallel fast food.
You stand in line while several (let's say 8) cashiers take the orders from the people in front of you.
Everyone before you is waiting for their burgers to be ready before leaving the counter because each of the 8 cashiers goes himself and preparers the burger right away before getting the next order.
Everyone before you is waiting for their burgers to be ready before leaving the counter because each of the 8 cashiers goes himself and prepares the burger right away before getting the next order.
Then it's finally your turn, you place your order of 2 very fancy burgers for your crush and you.

View File

@@ -53,6 +53,10 @@ Here's an incomplete list of some of them.
* <a href="https://medium.com/@arthur393/another-boilerplate-to-fastapi-azure-pipeline-ci-pytest-3c8d9a4be0bb" class="external-link" target="_blank">Another Boilerplate to FastAPI: Azure Pipeline CI + Pytest</a> by <a href="https://twitter.com/arthurheinrique" class="external-link" target="_blank">Arthur Henrique</a>.
* <a href="https://iwpnd.pw/articles/2020-01/deploy-fastapi-to-aws-lambda" class="external-link" target="_blank">How to continuously deploy a FastAPI to AWS Lambda with AWS SAM</a> by <a href="https://iwpnd.pw" class="external-link" target="_blank">Benjamin Ramser</a>.
* <a href="https://www.tutlinks.com/create-and-deploy-fastapi-app-to-heroku/" class="external-link" target="_blank">Create and Deploy FastAPI app to Heroku without using Docker</a> by <a href="https://www.linkedin.com/in/navule/" class="external-link" target="_blank">Navule Pavan Kumar Rao</a>.
### Japanese
* <a href="https://qiita.com/mtitg/items/47770e9a562dd150631d" class="external-link" target="_blank">FastAPIDB接続してCRUDするPython製APIサーバーを構築</a> by <a href="https://qiita.com/mtitg" class="external-link" target="_blank">@mtitg</a>.

View File

@@ -319,7 +319,7 @@ Coming back to the previous code example, **FastAPI** will:
* Without the `None` it would be required (as is the body in the case with `PUT`).
* For `PUT` requests to `/items/{item_id}`, Read the body as JSON:
* Check that it has a required attribute `name` that should be a `str`.
* Check that is has a required attribute `price` that has to be a `float`.
* Check that it has a required attribute `price` that has to be a `float`.
* Check that it has an optional attribute `is_offer`, that should be a `bool`, if present.
* All this would also work for deeply nested JSON objects.
* Convert from and to JSON automatically.

View File

@@ -1,5 +1,27 @@
## Latest changes
## 0.48.0
* Run linters first in tests to error out faster. PR [#948](https://github.com/tiangolo/fastapi/pull/948).
* Log warning about `email-validator` only when used. PR [#946](https://github.com/tiangolo/fastapi/pull/946).
* Simplify [Peewee docs](https://fastapi.tiangolo.com/advanced/sql-databases-peewee/) with double dependency with `yield`. PR [#947](https://github.com/tiangolo/fastapi/pull/947).
* Add article [External Links](https://fastapi.tiangolo.com/external-links/): [Create and Deploy FastAPI app to Heroku](https://www.tutlinks.com/create-and-deploy-fastapi-app-to-heroku/). PR [#942](https://github.com/tiangolo/fastapi/pull/942) by [@windson](https://github.com/windson).
* Update description of Sanic, as it is now ASGI too. PR [#932](https://github.com/tiangolo/fastapi/pull/932) by [@raphaelauv](https://github.com/raphaelauv).
* Fix typo in main page. PR [#920](https://github.com/tiangolo/fastapi/pull/920) by [@mMarzeta](https://github.com/mMarzeta).
* Fix parsing of possibly invalid bodies. PR [#918](https://github.com/tiangolo/fastapi/pull/918) by [@dmontagu](https://github.com/dmontagu).
* Fix typo [#916](https://github.com/tiangolo/fastapi/pull/916) by [@adursun](https://github.com/adursun).
* Allow `Any` type for enums in OpenAPI. PR [#906](https://github.com/tiangolo/fastapi/pull/906) by [@songzhi](https://github.com/songzhi).
* Add article to [External Links](https://fastapi.tiangolo.com/external-links/): [How to continuously deploy a FastAPI to AWS Lambda with AWS SAM](https://iwpnd.pw/articles/2020-01/deploy-fastapi-to-aws-lambda). PR [#901](https://github.com/tiangolo/fastapi/pull/901) by [@iwpnd](https://github.com/iwpnd).
* Add note about using Body parameters without Pydantic. PR [#900](https://github.com/tiangolo/fastapi/pull/900) by [@pawamoy](https://github.com/pawamoy).
* Fix Pydantic field clone logic. PR [#899](https://github.com/tiangolo/fastapi/pull/899) by [@deuce2367](https://github.com/deuce2367).
* Fix link in middleware docs. PR [#893](https://github.com/tiangolo/fastapi/pull/893) by [@linchiwei123](https://github.com/linchiwei123).
* Rename default API title from "Fast API" to "FastAPI" for consistency. PR [#890](https://github.com/tiangolo/fastapi/pull/890).
## 0.47.1
* Fix model filtering in `response_model`, cloning sub-models. PR [#889](https://github.com/tiangolo/fastapi/pull/889).
* Fix FastAPI serialization of Pydantic models using ORM mode blocking the event loop. PR [#888](https://github.com/tiangolo/fastapi/pull/888).
## 0.47.0
* Refactor documentation to make a simpler and shorter [Tutorial - User Guide](https://fastapi.tiangolo.com/tutorial/) and an additional [Advanced User Guide](https://fastapi.tiangolo.com/advanced/) with all the additional docs. PR [#887](https://github.com/tiangolo/fastapi/pull/887).

View File

@@ -2,7 +2,6 @@ import time
from typing import List
from fastapi import Depends, FastAPI, HTTPException
from starlette.requests import Request
from . import crud, database, models, schemas
from .database import db_state_default
@@ -16,7 +15,12 @@ app = FastAPI()
sleep_time = 10
def get_db():
async def reset_db_state():
database.db._state._state.set(db_state_default.copy())
database.db._state.reset()
def get_db(db_state=Depends(reset_db_state)):
try:
database.db.connect()
yield
@@ -25,14 +29,6 @@ def get_db():
database.db.close()
@app.middleware("http")
async def reset_db_middleware(request: Request, call_next):
database.db._state._state.set(db_state_default.copy())
database.db._state.reset()
response = await call_next(request)
return response
@app.post("/users/", response_model=schemas.User, dependencies=[Depends(get_db)])
def create_user(user: schemas.UserCreate):
db_user = crud.get_user_by_email(email=user.email)

View File

@@ -139,3 +139,7 @@ The function parameters will be recognized as follows:
* If the parameter is also declared in the **path**, it will be used as a path parameter.
* If the parameter is of a **singular type** (like `int`, `float`, `str`, `bool`, etc) it will be interpreted as a **query** parameter.
* If the parameter is declared to be of the type of a **Pydantic model**, it will be interpreted as a request **body**.
## Without Pydantic
If you don't want to use Pydantic models, you can also use **Body** parameters. See the docs for [Body - Multiple Parameters: Singular values in body](body-multiple-params.md#singular-values-in-body){.internal-link target=_blank}.

View File

@@ -92,7 +92,7 @@ It will show a JSON starting with something like:
{
"openapi": "3.0.2",
"info": {
"title": "Fast API",
"title": "FastAPI",
"version": "0.1.0"
},
"paths": {

View File

@@ -28,8 +28,7 @@ The middleware function receives:
!!! tip
Have in mind that custom proprietary headers can be added <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers" class="external-link" 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 CORS configurations ([CORS (Cross-Origin Resource Sharing)
](cors.md){.internal-link target=_blank}) using the parameter `expose_headers` documented in <a href="https://www.starlette.io/middleware/#corsmiddleware" class="external-link" target="_blank">Starlette's CORS docs</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 CORS configurations ([CORS (Cross-Origin Resource Sharing)](cors.md){.internal-link target=_blank}) using the parameter `expose_headers` documented in <a href="https://www.starlette.io/middleware/#corsmiddleware" class="external-link" target="_blank">Starlette's CORS docs</a>.
### Before and after the `response`

View File

@@ -1,6 +1,6 @@
"""FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
__version__ = "0.47.0"
__version__ = "0.48.0"
from starlette.background import BackgroundTasks

View File

@@ -32,7 +32,7 @@ class FastAPI(Starlette):
debug: bool = False,
routes: List[BaseRoute] = None,
template_directory: str = None,
title: str = "Fast API",
title: str = "FastAPI",
description: str = "",
version: str = "0.1.0",
openapi_url: Optional[str] = "/openapi.json",

View File

@@ -634,7 +634,11 @@ async def request_body_to_args(
) and isinstance(received_body, FormData):
value = received_body.getlist(field.alias)
else:
value = received_body.get(field.alias)
try:
value = received_body.get(field.alias)
except AttributeError:
errors.append(get_missing_field_error(field.alias))
continue
if (
value is None
or (isinstance(field_info, params.Form) and value == "")
@@ -645,18 +649,7 @@ async def request_body_to_args(
)
):
if field.required:
if PYDANTIC_1:
errors.append(
ErrorWrapper(MissingError(), loc=("body", field.alias))
)
else: # pragma: nocover
errors.append(
ErrorWrapper( # type: ignore
MissingError(),
loc=("body", field.alias),
config=BaseConfig,
)
)
errors.append(get_missing_field_error(field.alias))
else:
values[field.name] = deepcopy(field.default)
continue
@@ -685,6 +678,16 @@ async def request_body_to_args(
return values, errors
def get_missing_field_error(field_alias: str) -> ErrorWrapper:
if PYDANTIC_1:
missing_field_error = ErrorWrapper(MissingError(), loc=("body", field_alias))
else: # pragma: no cover
missing_field_error = ErrorWrapper( # type: ignore
MissingError(), loc=("body", field_alias), config=BaseConfig,
)
return missing_field_error
def get_schema_compatible_field(*, field: ModelField) -> ModelField:
out_field = field
if lenient_issubclass(field.type_, UploadFile):

View File

@@ -1,5 +1,5 @@
from enum import Enum
from typing import Any, Dict, List, Optional, Union
from typing import Any, Callable, Dict, Iterable, List, Optional, Union
from fastapi.logger import logger
from pydantic import BaseModel
@@ -21,13 +21,19 @@ try:
# TODO: remove when removing support for Pydantic < 1.0.0
from pydantic.types import EmailStr # type: ignore
except ImportError: # pragma: no cover
logger.info(
"email-validator not installed, email fields will be treated as str.\n"
"To install, run: pip install email-validator"
)
class EmailStr(str): # type: ignore
pass
@classmethod
def __get_validators__(cls) -> Iterable[Callable]:
yield cls.validate
@classmethod
def validate(cls, v: Any) -> str:
logger.warning(
"email-validator not installed, email fields will be treated as str.\n"
"To install, run: pip install email-validator"
)
return str(v)
class Contact(BaseModel):
@@ -101,7 +107,7 @@ class SchemaBase(BaseModel):
maxProperties: Optional[int] = Field(None, gte=0)
minProperties: Optional[int] = Field(None, gte=0)
required: Optional[List[str]] = None
enum: Optional[List[str]] = None
enum: Optional[List[Any]] = None
type: Optional[str] = None
allOf: Optional[List[Any]] = None
oneOf: Optional[List[Any]] = None

View File

@@ -47,7 +47,7 @@ except ImportError: # pragma: nocover
from pydantic.fields import Field as ModelField # type: ignore
def serialize_response(
async def serialize_response(
*,
field: ModelField = None,
response: Response,
@@ -55,6 +55,7 @@ def serialize_response(
exclude: Union[SetIntStr, DictIntStrAny] = set(),
by_alias: bool = True,
exclude_unset: bool = False,
is_coroutine: bool = True,
) -> Any:
if field:
errors = []
@@ -63,7 +64,12 @@ def serialize_response(
response = response.dict(exclude_unset=exclude_unset)
else:
response = response.dict(skip_defaults=exclude_unset) # pragma: nocover
value, errors_ = field.validate(response, {}, loc=("response",))
if is_coroutine:
value, errors_ = field.validate(response, {}, loc=("response",))
else:
value, errors_ = await run_in_threadpool(
field.validate, response, {}, loc=("response",)
)
if isinstance(errors_, ErrorWrapper):
errors.append(errors_)
elif isinstance(errors_, list):
@@ -131,13 +137,14 @@ def get_request_handler(
if raw_response.background is None:
raw_response.background = background_tasks
return raw_response
response_data = serialize_response(
response_data = await serialize_response(
field=response_field,
response=raw_response,
include=response_model_include,
exclude=response_model_exclude,
by_alias=response_model_by_alias,
exclude_unset=response_model_exclude_unset,
is_coroutine=is_coroutine,
)
response = response_class(
content=response_data,

View File

@@ -93,12 +93,9 @@ def create_cloned_field(field: ModelField) -> ModelField:
use_type = original_type
if lenient_issubclass(original_type, BaseModel):
original_type = cast(Type[BaseModel], original_type)
use_type = create_model(
original_type.__name__, __config__=original_type.__config__
)
use_type = create_model(original_type.__name__, __base__=original_type)
for f in original_type.__fields__.values():
use_type.__fields__[f.name] = f
use_type.__validators__ = original_type.__validators__
use_type.__fields__[f.name] = create_cloned_field(f)
if PYDANTIC_1:
new_field = ModelField(
name=field.name,

View File

@@ -7,9 +7,8 @@ set -x
if [ -f ./test.db ]; then
rm ./test.db
fi
export PYTHONPATH=./docs/src
pytest --cov=fastapi --cov=tests --cov=docs/src --cov-report=term-missing ${@}
bash ./scripts/lint.sh
# Check README.md is up to date
diff --brief docs/index.md README.md
export PYTHONPATH=./docs/src
pytest --cov=fastapi --cov=tests --cov=docs/src --cov-report=term-missing ${@}

View File

@@ -21,7 +21,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/foo": {
"post": {

View File

@@ -20,7 +20,7 @@ app.include_router(router)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/": {
"get": {

View File

@@ -12,7 +12,7 @@ async def a():
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/a": {
"get": {

View File

@@ -32,7 +32,7 @@ async def a(id):
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/a/{id}": {
"get": {

View File

@@ -11,7 +11,7 @@ async def a(id):
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/a/{id}": {
"get": {

View File

@@ -37,7 +37,7 @@ async def b():
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/a": {
"get": {

View File

@@ -37,7 +37,7 @@ app.include_router(router)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/a": {
"get": {

View File

@@ -7,7 +7,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/api_route": {
"get": {

View File

@@ -47,7 +47,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/a/": {
"get": {

View File

@@ -52,7 +52,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/{item_id}": {
"get": {

View File

@@ -0,0 +1,153 @@
import pytest
from fastapi import Depends, FastAPI
from pydantic import BaseModel, ValidationError, validator
from starlette.testclient import TestClient
app = FastAPI()
class ModelB(BaseModel):
username: str
class ModelC(ModelB):
password: str
class ModelA(BaseModel):
name: str
description: str = None
model_b: ModelB
@validator("name")
def lower_username(cls, name: str, values):
if not name.endswith("A"):
raise ValueError("name must end in A")
return name
async def get_model_c() -> ModelC:
return ModelC(username="test-user", password="test-password")
@app.get("/model/{name}", response_model=ModelA)
async def get_model_a(name: str, model_c=Depends(get_model_c)):
return {"name": name, "description": "model-a-desc", "model_b": model_c}
client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/model/{name}": {
"get": {
"summary": "Get Model A",
"operationId": "get_model_a_model__name__get",
"parameters": [
{
"required": True,
"schema": {"title": "Name", "type": "string"},
"name": "name",
"in": "path",
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {"$ref": "#/components/schemas/ModelA"}
}
},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
}
}
},
"components": {
"schemas": {
"HTTPValidationError": {
"title": "HTTPValidationError",
"type": "object",
"properties": {
"detail": {
"title": "Detail",
"type": "array",
"items": {"$ref": "#/components/schemas/ValidationError"},
}
},
},
"ModelA": {
"title": "ModelA",
"required": ["name", "model_b"],
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},
"description": {"title": "Description", "type": "string"},
"model_b": {"$ref": "#/components/schemas/ModelB"},
},
},
"ModelB": {
"title": "ModelB",
"required": ["username"],
"type": "object",
"properties": {"username": {"title": "Username", "type": "string"}},
},
"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"},
},
},
}
},
}
def test_openapi_schema():
response = client.get("/openapi.json")
assert response.status_code == 200
assert response.json() == openapi_schema
def test_filter_sub_model():
response = client.get("/model/modelA")
assert response.status_code == 200
assert response.json() == {
"name": "modelA",
"description": "model-a-desc",
"model_b": {"username": "test-user"},
}
def test_validator_is_cloned():
with pytest.raises(ValidationError) as err:
client.get("/model/modelX")
assert err.value.errors() == [
{
"loc": ("response", "name"),
"msg": "name must end in A",
"type": "value_error",
}
]

View File

@@ -6,7 +6,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/a/compute": {
"post": {

View File

@@ -23,7 +23,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/": {
"post": {

View File

@@ -16,7 +16,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/": {
"get": {

View File

@@ -14,7 +14,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/{item_id}": {
"put": {

View File

@@ -50,7 +50,7 @@ schema = {
},
}
},
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"openapi": "3.0.2",
"paths": {
"/": {

View File

@@ -37,7 +37,7 @@ async def b():
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/a": {
"get": {

View File

@@ -38,7 +38,7 @@ async def b():
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/a": {
"get": {

View File

@@ -26,7 +26,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/users/me": {
"get": {

View File

@@ -33,7 +33,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/users/me": {
"get": {

View File

@@ -26,7 +26,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/users/me": {
"get": {

View File

@@ -32,7 +32,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/users/me": {
"get": {

View File

@@ -26,7 +26,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/users/me": {
"get": {

View File

@@ -32,7 +32,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/users/me": {
"get": {

View File

@@ -16,7 +16,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/users/me": {
"get": {

View File

@@ -22,7 +22,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/users/me": {
"get": {

View File

@@ -22,7 +22,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/users/me": {
"get": {

View File

@@ -19,7 +19,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/users/me": {
"get": {

View File

@@ -16,7 +16,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/users/me": {
"get": {

View File

@@ -22,7 +22,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/users/me": {
"get": {

View File

@@ -16,7 +16,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/users/me": {
"get": {

View File

@@ -22,7 +22,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/users/me": {
"get": {

View File

@@ -43,7 +43,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/login": {
"post": {

View File

@@ -20,7 +20,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/": {
"get": {

View File

@@ -47,7 +47,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/login": {
"post": {

View File

@@ -20,7 +20,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/": {
"get": {

View File

@@ -26,7 +26,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/users/me": {
"get": {

View File

@@ -32,7 +32,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/users/me": {
"get": {

View File

@@ -29,7 +29,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/{item_id}": {
"get": {

View File

@@ -62,7 +62,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/invoices/": {
"post": {

View File

@@ -6,7 +6,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/{item_id}": {
"get": {

View File

@@ -9,7 +9,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/{item_id}": {
"get": {

View File

@@ -6,7 +6,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/{item_id}": {
"get": {

View File

@@ -9,7 +9,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/{item_id}": {
"get": {

View File

@@ -4,7 +4,7 @@ from async_sql_databases.tutorial001 import app
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/notes/": {
"get": {

View File

@@ -7,7 +7,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/users/": {
"get": {

View File

@@ -7,7 +7,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/": {
"post": {

View File

@@ -17,7 +17,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/{item_id}": {
"put": {

View File

@@ -7,7 +7,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/{item_id}": {
"put": {

View File

@@ -7,7 +7,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/{item_id}": {
"put": {
@@ -166,6 +166,30 @@ def test_openapi_schema():
]
},
),
(
"/items/5",
[],
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):

View File

@@ -6,7 +6,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/index-weights/": {
"post": {

View File

@@ -6,7 +6,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/{item_id}": {
"get": {

View File

@@ -7,7 +7,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/": {
"get": {

View File

@@ -6,7 +6,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/": {
"get": {

View File

@@ -6,7 +6,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/": {
"get": {

View File

@@ -7,7 +7,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/": {
"get": {

View File

@@ -7,7 +7,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/": {
"get": {

View File

@@ -6,7 +6,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/": {
"get": {

View File

@@ -4,7 +4,7 @@ from events.tutorial001 import app
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/{item_id}": {
"get": {

View File

@@ -4,7 +4,7 @@ from events.tutorial002 import app
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/": {
"get": {

View File

@@ -7,7 +7,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/{item_id}": {
"put": {

View File

@@ -6,7 +6,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/{item_id}": {
"get": {

View File

@@ -6,7 +6,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/": {
"get": {

View File

@@ -6,7 +6,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/keyword-weights/": {
"get": {

View File

@@ -7,7 +7,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/": {
"get": {

View File

@@ -6,7 +6,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/{item_id}": {
"get": {

View File

@@ -6,7 +6,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items-header/{item_id}": {
"get": {

View File

@@ -6,7 +6,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/unicorns/{name}": {
"get": {

View File

@@ -6,7 +6,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/{item_id}": {
"get": {

View File

@@ -6,7 +6,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/": {
"post": {

View File

@@ -6,7 +6,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/{item_id}": {
"get": {

View File

@@ -8,7 +8,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/": {
"get": {

View File

@@ -6,7 +6,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/invoices/": {
"post": {

View File

@@ -6,7 +6,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/": {
"get": {

View File

@@ -6,7 +6,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/": {
"get": {

View File

@@ -6,7 +6,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {},
}

View File

@@ -6,7 +6,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/": {
"post": {

View File

@@ -6,7 +6,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/": {
"post": {

View File

@@ -7,7 +7,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/": {
"get": {

View File

@@ -6,7 +6,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/files/{file_path}": {
"get": {

View File

@@ -7,7 +7,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/model/{model_name}": {
"get": {

View File

@@ -7,7 +7,7 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/{item_id}": {
"get": {

Some files were not shown because too many files have changed in this diff Show More