Skip to content

Commit d4d5b21

Browse files
📝 Add documentation about settings and env vars (fastapi#1118)
* Add doc and example for env var config * Syntax highlight for .env file * Add test for configuration docs * 📝 Update settings docs, add more examples * ✅ Add tests for settings * 🚚 Rename "Application Configuration" to "Metadata and Docs URLs" to disambiguate between that and settings * 🔥 Remove replaced example file Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
1 parent 6e1cd45 commit d4d5b21

File tree

22 files changed

+448
-11
lines changed

22 files changed

+448
-11
lines changed

docs/en/docs/advanced/settings.md

Lines changed: 294 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,294 @@
1+
# Settings and Environment Variables
2+
3+
In many cases your application could need some external settings or configurations, for example secret keys, database credentials, credentials for email services, etc.
4+
5+
Most of these settings are variable (can change), like database URLs. And many could be sensitive, like secrets.
6+
7+
For this reason it's common to provide them in environment variables that are read by the application.
8+
9+
## Environment Variables
10+
11+
!!! tip
12+
If you already know what "environment variables" are and how to use them, feel free to skip to the next section below.
13+
14+
An <a href="https://en.wikipedia.org/wiki/Environment_variable" class="external-link" target="_blank">environment variable</a> (also known as "env var") is a variable that lives outside of the Python code, in the operating system, and could be read by your Python code (or by other programs as well).
15+
16+
You can create and use environment variables in the shell, without needing Python:
17+
18+
<div class="termy">
19+
20+
```console
21+
// You could create an env var MY_NAME with
22+
$ export MY_NAME="Wade Wilson"
23+
24+
// Then you could use it with other programs, like
25+
$ echo "Hello $MY_NAME"
26+
27+
Hello Wade Wilson
28+
```
29+
30+
</div>
31+
32+
Or in PowerShell in Windows:
33+
34+
<div class="termy">
35+
36+
```console
37+
// Create an env var MY_NAME
38+
$ $Env:MY_NAME = "Wade Wilson"
39+
40+
// Use it with other programs, like
41+
$ echo "Hello $Env:MY_NAME"
42+
43+
Hello Wade Wilson
44+
```
45+
46+
</div>
47+
48+
### Read env vars in Python
49+
50+
You could also create environment variables outside of Python, in the terminal (or with any other method), and then read them in Python.
51+
52+
For example you could have a file `main.py` with:
53+
54+
```Python hl_lines="3"
55+
import os
56+
57+
name = os.getenv("MY_NAME", "World")
58+
print(f"Hello {name} from Python")
59+
```
60+
61+
!!! tip
62+
The second argument to <a href="https://docs.python.org/3.8/library/os.html#os.getenv" class="external-link" target="_blank">`os.getenv()`</a> is the default value to return.
63+
64+
If not provided, it's `None` by default, here we provide `"World"` as the default value to use.
65+
66+
Then you could call that Python program:
67+
68+
<div class="termy">
69+
70+
```console
71+
// Here we don't set the env var yet
72+
$ python main.py
73+
74+
// As we didn't set the env var, we get the default value
75+
76+
Hello World from Python
77+
78+
// But if we create an environment variable first
79+
$ export MY_NAME="Wade Wilson"
80+
81+
// And then call the program again
82+
$ python main.py
83+
84+
// Now it can read the environment variable
85+
86+
Hello Wade Wilson from Python
87+
```
88+
89+
</div>
90+
91+
As environment variables can be set outside of the code, but can be read by the code, and don't have to be stored (committed to `git`) with the rest of the files, it's common to use them for configurations or settings.
92+
93+
You can also create an environment variable only for a specific program invocation, that is only available to that program, and only for its duration.
94+
95+
To do that, create it right before the program itself, on the same line:
96+
97+
<div class="termy">
98+
99+
```console
100+
// Create an env var MY_NAME in line for this program call
101+
$ MY_NAME="Wade Wilson" python main.py
102+
103+
// Now it can read the environment variable
104+
105+
Hello Wade Wilson from Python
106+
107+
// The env var no longer exists afterwards
108+
$ python main.py
109+
110+
Hello World from Python
111+
```
112+
113+
</div>
114+
115+
!!! tip
116+
You can read more about it at <a href="https://12factor.net/config" class="external-link" target="_blank">The Twelve-Factor App: Config</a>.
117+
118+
### Types and validation
119+
120+
These environment variables can only handle text strings, as they are external to Python and have to be compatible with other programs and the rest of the system (and even with different operating systems, as Linux, Windows, macOS).
121+
122+
That means that any value read in Python from an environment variable will be a `str`, and any conversion to a different type or validation has be done in code.
123+
124+
## Pydantic `Settings`
125+
126+
Fortunately, Pydantic provides a great utility to handle these settings coming from environment variables with <a href="https://pydantic-docs.helpmanual.io/usage/settings/" class="external-link" target="_blank">Pydantic: Settings management</a>.
127+
128+
### Create the `Settings` object
129+
130+
Import `BaseSettings` from Pydantic and create a sub-class, very much like with a Pydantic model.
131+
132+
The same way as with Pydantic models, you declare class attributes with type annotations, and possibly default values.
133+
134+
You can use all the same validation features and tools you use for Pydantic models, like different data types and additional validations with `Field()`.
135+
136+
```Python hl_lines="2 5 6 7 8 11"
137+
{!../../../docs_src/settings/tutorial001.py!}
138+
```
139+
140+
Then, when you create an instance of that `Settings` class (in this case, in the `settings` object), Pydantic will read the environment variables in a case-insensitive way, so, an upper-case variable `APP_NAME` will still be read for the attribute `app_name`.
141+
142+
Next it will convert and validate the data. So, when you use that `settings` object, you will have data of the types you declared (e.g. `items_per_user` will be an `int`).
143+
144+
### Use the `settings`
145+
146+
Then you can use the new `settings` object in your application:
147+
148+
```Python hl_lines="18 19 20"
149+
{!../../../docs_src/settings/tutorial001.py!}
150+
```
151+
152+
### Run the server
153+
154+
Then you would run the server passing the configurations as environment variables, for example you could set an `ADMIN_EMAIL` and `APP_NAME` with:
155+
156+
<div class="termy">
157+
158+
```console
159+
$ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp" uvicorn main:app
160+
161+
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
162+
```
163+
164+
</div>
165+
166+
!!! tip
167+
To set multiple env vars for a single command just separate them with a space, and put them all before the command.
168+
169+
And then the `admin_email` setting would be set to `"deadpool@example.com"`.
170+
171+
The `app_name` would be `"ChimichangApp"`.
172+
173+
And the `items_per_user` would keep its default value of `50`.
174+
175+
## Settings in another module
176+
177+
You could put those settings in another module file as you saw in [Bigger Applications - Multiple Files](bigger-applications.md){.internal-link target=_blank}.
178+
179+
For example, you could have a file `config.py` with:
180+
181+
```Python
182+
{!../../../docs_src/settings/app01/config.py!}
183+
```
184+
185+
And then use it in a file `main.py`:
186+
187+
```Python hl_lines="3 11 12 13"
188+
{!../../../docs_src/settings/app01/main.py!}
189+
```
190+
191+
!!! tip
192+
You would also need a file `__init__.py` as you saw on [Bigger Applications - Multiple Files](bigger-applications.md){.internal-link target=_blank}.
193+
194+
## Settings in a dependency
195+
196+
In some occasions it might be useful to provide the settings from a dependency, instead of having a global object with `settings` that is used everywhere.
197+
198+
This could be especially useful during testing, as it's very easy to override a dependency with your own custom settings.
199+
200+
### The config file
201+
202+
Coming from the previous example, your `config.py` file could look like:
203+
204+
```Python hl_lines="10"
205+
{!../../../docs_src/settings/app02/config.py!}
206+
```
207+
208+
Notice that now we don't create a default instance `settings = Settings()`.
209+
210+
Instead we declare its type as `Settings`, but the value as `None`.
211+
212+
### The main app file
213+
214+
Now we create a dependency that returns the `settings` object if we already created it.
215+
216+
Otherwise we create a new one, assign it to `config.settings` and then return it from the dependency.
217+
218+
```Python hl_lines="8 9 10 11 12"
219+
{!../../../docs_src/settings/app02/main.py!}
220+
```
221+
222+
And then we can require it from the *path operation function* as a dependency and use it anywhere we need it.
223+
224+
```Python hl_lines="16 18 19 20"
225+
{!../../../docs_src/settings/app02/main.py!}
226+
```
227+
228+
### Settings and testing
229+
230+
Then it would be very easy to provide a different settings object during testing by creating a dependency override for `get_settings`:
231+
232+
```Python hl_lines="8 9 12 21"
233+
{!../../../docs_src/settings/app02/test_main.py!}
234+
```
235+
236+
In the dependency override we set a new value for the `admin_email` when creating the new `Settings` object, and then we return that new object.
237+
238+
Then we can test that it is used.
239+
240+
## Reading a `.env` file
241+
242+
If you have many settings that possibly change a lot, maybe in different environments, it might be useful to put them on a file and then read them from it as if they were environment variables.
243+
244+
This practice is common enough that it has a name, these environment variables are commonly placed in a file `.env`, and the file is called a "dotenv".
245+
246+
!!! tip
247+
A file starting with a dot (`.`) is a hidden file in Unix-like systems, like Linux and macOS.
248+
249+
But a dotenv file doesn't really have to have that exact filename.
250+
251+
Pydantic has support for reading from these types of files using an external library. You can read more at <a href="https://pydantic-docs.helpmanual.io/usage/settings/#dotenv-env-support" class="external-link" target="_blank">Pydantic Settings: Dotenv (.env) support</a>.
252+
253+
!!! tip
254+
For this to work, you need to `pip install python-dotenv`.
255+
256+
### The `.env` file
257+
258+
You could have a `.env` file with:
259+
260+
```bash
261+
ADMIN_EMAIL="deadpool@example.com"
262+
APP_NAME="ChimichangApp"
263+
```
264+
265+
### Read settings from `.env`
266+
267+
And then update your `config.py` with:
268+
269+
```Python hl_lines="9 10"
270+
{!../../../docs_src/settings/app03/config.py!}
271+
```
272+
273+
Here we create a class `Config` inside of your Pydantic `Settings` class, and set the `env_file` to the filename with the dotenv file we want to use.
274+
275+
!!! tip
276+
The `Config` class is used just for Pydantic configuration. You can read more at <a href="https://pydantic-docs.helpmanual.io/usage/model_config/" class="external-link" target="_blank">Pydantic Model Config</a>
277+
278+
### Creating the settings object
279+
280+
Reading a file from disk is normally a costly (slow) operation, so you probably want to do it only once and then re-use the same settings, instead of reading it for each request.
281+
282+
Because of that, in the dependency function, we first check if we already have a `settings` object, and create a new one (that could read from disk) only if it's still `None`, so, it would happen only the first time:
283+
284+
```Python hl_lines="9 10 11 12"
285+
{!../../../docs_src/settings/app03/main.py!}
286+
```
287+
288+
## Recap
289+
290+
You can use Pydantic Settings to handle the settings or configurations for your application, with all the power of Pydantic models.
291+
292+
* By using a dependency you can simplify testing.
293+
* You can use `.env` files with it.
294+
* Saving the settings in a variable lets you avoid reading the dotenv file again and again for each request.

docs/en/docs/tutorial/application-configuration.md renamed to docs/en/docs/tutorial/metadata.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,25 @@
1-
# Application Configuration
1+
# Metadata and Docs URLs
22

3-
There are several things that you can configure in your FastAPI application.
3+
You can customize several metadata configurations in your **FastAPI** application.
44

55
## Title, description, and version
66

77
You can set the:
88

9-
* Title: used as your API's title/name, in OpenAPI and the automatic API docs UIs.
10-
* Description: the description of your API, in OpenAPI and the automatic API docs UIs.
11-
* Version: the version of your API, e.g. `v2` or `2.5.0`.
9+
* **Title**: used as your API's title/name, in OpenAPI and the automatic API docs UIs.
10+
* **Description**: the description of your API, in OpenAPI and the automatic API docs UIs.
11+
* **Version**: the version of your API, e.g. `v2` or `2.5.0`.
1212
* Useful for example if you had a previous version of the application, also using OpenAPI.
1313

1414
To set them, use the parameters `title`, `description`, and `version`:
1515

1616
```Python hl_lines="4 5 6"
17-
{!../../../docs_src/application_configuration/tutorial001.py!}
17+
{!../../../docs_src/metadata/tutorial001.py!}
1818
```
1919

2020
With this configuration, the automatic API docs would look like:
2121

22-
<img src="/img/tutorial/application-configuration/image01.png">
22+
<img src="/img/tutorial/metadata/image01.png">
2323

2424
## OpenAPI URL
2525

@@ -30,7 +30,7 @@ But you can configure it with the parameter `openapi_url`.
3030
For example, to set it to be served at `/api/v1/openapi.json`:
3131

3232
```Python hl_lines="3"
33-
{!../../../docs_src/application_configuration/tutorial002.py!}
33+
{!../../../docs_src/metadata/tutorial002.py!}
3434
```
3535

3636
If you want to disable the OpenAPI schema completely you can set `openapi_url=None`.
@@ -49,5 +49,5 @@ You can configure the two documentation user interfaces included:
4949
For example, to set Swagger UI to be served at `/documentation` and disable ReDoc:
5050

5151
```Python hl_lines="3"
52-
{!../../../docs_src/application_configuration/tutorial003.py!}
52+
{!../../../docs_src/metadata/tutorial003.py!}
5353
```

docs/en/mkdocs.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ nav:
6565
- tutorial/sql-databases.md
6666
- tutorial/bigger-applications.md
6767
- tutorial/background-tasks.md
68-
- tutorial/application-configuration.md
68+
- tutorial/metadata.md
6969
- tutorial/static-files.md
7070
- tutorial/testing.md
7171
- tutorial/debugging.md
@@ -98,6 +98,7 @@ nav:
9898
- advanced/testing-websockets.md
9999
- advanced/testing-events.md
100100
- advanced/testing-dependencies.md
101+
- advanced/settings.md
101102
- advanced/extending-openapi.md
102103
- advanced/openapi-callbacks.md
103104
- advanced/wsgi.md

docs_src/settings/app01/config.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from pydantic import BaseSettings
2+
3+
4+
class Settings(BaseSettings):
5+
app_name: str = "Awesome API"
6+
admin_email: str
7+
items_per_user: int = 50
8+
9+
10+
settings = Settings()

docs_src/settings/app01/main.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from fastapi import FastAPI
2+
3+
from . import config
4+
5+
app = FastAPI()
6+
7+
8+
@app.get("/info")
9+
async def info():
10+
return {
11+
"app_name": config.settings.app_name,
12+
"admin_email": config.settings.admin_email,
13+
"items_per_user": config.settings.items_per_user,
14+
}

0 commit comments

Comments
 (0)