mirror of https://github.com/tiangolo/fastapi.git
✨ Update docs to use Pydantic v2 settings and add note and example about v1 (#9788)
* ➕ Add pydantic-settings to all extras * 📝 Update docs for Pydantic settings * 📝 Update Settings source examples to use Pydantic v2, and add a Pydantic v1 version * ✅ Add tests for settings with Pydantic v1 and v2 * 🔥 Remove solved TODO comment * ♻️ Update conditional OpenAPI to use new Pydantic v2 settings
This commit is contained in:
parent
b892664f25
commit
9ebd800d9b
|
|
@ -125,7 +125,34 @@ That means that any value read in Python from an environment variable will be a
|
|||
|
||||
## Pydantic `Settings`
|
||||
|
||||
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>.
|
||||
Fortunately, Pydantic provides a great utility to handle these settings coming from environment variables with <a href="https://docs.pydantic.dev/latest/usage/pydantic_settings/" class="external-link" target="_blank">Pydantic: Settings management</a>.
|
||||
|
||||
### Install `pydantic-settings`
|
||||
|
||||
First, install the `pydantic-settings` package:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install pydantic-settings
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
It also comes included when you install the `all` extras with:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install "fastapi[all]"
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
!!! info
|
||||
In Pydantic v1 it came included with the main package. Now it is distributed as this independent package so that you can choose to install it or not if you don't need that functionality.
|
||||
|
||||
### Create the `Settings` object
|
||||
|
||||
|
|
@ -135,9 +162,20 @@ The same way as with Pydantic models, you declare class attributes with type ann
|
|||
|
||||
You can use all the same validation features and tools you use for Pydantic models, like different data types and additional validations with `Field()`.
|
||||
|
||||
```Python hl_lines="2 5-8 11"
|
||||
{!../../../docs_src/settings/tutorial001.py!}
|
||||
```
|
||||
=== "Pydantic v2"
|
||||
|
||||
```Python hl_lines="2 5-8 11"
|
||||
{!> ../../../docs_src/settings/tutorial001.py!}
|
||||
```
|
||||
|
||||
=== "Pydantic v1"
|
||||
|
||||
!!! info
|
||||
In Pydantic v1 you would import `BaseSettings` directly from `pydantic` instead of from `pydantic_settings`.
|
||||
|
||||
```Python hl_lines="2 5-8 11"
|
||||
{!> ../../../docs_src/settings/tutorial001_pv1.py!}
|
||||
```
|
||||
|
||||
!!! tip
|
||||
If you want something quick to copy and paste, don't use this example, use the last one below.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
from fastapi import FastAPI
|
||||
from pydantic import BaseSettings
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from pydantic import BaseSettings
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from pydantic import BaseSettings
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from pydantic import BaseSettings
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from pydantic import BaseSettings
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from pydantic import BaseSettings
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from pydantic import BaseSettings
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from pydantic import BaseSettings
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
from fastapi import FastAPI
|
||||
from pydantic import BaseSettings
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
from fastapi import FastAPI
|
||||
from pydantic import BaseSettings
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
app_name: str = "Awesome API"
|
||||
admin_email: str
|
||||
items_per_user: int = 50
|
||||
|
||||
|
||||
settings = Settings()
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.get("/info")
|
||||
async def info():
|
||||
return {
|
||||
"app_name": settings.app_name,
|
||||
"admin_email": settings.admin_email,
|
||||
"items_per_user": settings.items_per_user,
|
||||
}
|
||||
|
|
@ -63,6 +63,7 @@ all = [
|
|||
"orjson >=3.2.1",
|
||||
"email_validator >=2.0.0",
|
||||
"uvicorn[standard] >=0.12.0",
|
||||
"pydantic-settings >=2.0.0",
|
||||
]
|
||||
|
||||
[tool.hatch.version]
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import importlib
|
|||
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from ...utils import needs_pydanticv1
|
||||
from ...utils import needs_pydanticv2
|
||||
|
||||
|
||||
def get_client() -> TestClient:
|
||||
|
|
@ -14,8 +14,7 @@ def get_client() -> TestClient:
|
|||
return client
|
||||
|
||||
|
||||
# TODO: pv2 add version with Pydantic v2
|
||||
@needs_pydanticv1
|
||||
@needs_pydanticv2
|
||||
def test_disable_openapi(monkeypatch):
|
||||
monkeypatch.setenv("OPENAPI_URL", "")
|
||||
# Load the client after setting the env var
|
||||
|
|
@ -28,8 +27,7 @@ def test_disable_openapi(monkeypatch):
|
|||
assert response.status_code == 404, response.text
|
||||
|
||||
|
||||
# TODO: pv2 add version with Pydantic v2
|
||||
@needs_pydanticv1
|
||||
@needs_pydanticv2
|
||||
def test_root():
|
||||
client = get_client()
|
||||
response = client.get("/")
|
||||
|
|
@ -37,8 +35,7 @@ def test_root():
|
|||
assert response.json() == {"message": "Hello World"}
|
||||
|
||||
|
||||
# TODO: pv2 add version with Pydantic v2
|
||||
@needs_pydanticv1
|
||||
@needs_pydanticv2
|
||||
def test_default_openapi():
|
||||
client = get_client()
|
||||
response = client.get("/docs")
|
||||
|
|
|
|||
|
|
@ -3,8 +3,6 @@ from dirty_equals import IsDict
|
|||
from fastapi.testclient import TestClient
|
||||
from fastapi.utils import match_pydantic_error_url
|
||||
|
||||
# TODO: pv2 docs note to mention before it was regex instead of pattern
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client():
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
from pytest import MonkeyPatch
|
||||
|
||||
from ...utils import needs_pydanticv1
|
||||
from ...utils import needs_pydanticv2
|
||||
|
||||
|
||||
# TODO: pv2 add version with Pydantic v2
|
||||
@needs_pydanticv1
|
||||
@needs_pydanticv2
|
||||
def test_settings(monkeypatch: MonkeyPatch):
|
||||
from docs_src.settings.app02 import main
|
||||
|
||||
|
|
@ -14,8 +13,7 @@ def test_settings(monkeypatch: MonkeyPatch):
|
|||
assert settings.items_per_user == 50
|
||||
|
||||
|
||||
# TODO: pv2 add version with Pydantic v2
|
||||
@needs_pydanticv1
|
||||
@needs_pydanticv2
|
||||
def test_override_settings():
|
||||
from docs_src.settings.app02 import test_main
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
from fastapi.testclient import TestClient
|
||||
from pytest import MonkeyPatch
|
||||
|
||||
from ...utils import needs_pydanticv2
|
||||
|
||||
|
||||
@needs_pydanticv2
|
||||
def test_settings(monkeypatch: MonkeyPatch):
|
||||
monkeypatch.setenv("ADMIN_EMAIL", "admin@example.com")
|
||||
from docs_src.settings.tutorial001 import app
|
||||
|
||||
client = TestClient(app)
|
||||
response = client.get("/info")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"app_name": "Awesome API",
|
||||
"admin_email": "admin@example.com",
|
||||
"items_per_user": 50,
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
from fastapi.testclient import TestClient
|
||||
from pytest import MonkeyPatch
|
||||
|
||||
from ...utils import needs_pydanticv1
|
||||
|
||||
|
||||
@needs_pydanticv1
|
||||
def test_settings(monkeypatch: MonkeyPatch):
|
||||
monkeypatch.setenv("ADMIN_EMAIL", "admin@example.com")
|
||||
from docs_src.settings.tutorial001_pv1 import app
|
||||
|
||||
client = TestClient(app)
|
||||
response = client.get("/info")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"app_name": "Awesome API",
|
||||
"admin_email": "admin@example.com",
|
||||
"items_per_user": 50,
|
||||
}
|
||||
Loading…
Reference in New Issue