Re-export utils from Starlette (#1064)

*  Re-export main features used from Starlette to simplify developer's code

* ♻️ Refactor Starlette exports

* ♻️ Refactor tutorial examples to use re-exported utils from Starlette

* 📝 Add examples for all middlewares

* 📝 Add new docs for middlewares

* 📝 Add examples for custom responses

* 📝 Extend docs for custom responses

* 📝 Update docs and add notes explaining re-exports from Starlette everywhere

* 🍱 Update screenshot for HTTP status

* 🔧 Update MkDocs config with new content

* ♻️ Refactor tests to use re-exported utils from Starlette

*  Re-export WebSocketDisconnect from Starlette for tests

*  Add extra tests for extra re-exported middleware

*  Add tests for re-exported responses from Starlette

*  Add docs about mounting WSGI apps

*  Add Flask as a dependency to test WSGIMiddleware

*  Test WSGIMiddleware example
This commit is contained in:
Sebastián Ramírez
2020-03-01 21:49:20 +01:00
committed by GitHub
parent f2bd2c44e2
commit 0ac9b3ee5c
267 changed files with 1000 additions and 403 deletions

View File

@@ -32,9 +32,9 @@ So, for everything to work correctly, it's better to specify explicitly the allo
## Use `CORSMiddleware`
You can configure it in your **FastAPI** application using Starlette's <a href="https://www.starlette.io/middleware/#corsmiddleware" class="external-link" target="_blank">`CORSMiddleware`</a>.
You can configure it in your **FastAPI** application using the `CORSMiddleware`.
* Import it from Starlette.
* Import `CORSMiddleware`.
* Create a list of allowed origins (as strings).
* Add it as a "middleware" to your **FastAPI** application.
@@ -44,12 +44,39 @@ You can also specify if your backend allows:
* Specific HTTP methods (`POST`, `PUT`) or all of them with the wildcard `"*"`.
* Specific HTTP headers or all of them with the wildcard `"*"`.
```Python hl_lines="2 6 7 8 9 10 11 13 14 15 16 17 18 19"
```Python hl_lines="2 6 7 8 9 10 11 13 14 15 16 17 18 19"
{!./src/cors/tutorial001.py!}
```
The default parameters used by the `CORSMiddleware` implementation are restrictive by default, so you'll need to explicitly enable particular origins, methods, or headers, in order for browsers to be permitted to use them in a Cross-Domain context.
The following arguments are supported:
* `allow_origins` - A list of origins that should be permitted to make cross-origin requests. E.g. `['https://example.org', 'https://www.example.org']`. You can use `['*']` to allow any origin.
* `allow_origin_regex` - A regex string to match against origins that should be permitted to make cross-origin requests. eg. `'https://.*\.example\.org'`.
* `allow_methods` - A list of HTTP methods that should be allowed for cross-origin requests. Defaults to `['GET']`. You can use `['*']` to allow all standard methods.
* `allow_headers` - A list of HTTP request headers that should be supported for cross-origin requests. Defaults to `[]`. You can use `['*']` to allow all headers. The `Accept`, `Accept-Language`, `Content-Language` and `Content-Type` headers are always allowed for CORS requests.
* `allow_credentials` - Indicate that cookies should be supported for cross-origin requests. Defaults to `False`.
* `expose_headers` - Indicate any response headers that should be made accessible to the browser. Defaults to `[]`.
* `max_age` - Sets a maximum time in seconds for browsers to cache CORS responses. Defaults to `60`.
The middleware responds to two particular types of HTTP request...
### CORS preflight requests
These are any `OPTIONS` request with `Origin` and `Access-Control-Request-Method` headers.
In this case the middleware will intercept the incoming request and respond with appropriate CORS headers, and either a `200` or `400` response for informational purposes.
### Simple requests
Any request with an `Origin` header. In this case the middleware will pass the request through as normal, but will include appropriate CORS headers on the response.
## More info
For more details of what you can specify in `CORSMiddleware`, check <a href="https://www.starlette.io/middleware/#corsmiddleware" class="external-link" target="_blank">Starlette's `CORSMiddleware` docs</a>.
For more info about <abbr title="Cross-Origin Resource Sharing">CORS</abbr>, check the <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" class="external-link" target="_blank">Mozilla CORS documentation</a>.
!!! note "Technical Details"
You could also use `from starlette.middleware.cors import CORSMiddleware`.
**FastAPI** provides several middlewares in `fastapi.middleware` just as a convenience for you, the developer. But most of the available middlewares come directly from Starlette.

View File

@@ -90,7 +90,7 @@ And you want to handle this exception globally with FastAPI.
You could add a custom exception handler with `@app.exception_handler()`:
```Python hl_lines="6 7 8 14 15 16 17 18 24"
```Python hl_lines="5 6 7 13 14 15 16 17 18 24"
{!./src/handling_errors/tutorial003.py!}
```
@@ -104,6 +104,11 @@ So, you will receive a clean error, with an HTTP status code of `418` and a JSON
{"message": "Oops! yolo did something. There goes a rainbow..."}
```
!!! note "Technical Details"
You could also use `from starlette.requests import Request` and `from starlette.responses import JSONResponse`.
**FastAPI** provides the same `starlette.responses` as `fastapi.responses` just as a convenience for you, the developer. But most of the available responses come directly from Starlette. The same with `Request`.
## Override the default exception handlers
**FastAPI** has some default exception handlers.
@@ -172,17 +177,22 @@ The same way, you can override the `HTTPException` handler.
For example, you could want to return a plain text response instead of JSON for these errors:
```Python hl_lines="1 3 9 10 11 22"
```Python hl_lines="3 4 9 10 11 22"
{!./src/handling_errors/tutorial004.py!}
```
!!! note "Technical Details"
You could also use `from starlette.responses import PlainTextResponse`.
**FastAPI** provides the same `starlette.responses` as `fastapi.responses` just as a convenience for you, the developer. But most of the available responses come directly from Starlette.
### Use the `RequestValidationError` body
The `RequestValidationError` contains the `body` it received with invalid data.
You could use it while developing your app to log the body and debug it, return it to the user, etc.
```Python hl_lines="16"
```Python hl_lines="14"
{!./src/handling_errors/tutorial005.py!}
```
@@ -231,7 +241,7 @@ So, you can keep raising **FastAPI**'s `HTTPException` as normally in your code.
But when you register an exception handler, you should register it for Starlette's `HTTPException`.
This way, if any part of Starlette's internal code, or a Starlette extension or plug-in, raises an `HTTPException`, your handler will be able to catch and handle it.
This way, if any part of Starlette's internal code, or a Starlette extension or plug-in, raises a Starlette `HTTPException`, your handler will be able to catch and handle it.
In this example, to be able to have both `HTTPException`s in the same code, Starlette's exceptions is renamed to `StarletteHTTPException`:

View File

@@ -9,6 +9,11 @@ A "middleware" is a function that works with every **request** before it is proc
* It can do something to that **response** or run any needed code.
* Then it returns the **response**.
!!! note "Technical Details"
If you have dependencies with `yield`, the exit code will run *after* the middleware.
If there were any background tasks (documented later), they will run *after* all the middleware.
## Create a middleware
To create a middleware you use the decorator `@app.middleware("http")` on top of a function.
@@ -21,7 +26,7 @@ The middleware function receives:
* Then it returns the `response` generated by the corresponding *path operation*.
* You can then modify further the `response` before returning it.
```Python hl_lines="9 10 12 15"
```Python hl_lines="8 9 11 14"
{!./src/middleware/tutorial001.py!}
```
@@ -30,6 +35,11 @@ The middleware function receives:
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>.
!!! note "Technical Details"
You could also use `from starlette.requests import Request`.
**FastAPI** provides it as a convenience for you, the developer. But it comes directly from Starlette.
### Before and after the `response`
You can add code to be run with the `request`, before any *path operation* receives it.
@@ -38,19 +48,12 @@ And also after the `response` is generated, before returning it.
For example, you could add a custom header `X-Process-Time` containing the time in seconds that it took to process the request and generate a response:
```Python hl_lines="11 13 14"
```Python hl_lines="10 12 13"
{!./src/middleware/tutorial001.py!}
```
## Starlette's Middleware
## Other middlewares
You can also add any other <a href="https://www.starlette.io/middleware/" class="external-link" target="_blank">Starlette Middleware</a>.
You can later read more about other middlewares in the [Advanced User Guide: Advanced Middleware](../advanced/middleware.md){.internal-link target=_blank}.
These are classes instead of plain functions.
Including:
* `CORSMiddleware` (described in the next section).
* `GZipMiddleware`.
* `SentryMiddleware`.
* ...and others.
You will read about how to handle <abbr title="Cross-Origin Resource Sharing">CORS</abbr> with a middleware in the next section.

View File

@@ -9,14 +9,19 @@ You can define the (HTTP) `status_code` to be used in the response of your *path
You can pass directly the `int` code, like `404`.
But if you don't remember what each number code is for, you can use the shortcut constants from `starlette`:
But if you don't remember what each number code is for, you can use the shortcut constants in `status`:
```Python hl_lines="5 18"
```Python hl_lines="3 17"
{!./src/path_operation_configuration/tutorial001.py!}
```
That status code will be used in the response and will be added to the OpenAPI schema.
!!! note "Technical Details"
You could also use `from starlette import status`.
**FastAPI** provides the same `starlette.status` as `fastapi.status` just as a convenience for you, the developer. But it comes directly from Starlette.
## Tags
You can add tags to your *path operation*, pass the parameter `tags` with a `list` of `str` (commonly just one `str`):

View File

@@ -132,6 +132,11 @@ You will receive, as declared, a `list` of `bytes` or `UploadFile`s.
So, whenever Swagger UI supports multi-file uploads, or any other tools that supports OpenAPI, they will be compatible with **FastAPI**.
!!! note "Technical Details"
You could also use `from starlette.responses import HTMLResponse`.
**FastAPI** provides the same `starlette.responses` as `fastapi.responses` just as a convenience for you, the developer. But most of the available responses come directly from Starlette.
## Recap
Use `File` to declare files to be uploaded as input parameters (as form data).

View File

@@ -64,9 +64,9 @@ Let's see the previous example again:
But you don't have to memorize what each of these codes mean.
You can use the convenience variables from `starlette.status`.
You can use the convenience variables from `fastapi.status`.
```Python hl_lines="2 7"
```Python hl_lines="1 6"
{!./src/response_status_code/tutorial002.py!}
```
@@ -74,6 +74,11 @@ They are just a convenience, they hold the same number, but that way you can use
<img src="/img/tutorial/response-status-code/image02.png">
!!! note "Technical Details"
You could also use `from starlette import status`.
**FastAPI** provides the same `starlette.status` as `fastapi.status` just as a convenience for you, the developer. But it comes directly from Starlette.
## Changing the default
Later, in the **Advanced User Guide**, you will see how to return a different status code than the default you are declaring here.

View File

@@ -86,7 +86,7 @@ And another utility to verify if a received password matches the hash stored.
And another one to authenticate and return a user.
```Python hl_lines="7 39 56 57 60 61 70 71 72 73 74 75 76"
```Python hl_lines="7 48 55 56 59 60 69 70 71 72 73 74 75"
{!./src/security/tutorial004.py!}
```
@@ -115,7 +115,7 @@ Define a Pydantic Model that will be used in the token endpoint for the response
Create a utility function to generate a new access token.
```Python hl_lines="3 6 13 14 15 29 30 31 79 80 81 82 83 84 85 86 87"
```Python hl_lines="3 6 12 13 14 28 29 30 78 79 80 81 82 83 84 85 86"
{!./src/security/tutorial004.py!}
```
@@ -127,7 +127,7 @@ Decode the received token, verify it, and return the current user.
If the token is invalid, return an HTTP error right away.
```Python hl_lines="90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107"
```Python hl_lines="89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106"
{!./src/security/tutorial004.py!}
```
@@ -137,7 +137,7 @@ Create a `timedelta` with the expiration time of the token.
Create a real JWT access token and return it.
```Python hl_lines="116 117 118 119 120 121 122 123 124 125 126 127 128 129"
```Python hl_lines="115 116 117 118 119 120 121 122 123 124 125 126 127 128"
{!./src/security/tutorial004.py!}
```

View File

@@ -47,7 +47,7 @@ Now let's use the utilities provided by **FastAPI** to handle this.
First, import `OAuth2PasswordRequestForm`, and use it as a dependency with `Depends` for the path `/token`:
```Python hl_lines="2 75"
```Python hl_lines="2 74"
{!./src/security/tutorial003.py!}
```
@@ -88,7 +88,7 @@ If there is no such user, we return an error saying "incorrect username or passw
For the error, we use the exception `HTTPException`:
```Python hl_lines="1 76 77 78"
```Python hl_lines="1 75 76 77"
{!./src/security/tutorial003.py!}
```
@@ -116,7 +116,7 @@ If your database is stolen, the thief won't have your users' plaintext passwords
So, the thief won't be able to try to use those same passwords in another system (as many users use the same password everywhere, this would be dangerous).
```Python hl_lines="79 80 81 82"
```Python hl_lines="78 79 80 81"
{!./src/security/tutorial003.py!}
```
@@ -154,7 +154,7 @@ For this simple example, we are going to just be completely insecure and return
But for now, let's focus on the specific details we need.
```Python hl_lines="84"
```Python hl_lines="83"
{!./src/security/tutorial003.py!}
```
@@ -179,7 +179,7 @@ Both of these dependencies will just return an HTTP error if the user doesn't ex
So, in our endpoint, we will only get a user if the user exists, was correctly authenticated, and is active:
```Python hl_lines="57 58 59 60 61 62 63 64 65 68 69 70 71 88"
```Python hl_lines="56 57 58 59 60 61 62 63 64 65 67 68 69 70 88"
{!./src/security/tutorial003.py!}
```

View File

@@ -626,7 +626,7 @@ A "middleware" is basically a function that is always executed for each request,
The middleware we'll add (just a function) will create a new SQLAlchemy `SessionLocal` for each request, add it to the request and then close it once the request is finished.
```Python hl_lines="16 17 18 19 20 21 22 23 24"
```Python hl_lines="14 15 16 17 18 19 20 21 22"
{!./src/sql_databases/sql_app/alt_main.py!}
```
@@ -639,7 +639,7 @@ The middleware we'll add (just a function) will create a new SQLAlchemy `Session
### About `request.state`
<a href="https://www.starlette.io/requests/#other-state" class="external-link" target="_blank">`request.state` is a property of each Starlette `Request` object</a>. It is there to store arbitrary objects attached to the request itself, like the database session in this case.
`request.state` is a property of each `Request` object. It is there to store arbitrary objects attached to the request itself, like the database session in this case. You can read more about it in <a href="https://www.starlette.io/requests/#other-state" class="external-link" target="_blank">Starlette's docs about `Request` state</a>.
For us in this case, it helps us ensure a single database session is used through all the request, and then closed afterwards (in the middleware).

View File

@@ -1,4 +1,4 @@
You can serve static files automatically from a directory using <a href="https://www.starlette.io/staticfiles/" class="external-link" target="_blank">Starlette's Static Files</a>.
You can serve static files automatically from a directory using `StaticFiles`.
## Install `aiofiles`
@@ -10,13 +10,18 @@ pip install aiofiles
## Use `StaticFiles`
* Import `StaticFiles` from Starlette.
* Import `StaticFiles`.
* "Mount" a `StaticFiles()` instance in a specific path.
```Python hl_lines="2 6"
{!./src/static_files/tutorial001.py!}
```
!!! note "Technical Details"
You could also use `from starlette.staticfiles import StaticFiles`.
**FastAPI** provides the same `starlette.staticfiles` as `fastapi.staticfiles` just as a convenience for you, the developer. But it actually comes directly from Starlette.
### What is "Mounting"
"Mounting" means adding a complete "independent" application in a specific path, that then takes care of handling all the sub-paths.

View File

@@ -1,4 +1,4 @@
Thanks to <a href="https://www.starlette.io/testclient/" class="external-link" target="_blank">Starlette's TestClient</a>, testing **FastAPI** applications is easy and enjoyable.
Thanks to <a href="https://www.starlette.io/testclient/" class="external-link" target="_blank">Starlette</a>, testing **FastAPI** applications is easy and enjoyable.
It is based on <a href="http://docs.python-requests.org" class="external-link" target="_blank">Requests</a>, so it's very familiar and intuitive.
@@ -6,7 +6,7 @@ With it, you can use <a href="https://docs.pytest.org/" class="external-link" ta
## Using `TestClient`
Import `TestClient` from `starlette.testclient`.
Import `TestClient`.
Create a `TestClient` passing to it your **FastAPI**.
@@ -16,7 +16,7 @@ Use the `TestClient` object the same way as you do with `requests`.
Write simple `assert` statements with the standard Python expressions that you need to check (again, standard `pytest`).
```Python hl_lines="2 12 15 16 17 18"
```Python hl_lines="2 12 15 16 17 18"
{!./src/app_testing/tutorial001.py!}
```
@@ -27,6 +27,11 @@ Write simple `assert` statements with the standard Python expressions that you n
This allows you to use `pytest` directly without complications.
!!! note "Technical Details"
You could also use `from starlette.testclient import TestClient`.
**FastAPI** provides the same `starlette.testclient` as `fastapi.testclient` just as a convenience for you, the developer. But it comes directly from Starlette.
## Separating tests
In a real application, you probably would have your tests in a different file.