Skip to content

Commit 0ac9b3e

Browse files
authored
✨ Re-export utils from Starlette (fastapi#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
1 parent f2bd2c4 commit 0ac9b3e

File tree

267 files changed

+1000
-403
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

267 files changed

+1000
-403
lines changed

docs/advanced/additional-status-codes.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
By default, **FastAPI** will return the responses using Starlette's `JSONResponse`, putting the content you return from your *path operation* inside of that `JSONResponse`.
1+
By default, **FastAPI** will return the responses using a `JSONResponse`, putting the content you return from your *path operation* inside of that `JSONResponse`.
22

33
It will use the default status code or the one you set in your *path operation*.
44

@@ -12,7 +12,7 @@ But you also want it to accept new items. And when the items didn't exist before
1212

1313
To achieve that, import `JSONResponse`, and return your content there directly, setting the `status_code` that you want:
1414

15-
```Python hl_lines="2 20"
15+
```Python hl_lines="2 19"
1616
{!./src/additional_status_codes/tutorial001.py!}
1717
```
1818

@@ -23,8 +23,13 @@ To achieve that, import `JSONResponse`, and return your content there directly,
2323

2424
Make sure it has the data you want it to have, and that the values are valid JSON (if you are using `JSONResponse`).
2525

26+
!!! note "Technical Details"
27+
You could also use `from starlette.responses import JSONResponse`.
28+
29+
**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 `status`.
30+
2631
## OpenAPI and API docs
2732

28-
If you return additional status codes and responses directly, they won't be included in the OpenAPI schema (the API docs), because FastAPI doesn't have a way to know before hand what you are going to return.
33+
If you return additional status codes and responses directly, they won't be included in the OpenAPI schema (the API docs), because FastAPI doesn't have a way to know beforehand what you are going to return.
2934

3035
But you can document that in your code, using: [Additional Responses](additional-responses.md){.internal-link target=_blank}.

docs/advanced/advanced-dependencies.md

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,3 @@
1-
!!! warning
2-
This is, more or less, an "advanced" chapter.
3-
4-
If you are just starting with **FastAPI** you might want to skip this chapter and come back to it later.
5-
61
## Parameterized dependencies
72

83
All the dependencies we have seen are a fixed function or class.

docs/advanced/custom-request-and-route.md

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,16 @@ And an `APIRoute` subclass to use that custom request class.
2525

2626
### Create a custom `GzipRequest` class
2727

28+
!!! tip
29+
This is a toy example to demonstrate how it works, if you need Gzip support, you can use the provided [`GzipMiddleware`](./middleware.md#gzipmiddleware){.internal-link target=_blank}.
30+
2831
First, we create a `GzipRequest` class, which will overwrite the `Request.body()` method to decompress the body in the presence of an appropriate header.
2932

3033
If there's no `gzip` in the header, it will not try to decompress the body.
3134

3235
That way, the same route class can handle gzip compressed or uncompressed requests.
3336

34-
```Python hl_lines="10 11 12 13 14 15 16 17"
37+
```Python hl_lines="8 9 10 11 12 13 14 15"
3538
{!./src/custom_request_and_route/tutorial001.py!}
3639
```
3740

@@ -45,7 +48,7 @@ This method returns a function. And that function is what will receive a request
4548

4649
Here we use it to create a `GzipRequest` from the original request.
4750

48-
```Python hl_lines="20 21 22 23 24 25 26 27 28"
51+
```Python hl_lines="18 19 20 21 22 23 24 25 26"
4952
{!./src/custom_request_and_route/tutorial001.py!}
5053
```
5154

@@ -79,26 +82,26 @@ We can also use this same approach to access the request body in an exception ha
7982

8083
All we need to do is handle the request inside a `try`/`except` block:
8184

82-
```Python hl_lines="15 17"
85+
```Python hl_lines="13 15"
8386
{!./src/custom_request_and_route/tutorial002.py!}
8487
```
8588

8689
If an exception occurs, the`Request` instance will still be in scope, so we can read and make use of the request body when handling the error:
8790

88-
```Python hl_lines="18 19 20"
91+
```Python hl_lines="16 17 18"
8992
{!./src/custom_request_and_route/tutorial002.py!}
9093
```
9194

9295
## Custom `APIRoute` class in a router
9396

9497
You can also set the `route_class` parameter of an `APIRouter`:
9598

96-
```Python hl_lines="28"
99+
```Python hl_lines="26"
97100
{!./src/custom_request_and_route/tutorial003.py!}
98101
```
99102

100103
In this example, the *path operations* under the `router` will use the custom `TimedRoute` class, and will have an extra `X-Response-Time` header in the response with the time it took to generate the response:
101104

102-
```Python hl_lines="15 16 17 18 19 20 21 22"
105+
```Python hl_lines="13 14 15 16 17 18 19 20"
103106
{!./src/custom_request_and_route/tutorial003.py!}
104107
```

docs/advanced/custom-response.md

Lines changed: 107 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,8 @@
1-
!!! warning
2-
This is a rather advanced topic.
3-
4-
If you are starting with **FastAPI**, you might not need this.
5-
6-
By default, **FastAPI** will return the responses using Starlette's `JSONResponse`.
1+
By default, **FastAPI** will return the responses using `JSONResponse`.
72

83
You can override it by returning a `Response` directly as seen in [Return a Response directly](response-directly.md){.internal-link target=_blank}.
94

10-
But if you return a `Response` directly, the data won't be automatically converted, and the documentation won't be automatically generated (for example, including the specific "media type", in the HTTP header `Content-Type`).
5+
But if you return a `Response` directly, the data won't be automatically converted, and the documentation won't be automatically generated (for example, including the specific "media type", in the HTTP header `Content-Type` as part of the generated OpenAPI).
116

127
But you can also declare the `Response` that you want to be used, in the *path operation decorator*.
138

@@ -20,17 +15,14 @@ And if that `Response` has a JSON media type (`application/json`), like is the c
2015

2116
## Use `UJSONResponse`
2217

23-
For example, if you are squeezing performance, you can install and use `ujson` and set the response to be Starlette's `UJSONResponse`.
18+
For example, if you are squeezing performance, you can install and use `ujson` and set the response to be `UJSONResponse`.
2419

2520
Import the `Response` class (sub-class) you want to use and declare it in the *path operation decorator*.
2621

2722
```Python hl_lines="2 7"
2823
{!./src/custom_response/tutorial001.py!}
2924
```
3025

31-
!!! note
32-
Notice that you import it directly from `starlette.responses`, not from `fastapi`.
33-
3426
!!! info
3527
The parameter `response_class` will also be used to define the "media type" of the response.
3628

@@ -49,17 +41,14 @@ To return a response with HTML directly from **FastAPI**, use `HTMLResponse`.
4941
{!./src/custom_response/tutorial002.py!}
5042
```
5143

52-
!!! note
53-
Notice that you import it directly from `starlette.responses`, not from `fastapi`.
54-
5544
!!! info
5645
The parameter `response_class` will also be used to define the "media type" of the response.
5746

5847
In this case, the HTTP header `Content-Type` will be set to `text/html`.
5948

6049
And it will be documented as such in OpenAPI.
6150

62-
### Return a Starlette `Response`
51+
### Return a `Response`
6352

6453
As seen in [Return a Response directly](response-directly.md){.internal-link target=_blank}, you can also override the response directly in your *path operation*, by returning it.
6554

@@ -89,14 +78,115 @@ For example, it could be something like:
8978
{!./src/custom_response/tutorial004.py!}
9079
```
9180

92-
In this example, the function `generate_html_response()` already generates a Starlette `Response` instead of the HTML in a `str`.
81+
In this example, the function `generate_html_response()` already generates and returns a `Response` instead of returning the HTML in a `str`.
9382

9483
By returning the result of calling `generate_html_response()`, you are already returning a `Response` that will override the default **FastAPI** behavior.
9584

96-
But as you passed the `HTMLResponse` in the `response_class`, **FastAPI** will know how to document it in OpenAPI and the interactive docs as HTML with `text/html`:
85+
But as you passed the `HTMLResponse` in the `response_class` too, **FastAPI** will know how to document it in OpenAPI and the interactive docs as HTML with `text/html`:
9786

9887
<img src="/img/tutorial/custom-response/image01.png">
9988

89+
## Available responses
90+
91+
Here are some of the available responses.
92+
93+
Have in mind that you can use `Response` to return anything else, or even create a custom sub-class.
94+
95+
!!! note "Technical Details"
96+
You could also use `from starlette.responses import HTMLResponse`.
97+
98+
**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.
99+
100+
### `Response`
101+
102+
The main `Response` class, all the other responses inherit from it.
103+
104+
You can return it directly.
105+
106+
It accepts the following parameters:
107+
108+
* `content` - A `str` or `bytes`.
109+
* `status_code` - An `int` HTTP status code.
110+
* `headers` - A `dict` of strings.
111+
* `media_type` - A `str` giving the media type. E.g. `"text/html"`.
112+
113+
FastAPI (actually Starlette) will automatically include a Content-Length header. It will also include a Content-Type header, based on the media_type and appending a charset for text types.
114+
115+
```Python hl_lines="1 18"
116+
{!./src/response_directly/tutorial002.py!}
117+
```
118+
119+
### `HTMLResponse`
120+
121+
Takes some text or bytes and returns an HTML response, as you read above.
122+
123+
### `PlainTextResponse`
124+
125+
Takes some text or bytes and returns an plain text response.
126+
127+
```Python hl_lines="2 7 9"
128+
{!./src/custom_response/tutorial005.py!}
129+
```
130+
131+
### `JSONResponse`
132+
133+
Takes some data and returns an `application/json` encoded response.
134+
135+
This is the default response used in **FastAPI**, as you read above.
136+
137+
### `UJSONResponse`
138+
139+
An alternative JSON response using `ujson` for faster serialization as you read above.
140+
141+
!!! warning
142+
`ujson` is less careful than Python's built-in implementation in how it handles some edge-cases.
143+
144+
### `RedirectResponse`
145+
146+
Returns an HTTP redirect. Uses a 307 status code (Temporary Redirect) by default.
147+
148+
```Python hl_lines="2 9"
149+
{!./src/custom_response/tutorial006.py!}
150+
```
151+
152+
### `StreamingResponse`
153+
154+
Takes an async generator or a normal generator/iterator and streams the response body.
155+
156+
```Python hl_lines="2 14"
157+
{!./src/custom_response/tutorial007.py!}
158+
```
159+
160+
#### Using `StreamingResponse` with file-like objects
161+
162+
If you have a file-like object (e.g. the object returned by `open()`), you can return it in a `StreamingResponse`.
163+
164+
This includes many libraries to interact with cloud storage, video processing, and others.
165+
166+
```Python hl_lines="2 10 11"
167+
{!./src/custom_response/tutorial008.py!}
168+
```
169+
170+
!!! tip
171+
Notice that here as we are using standard `open()` that doesn't support `async` and `await`, we declare the path operation with normal `def`.
172+
173+
### `FileResponse`
174+
175+
Asynchronously streams a file as the response.
176+
177+
Takes a different set of arguments to instantiate than the other response types:
178+
179+
* `path` - The filepath to the file to stream.
180+
* `headers` - Any custom headers to include, as a dictionary.
181+
* `media_type` - A string giving the media type. If unset, the filename or path will be used to infer a media type.
182+
* `filename` - If set, this will be included in the response `Content-Disposition`.
183+
184+
File responses will include appropriate `Content-Length`, `Last-Modified` and `ETag` headers.
185+
186+
```Python hl_lines="2 10"
187+
{!./src/custom_response/tutorial009.py!}
188+
```
189+
100190
## Additional documentation
101191

102192
You can also declare the media type and many other details in OpenAPI using `responses`: [Additional Responses in OpenAPI](additional-responses.md){.internal-link target=_blank}.

docs/advanced/extending-openapi.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ pip install aiofiles
161161

162162
### Serve the static files
163163

164-
* Import `StaticFiles` from Starlette.
164+
* Import `StaticFiles`.
165165
* "Mount" a `StaticFiles()` instance in a specific path.
166166

167167
```Python hl_lines="7 11"

docs/advanced/middleware.md

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
In the main tutorial you read how to add [Custom Middleware](../tutorial/middleware.md){.internal-link target=_blank} to your application.
2+
3+
And then you also read how to handle [CORS with the `CORSMiddleware`](../tutorial/cors.md){.internal-link target=_blank}.
4+
5+
In this section we'll see how to use other middlewares.
6+
7+
## Adding ASGI middlewares
8+
9+
As **FastAPI** is based on Starlette and implements the <abbr title="Asynchronous Server Gateway Interface">ASGI</abbr> specification, you can use any ASGI middleware.
10+
11+
A middleware doesn't have to be made for FastAPI or Starlette to work, as long as it follows the ASGI spec.
12+
13+
In general, ASGI middlewares are classes that expect to receive an ASGI app as the first argument.
14+
15+
So, in the documentation for third-party ASGI middlewares they will probably tell you to do something like:
16+
17+
```Python
18+
from unicorn import UnicornMiddleware
19+
20+
app = SomeASGIApp()
21+
22+
new_app = UnicornMiddleware(app, some_config="rainbow")
23+
```
24+
25+
But FastAPI (actually Starlette) provides a simpler way to do it that makes sure that the internal middlewares to handle server errors and custom exception handlers work properly.
26+
27+
For that, you use `app.add_middleware()` (as in the example for CORS).
28+
29+
```Python
30+
from fastapi import FastAPI
31+
from unicorn import UnicornMiddleware
32+
33+
app = FastAPI()
34+
35+
app.add_middleware(UnicornMiddleware, some_config="rainbow")
36+
```
37+
38+
`app.add_middleware()` receives a middleware class as the first argument and any additional arguments to be passed to the middleware.
39+
40+
## Integrated middlewares
41+
42+
**FastAPI** includes several middlewares for common use cases, we'll see next how to use them.
43+
44+
!!! note "Technical Details"
45+
For the next examples, you could also use `from starlette.middleware.something import SomethingMiddleware`.
46+
47+
**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.
48+
49+
## `HTTPSRedirectMiddleware`
50+
51+
Enforces that all incoming requests must either be `https` or `wss`.
52+
53+
Any incoming requests to `http` or `ws` will be redirected to the secure scheme instead.
54+
55+
```Python hl_lines="2 6"
56+
{!./src/advanced_middleware/tutorial001.py!}
57+
```
58+
59+
## `TrustedHostMiddleware`
60+
61+
Enforces that all incoming requests have a correctly set `Host` header, in order to guard against HTTP Host Header attacks.
62+
63+
```Python hl_lines="2 6 7 8"
64+
{!./src/advanced_middleware/tutorial002.py!}
65+
```
66+
67+
The following arguments are supported:
68+
69+
* `allowed_hosts` - A list of domain names that should be allowed as hostnames. Wildcard domains such as `*.example.com` are supported for matching subdomains to allow any hostname either use `allowed_hosts=["*"]` or omit the middleware.
70+
71+
If an incoming request does not validate correctly then a `400` response will be sent.
72+
73+
## `GZipMiddleware`
74+
75+
Handles GZip responses for any request that includes `"gzip"` in the `Accept-Encoding` header.
76+
77+
The middleware will handle both standard and streaming responses.
78+
79+
```Python hl_lines="2 6 7 8"
80+
{!./src/advanced_middleware/tutorial002.py!}
81+
```
82+
83+
The following arguments are supported:
84+
85+
* `minimum_size` - Do not GZip responses that are smaller than this minimum size in bytes. Defaults to `500`.
86+
87+
## Other middlewares
88+
89+
There are many other ASGI middlewares.
90+
91+
For example:
92+
93+
* <a href="https://docs.sentry.io/platforms/python/asgi/" class="external-link" target="_blank">Sentry</a>
94+
* <a href="https://github.com/encode/uvicorn/blob/master/uvicorn/middleware/proxy_headers.py" class="external-link" target="_blank">Uvicorn's `ProxyHeadersMiddleware`</a>
95+
* <a href="https://github.com/florimondmanca/msgpack-asgi" class="external-link" target="_blank">MessagePack</a>
96+
97+
To see other available middlewares check <a href="https://www.starlette.io/middleware/" class="external-link" target="_blank">Starlette's Middleware docs</a> and the <a href="https://github.com/florimondmanca/awesome-asgi" class="external-link" target="_blank">ASGI Awesome List</a>.

docs/advanced/response-change-status-code.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ You can declare a parameter of type `Response` in your *path operation function*
1818

1919
And then you can set the `status_code` in that *temporal* response object.
2020

21-
```Python hl_lines="2 11 14"
21+
```Python hl_lines="1 9 12"
2222
{!./src/response_change_status_code/tutorial001.py!}
2323
```
2424

0 commit comments

Comments
 (0)