kwargs. Even if they don't have a default value.
-{* ../../docs_src/path_params_numeric_validations/tutorial003_py39.py hl[7] *}
+{* ../../docs_src/path_params_numeric_validations/tutorial003_py310.py hl[7] *}
### Better with `Annotated` { #better-with-annotated }
Keep in mind that if you use `Annotated`, as you are not using function parameter default values, you won't have this problem, and you probably won't need to use `*`.
-{* ../../docs_src/path_params_numeric_validations/tutorial003_an_py39.py hl[10] *}
+{* ../../docs_src/path_params_numeric_validations/tutorial003_an_py310.py hl[10] *}
## Number validations: greater than or equal { #number-validations-greater-than-or-equal }
@@ -97,7 +97,7 @@ With `Query` and `Path` (and others you'll see later) you can declare number con
Here, with `ge=1`, `item_id` will need to be an integer number "`g`reater than or `e`qual" to `1`.
-{* ../../docs_src/path_params_numeric_validations/tutorial004_an_py39.py hl[10] *}
+{* ../../docs_src/path_params_numeric_validations/tutorial004_an_py310.py hl[10] *}
## Number validations: greater than and less than or equal { #number-validations-greater-than-and-less-than-or-equal }
@@ -106,7 +106,7 @@ The same applies for:
* `gt`: `g`reater `t`han
* `le`: `l`ess than or `e`qual
-{* ../../docs_src/path_params_numeric_validations/tutorial005_an_py39.py hl[10] *}
+{* ../../docs_src/path_params_numeric_validations/tutorial005_an_py310.py hl[10] *}
## Number validations: floats, greater than and less than { #number-validations-floats-greater-than-and-less-than }
@@ -118,7 +118,7 @@ So, `0.5` would be a valid value. But `0.0` or `0` would not.
And the same for lt.
-{* ../../docs_src/path_params_numeric_validations/tutorial006_an_py39.py hl[13] *}
+{* ../../docs_src/path_params_numeric_validations/tutorial006_an_py310.py hl[13] *}
## Recap { #recap }
diff --git a/docs/en/docs/tutorial/path-params.md b/docs/en/docs/tutorial/path-params.md
index cf312f1fe..8adbbcfa1 100644
--- a/docs/en/docs/tutorial/path-params.md
+++ b/docs/en/docs/tutorial/path-params.md
@@ -2,7 +2,7 @@
You can declare path "parameters" or "variables" with the same syntax used by Python format strings:
-{* ../../docs_src/path_params/tutorial001_py39.py hl[6:7] *}
+{* ../../docs_src/path_params/tutorial001_py310.py hl[6:7] *}
The value of the path parameter `item_id` will be passed to your function as the argument `item_id`.
@@ -16,7 +16,7 @@ So, if you run this example and go to Item:
- if x_token != fake_secret_token:
- raise HTTPException(status_code=400, detail="Invalid X-Token header")
- if item.id in fake_db:
- raise HTTPException(status_code=409, detail="Item already exists")
- fake_db[item.id] = item.model_dump()
- return item
diff --git a/docs_src/app_testing/app_b_an_py39/test_main.py b/docs_src/app_testing/app_b_an_py39/test_main.py
deleted file mode 100644
index 4e1c51ecc..000000000
--- a/docs_src/app_testing/app_b_an_py39/test_main.py
+++ /dev/null
@@ -1,65 +0,0 @@
-from fastapi.testclient import TestClient
-
-from .main import app
-
-client = TestClient(app)
-
-
-def test_read_item():
- response = client.get("/items/foo", headers={"X-Token": "coneofsilence"})
- assert response.status_code == 200
- assert response.json() == {
- "id": "foo",
- "title": "Foo",
- "description": "There goes my hero",
- }
-
-
-def test_read_item_bad_token():
- response = client.get("/items/foo", headers={"X-Token": "hailhydra"})
- assert response.status_code == 400
- assert response.json() == {"detail": "Invalid X-Token header"}
-
-
-def test_read_nonexistent_item():
- response = client.get("/items/baz", headers={"X-Token": "coneofsilence"})
- assert response.status_code == 404
- assert response.json() == {"detail": "Item not found"}
-
-
-def test_create_item():
- response = client.post(
- "/items/",
- headers={"X-Token": "coneofsilence"},
- json={"id": "foobar", "title": "Foo Bar", "description": "The Foo Barters"},
- )
- assert response.status_code == 200
- assert response.json() == {
- "id": "foobar",
- "title": "Foo Bar",
- "description": "The Foo Barters",
- }
-
-
-def test_create_item_bad_token():
- response = client.post(
- "/items/",
- headers={"X-Token": "hailhydra"},
- json={"id": "bazz", "title": "Bazz", "description": "Drop the bazz"},
- )
- assert response.status_code == 400
- assert response.json() == {"detail": "Invalid X-Token header"}
-
-
-def test_create_existing_item():
- response = client.post(
- "/items/",
- headers={"X-Token": "coneofsilence"},
- json={
- "id": "foo",
- "title": "The Foo ID Stealers",
- "description": "There goes my stealer",
- },
- )
- assert response.status_code == 409
- assert response.json() == {"detail": "Item already exists"}
diff --git a/docs_src/app_testing/app_b_py39/main.py b/docs_src/app_testing/app_b_py39/main.py
deleted file mode 100644
index ed38f4721..000000000
--- a/docs_src/app_testing/app_b_py39/main.py
+++ /dev/null
@@ -1,38 +0,0 @@
-from typing import Union
-
-from fastapi import FastAPI, Header, HTTPException
-from pydantic import BaseModel
-
-fake_secret_token = "coneofsilence"
-
-fake_db = {
- "foo": {"id": "foo", "title": "Foo", "description": "There goes my hero"},
- "bar": {"id": "bar", "title": "Bar", "description": "The bartenders"},
-}
-
-app = FastAPI()
-
-
-class Item(BaseModel):
- id: str
- title: str
- description: Union[str, None] = None
-
-
-@app.get("/items/{item_id}", response_model=Item)
-async def read_main(item_id: str, x_token: str = Header()):
- if x_token != fake_secret_token:
- raise HTTPException(status_code=400, detail="Invalid X-Token header")
- if item_id not in fake_db:
- raise HTTPException(status_code=404, detail="Item not found")
- return fake_db[item_id]
-
-
-@app.post("/items/")
-async def create_item(item: Item, x_token: str = Header()) -> Item:
- if x_token != fake_secret_token:
- raise HTTPException(status_code=400, detail="Invalid X-Token header")
- if item.id in fake_db:
- raise HTTPException(status_code=409, detail="Item already exists")
- fake_db[item.id] = item.model_dump()
- return item
diff --git a/docs_src/app_testing/app_b_py39/test_main.py b/docs_src/app_testing/app_b_py39/test_main.py
deleted file mode 100644
index 4e1c51ecc..000000000
--- a/docs_src/app_testing/app_b_py39/test_main.py
+++ /dev/null
@@ -1,65 +0,0 @@
-from fastapi.testclient import TestClient
-
-from .main import app
-
-client = TestClient(app)
-
-
-def test_read_item():
- response = client.get("/items/foo", headers={"X-Token": "coneofsilence"})
- assert response.status_code == 200
- assert response.json() == {
- "id": "foo",
- "title": "Foo",
- "description": "There goes my hero",
- }
-
-
-def test_read_item_bad_token():
- response = client.get("/items/foo", headers={"X-Token": "hailhydra"})
- assert response.status_code == 400
- assert response.json() == {"detail": "Invalid X-Token header"}
-
-
-def test_read_nonexistent_item():
- response = client.get("/items/baz", headers={"X-Token": "coneofsilence"})
- assert response.status_code == 404
- assert response.json() == {"detail": "Item not found"}
-
-
-def test_create_item():
- response = client.post(
- "/items/",
- headers={"X-Token": "coneofsilence"},
- json={"id": "foobar", "title": "Foo Bar", "description": "The Foo Barters"},
- )
- assert response.status_code == 200
- assert response.json() == {
- "id": "foobar",
- "title": "Foo Bar",
- "description": "The Foo Barters",
- }
-
-
-def test_create_item_bad_token():
- response = client.post(
- "/items/",
- headers={"X-Token": "hailhydra"},
- json={"id": "bazz", "title": "Bazz", "description": "Drop the bazz"},
- )
- assert response.status_code == 400
- assert response.json() == {"detail": "Invalid X-Token header"}
-
-
-def test_create_existing_item():
- response = client.post(
- "/items/",
- headers={"X-Token": "coneofsilence"},
- json={
- "id": "foo",
- "title": "The Foo ID Stealers",
- "description": "There goes my stealer",
- },
- )
- assert response.status_code == 409
- assert response.json() == {"detail": "Item already exists"}
diff --git a/docs_src/app_testing/tutorial001_py310.py b/docs_src/app_testing/tutorial001_py310.py
new file mode 100644
index 000000000..79a853b48
--- /dev/null
+++ b/docs_src/app_testing/tutorial001_py310.py
@@ -0,0 +1,18 @@
+from fastapi import FastAPI
+from fastapi.testclient import TestClient
+
+app = FastAPI()
+
+
+@app.get("/")
+async def read_main():
+ return {"msg": "Hello World"}
+
+
+client = TestClient(app)
+
+
+def test_read_main():
+ response = client.get("/")
+ assert response.status_code == 200
+ assert response.json() == {"msg": "Hello World"}
diff --git a/docs_src/app_testing/tutorial002_py310.py b/docs_src/app_testing/tutorial002_py310.py
new file mode 100644
index 000000000..71c898b3c
--- /dev/null
+++ b/docs_src/app_testing/tutorial002_py310.py
@@ -0,0 +1,31 @@
+from fastapi import FastAPI
+from fastapi.testclient import TestClient
+from fastapi.websockets import WebSocket
+
+app = FastAPI()
+
+
+@app.get("/")
+async def read_main():
+ return {"msg": "Hello World"}
+
+
+@app.websocket("/ws")
+async def websocket(websocket: WebSocket):
+ await websocket.accept()
+ await websocket.send_json({"msg": "Hello WebSocket"})
+ await websocket.close()
+
+
+def test_read_main():
+ client = TestClient(app)
+ response = client.get("/")
+ assert response.status_code == 200
+ assert response.json() == {"msg": "Hello World"}
+
+
+def test_websocket():
+ client = TestClient(app)
+ with client.websocket_connect("/ws") as websocket:
+ data = websocket.receive_json()
+ assert data == {"msg": "Hello WebSocket"}
diff --git a/docs_src/app_testing/tutorial003_py310.py b/docs_src/app_testing/tutorial003_py310.py
new file mode 100644
index 000000000..ca6b45ce0
--- /dev/null
+++ b/docs_src/app_testing/tutorial003_py310.py
@@ -0,0 +1,24 @@
+from fastapi import FastAPI
+from fastapi.testclient import TestClient
+
+app = FastAPI()
+
+items = {}
+
+
+@app.on_event("startup")
+async def startup_event():
+ items["foo"] = {"name": "Fighters"}
+ items["bar"] = {"name": "Tenders"}
+
+
+@app.get("/items/{item_id}")
+async def read_items(item_id: str):
+ return items[item_id]
+
+
+def test_read_items():
+ with TestClient(app) as client:
+ response = client.get("/items/foo")
+ assert response.status_code == 200
+ assert response.json() == {"name": "Fighters"}
diff --git a/docs_src/app_testing/tutorial004_py310.py b/docs_src/app_testing/tutorial004_py310.py
new file mode 100644
index 000000000..f83ac9ae9
--- /dev/null
+++ b/docs_src/app_testing/tutorial004_py310.py
@@ -0,0 +1,43 @@
+from contextlib import asynccontextmanager
+
+from fastapi import FastAPI
+from fastapi.testclient import TestClient
+
+items = {}
+
+
+@asynccontextmanager
+async def lifespan(app: FastAPI):
+ items["foo"] = {"name": "Fighters"}
+ items["bar"] = {"name": "Tenders"}
+ yield
+ # clean up items
+ items.clear()
+
+
+app = FastAPI(lifespan=lifespan)
+
+
+@app.get("/items/{item_id}")
+async def read_items(item_id: str):
+ return items[item_id]
+
+
+def test_read_items():
+ # Before the lifespan starts, "items" is still empty
+ assert items == {}
+
+ with TestClient(app) as client:
+ # Inside the "with TestClient" block, the lifespan starts and items added
+ assert items == {"foo": {"name": "Fighters"}, "bar": {"name": "Tenders"}}
+
+ response = client.get("/items/foo")
+ assert response.status_code == 200
+ assert response.json() == {"name": "Fighters"}
+
+ # After the requests is done, the items are still there
+ assert items == {"foo": {"name": "Fighters"}, "bar": {"name": "Tenders"}}
+
+ # The end of the "with TestClient" block simulates terminating the app, so
+ # the lifespan ends and items are cleaned up
+ assert items == {}
diff --git a/docs_src/app_testing/app_b_py39/__init__.py b/docs_src/async_tests/app_a_py310/__init__.py
similarity index 100%
rename from docs_src/app_testing/app_b_py39/__init__.py
rename to docs_src/async_tests/app_a_py310/__init__.py
diff --git a/docs_src/async_tests/app_a_py310/main.py b/docs_src/async_tests/app_a_py310/main.py
new file mode 100644
index 000000000..9594f859c
--- /dev/null
+++ b/docs_src/async_tests/app_a_py310/main.py
@@ -0,0 +1,8 @@
+from fastapi import FastAPI
+
+app = FastAPI()
+
+
+@app.get("/")
+async def root():
+ return {"message": "Tomato"}
diff --git a/docs_src/async_tests/app_a_py310/test_main.py b/docs_src/async_tests/app_a_py310/test_main.py
new file mode 100644
index 000000000..a57a31f7d
--- /dev/null
+++ b/docs_src/async_tests/app_a_py310/test_main.py
@@ -0,0 +1,14 @@
+import pytest
+from httpx import ASGITransport, AsyncClient
+
+from .main import app
+
+
+@pytest.mark.anyio
+async def test_root():
+ async with AsyncClient(
+ transport=ASGITransport(app=app), base_url="http://test"
+ ) as ac:
+ response = await ac.get("/")
+ assert response.status_code == 200
+ assert response.json() == {"message": "Tomato"}
diff --git a/docs_src/authentication_error_status_code/tutorial001_an_py310.py b/docs_src/authentication_error_status_code/tutorial001_an_py310.py
new file mode 100644
index 000000000..7bbc2f717
--- /dev/null
+++ b/docs_src/authentication_error_status_code/tutorial001_an_py310.py
@@ -0,0 +1,21 @@
+from typing import Annotated
+
+from fastapi import Depends, FastAPI, HTTPException, status
+from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
+
+app = FastAPI()
+
+
+class HTTPBearer403(HTTPBearer):
+ def make_not_authenticated_error(self) -> HTTPException:
+ return HTTPException(
+ status_code=status.HTTP_403_FORBIDDEN, detail="Not authenticated"
+ )
+
+
+CredentialsDep = Annotated[HTTPAuthorizationCredentials, Depends(HTTPBearer403())]
+
+
+@app.get("/me")
+def read_me(credentials: CredentialsDep):
+ return {"message": "You are authenticated", "token": credentials.credentials}
diff --git a/docs_src/background_tasks/tutorial001_py310.py b/docs_src/background_tasks/tutorial001_py310.py
new file mode 100644
index 000000000..1720a7433
--- /dev/null
+++ b/docs_src/background_tasks/tutorial001_py310.py
@@ -0,0 +1,15 @@
+from fastapi import BackgroundTasks, FastAPI
+
+app = FastAPI()
+
+
+def write_notification(email: str, message=""):
+ with open("log.txt", mode="w") as email_file:
+ content = f"notification for {email}: {message}"
+ email_file.write(content)
+
+
+@app.post("/send-notification/{email}")
+async def send_notification(email: str, background_tasks: BackgroundTasks):
+ background_tasks.add_task(write_notification, email, message="some notification")
+ return {"message": "Notification sent in the background"}
diff --git a/docs_src/background_tasks/tutorial002_an_py39.py b/docs_src/background_tasks/tutorial002_an_py39.py
deleted file mode 100644
index bfdd14875..000000000
--- a/docs_src/background_tasks/tutorial002_an_py39.py
+++ /dev/null
@@ -1,26 +0,0 @@
-from typing import Annotated, Union
-
-from fastapi import BackgroundTasks, Depends, FastAPI
-
-app = FastAPI()
-
-
-def write_log(message: str):
- with open("log.txt", mode="a") as log:
- log.write(message)
-
-
-def get_query(background_tasks: BackgroundTasks, q: Union[str, None] = None):
- if q:
- message = f"found query: {q}\n"
- background_tasks.add_task(write_log, message)
- return q
-
-
-@app.post("/send-notification/{email}")
-async def send_notification(
- email: str, background_tasks: BackgroundTasks, q: Annotated[str, Depends(get_query)]
-):
- message = f"message to {email}\n"
- background_tasks.add_task(write_log, message)
- return {"message": "Message sent"}
diff --git a/docs_src/background_tasks/tutorial002_py39.py b/docs_src/background_tasks/tutorial002_py39.py
deleted file mode 100644
index 2e1b2f6c6..000000000
--- a/docs_src/background_tasks/tutorial002_py39.py
+++ /dev/null
@@ -1,26 +0,0 @@
-from typing import Union
-
-from fastapi import BackgroundTasks, Depends, FastAPI
-
-app = FastAPI()
-
-
-def write_log(message: str):
- with open("log.txt", mode="a") as log:
- log.write(message)
-
-
-def get_query(background_tasks: BackgroundTasks, q: Union[str, None] = None):
- if q:
- message = f"found query: {q}\n"
- background_tasks.add_task(write_log, message)
- return q
-
-
-@app.post("/send-notification/{email}")
-async def send_notification(
- email: str, background_tasks: BackgroundTasks, q: str = Depends(get_query)
-):
- message = f"message to {email}\n"
- background_tasks.add_task(write_log, message)
- return {"message": "Message sent"}
diff --git a/docs_src/behind_a_proxy/tutorial001_01_py310.py b/docs_src/behind_a_proxy/tutorial001_01_py310.py
new file mode 100644
index 000000000..52b114395
--- /dev/null
+++ b/docs_src/behind_a_proxy/tutorial001_01_py310.py
@@ -0,0 +1,8 @@
+from fastapi import FastAPI
+
+app = FastAPI()
+
+
+@app.get("/items/")
+def read_items():
+ return ["plumbus", "portal gun"]
diff --git a/docs_src/behind_a_proxy/tutorial001_py310.py b/docs_src/behind_a_proxy/tutorial001_py310.py
new file mode 100644
index 000000000..ede59ada1
--- /dev/null
+++ b/docs_src/behind_a_proxy/tutorial001_py310.py
@@ -0,0 +1,8 @@
+from fastapi import FastAPI, Request
+
+app = FastAPI()
+
+
+@app.get("/app")
+def read_main(request: Request):
+ return {"message": "Hello World", "root_path": request.scope.get("root_path")}
diff --git a/docs_src/behind_a_proxy/tutorial002_py310.py b/docs_src/behind_a_proxy/tutorial002_py310.py
new file mode 100644
index 000000000..c1600cde9
--- /dev/null
+++ b/docs_src/behind_a_proxy/tutorial002_py310.py
@@ -0,0 +1,8 @@
+from fastapi import FastAPI, Request
+
+app = FastAPI(root_path="/api/v1")
+
+
+@app.get("/app")
+def read_main(request: Request):
+ return {"message": "Hello World", "root_path": request.scope.get("root_path")}
diff --git a/docs_src/behind_a_proxy/tutorial003_py310.py b/docs_src/behind_a_proxy/tutorial003_py310.py
new file mode 100644
index 000000000..3b7d8be01
--- /dev/null
+++ b/docs_src/behind_a_proxy/tutorial003_py310.py
@@ -0,0 +1,14 @@
+from fastapi import FastAPI, Request
+
+app = FastAPI(
+ servers=[
+ {"url": "https://stag.example.com", "description": "Staging environment"},
+ {"url": "https://prod.example.com", "description": "Production environment"},
+ ],
+ root_path="/api/v1",
+)
+
+
+@app.get("/app")
+def read_main(request: Request):
+ return {"message": "Hello World", "root_path": request.scope.get("root_path")}
diff --git a/docs_src/behind_a_proxy/tutorial004_py310.py b/docs_src/behind_a_proxy/tutorial004_py310.py
new file mode 100644
index 000000000..51bd5babc
--- /dev/null
+++ b/docs_src/behind_a_proxy/tutorial004_py310.py
@@ -0,0 +1,15 @@
+from fastapi import FastAPI, Request
+
+app = FastAPI(
+ servers=[
+ {"url": "https://stag.example.com", "description": "Staging environment"},
+ {"url": "https://prod.example.com", "description": "Production environment"},
+ ],
+ root_path="/api/v1",
+ root_path_in_servers=False,
+)
+
+
+@app.get("/app")
+def read_main(request: Request):
+ return {"message": "Hello World", "root_path": request.scope.get("root_path")}
diff --git a/docs_src/bigger_applications/app_py39/internal/__init__.py b/docs_src/bigger_applications/app_an_py310/__init__.py
similarity index 100%
rename from docs_src/bigger_applications/app_py39/internal/__init__.py
rename to docs_src/bigger_applications/app_an_py310/__init__.py
diff --git a/docs_src/bigger_applications/app_an_py310/dependencies.py b/docs_src/bigger_applications/app_an_py310/dependencies.py
new file mode 100644
index 000000000..5c7612aa0
--- /dev/null
+++ b/docs_src/bigger_applications/app_an_py310/dependencies.py
@@ -0,0 +1,13 @@
+from typing import Annotated
+
+from fastapi import Header, HTTPException
+
+
+async def get_token_header(x_token: Annotated[str, Header()]):
+ if x_token != "fake-super-secret-token":
+ raise HTTPException(status_code=400, detail="X-Token header invalid")
+
+
+async def get_query_token(token: str):
+ if token != "jessica":
+ raise HTTPException(status_code=400, detail="No Jessica token provided")
diff --git a/docs_src/bigger_applications/app_py39/routers/__init__.py b/docs_src/bigger_applications/app_an_py310/internal/__init__.py
similarity index 100%
rename from docs_src/bigger_applications/app_py39/routers/__init__.py
rename to docs_src/bigger_applications/app_an_py310/internal/__init__.py
diff --git a/docs_src/bigger_applications/app_py39/internal/admin.py b/docs_src/bigger_applications/app_an_py310/internal/admin.py
similarity index 100%
rename from docs_src/bigger_applications/app_py39/internal/admin.py
rename to docs_src/bigger_applications/app_an_py310/internal/admin.py
diff --git a/docs_src/bigger_applications/app_an_py310/main.py b/docs_src/bigger_applications/app_an_py310/main.py
new file mode 100644
index 000000000..ae544a3aa
--- /dev/null
+++ b/docs_src/bigger_applications/app_an_py310/main.py
@@ -0,0 +1,23 @@
+from fastapi import Depends, FastAPI
+
+from .dependencies import get_query_token, get_token_header
+from .internal import admin
+from .routers import items, users
+
+app = FastAPI(dependencies=[Depends(get_query_token)])
+
+
+app.include_router(users.router)
+app.include_router(items.router)
+app.include_router(
+ admin.router,
+ prefix="/admin",
+ tags=["admin"],
+ dependencies=[Depends(get_token_header)],
+ responses={418: {"description": "I'm a teapot"}},
+)
+
+
+@app.get("/")
+async def root():
+ return {"message": "Hello Bigger Applications!"}
diff --git a/docs_src/bigger_applications/app_an_py310/routers/__init__.py b/docs_src/bigger_applications/app_an_py310/routers/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/bigger_applications/app_py39/routers/items.py b/docs_src/bigger_applications/app_an_py310/routers/items.py
similarity index 100%
rename from docs_src/bigger_applications/app_py39/routers/items.py
rename to docs_src/bigger_applications/app_an_py310/routers/items.py
diff --git a/docs_src/bigger_applications/app_py39/routers/users.py b/docs_src/bigger_applications/app_an_py310/routers/users.py
similarity index 100%
rename from docs_src/bigger_applications/app_py39/routers/users.py
rename to docs_src/bigger_applications/app_an_py310/routers/users.py
diff --git a/docs_src/body/tutorial001_py39.py b/docs_src/body/tutorial001_py39.py
deleted file mode 100644
index f93317274..000000000
--- a/docs_src/body/tutorial001_py39.py
+++ /dev/null
@@ -1,19 +0,0 @@
-from typing import Union
-
-from fastapi import FastAPI
-from pydantic import BaseModel
-
-
-class Item(BaseModel):
- name: str
- description: Union[str, None] = None
- price: float
- tax: Union[float, None] = None
-
-
-app = FastAPI()
-
-
-@app.post("/items/")
-async def create_item(item: Item):
- return item
diff --git a/docs_src/body/tutorial002_py39.py b/docs_src/body/tutorial002_py39.py
deleted file mode 100644
index fb212e8e7..000000000
--- a/docs_src/body/tutorial002_py39.py
+++ /dev/null
@@ -1,23 +0,0 @@
-from typing import Union
-
-from fastapi import FastAPI
-from pydantic import BaseModel
-
-
-class Item(BaseModel):
- name: str
- description: Union[str, None] = None
- price: float
- tax: Union[float, None] = None
-
-
-app = FastAPI()
-
-
-@app.post("/items/")
-async def create_item(item: Item):
- item_dict = item.model_dump()
- if item.tax is not None:
- price_with_tax = item.price + item.tax
- item_dict.update({"price_with_tax": price_with_tax})
- return item_dict
diff --git a/docs_src/body/tutorial003_py39.py b/docs_src/body/tutorial003_py39.py
deleted file mode 100644
index 636ba2275..000000000
--- a/docs_src/body/tutorial003_py39.py
+++ /dev/null
@@ -1,19 +0,0 @@
-from typing import Union
-
-from fastapi import FastAPI
-from pydantic import BaseModel
-
-
-class Item(BaseModel):
- name: str
- description: Union[str, None] = None
- price: float
- tax: Union[float, None] = None
-
-
-app = FastAPI()
-
-
-@app.put("/items/{item_id}")
-async def update_item(item_id: int, item: Item):
- return {"item_id": item_id, **item.model_dump()}
diff --git a/docs_src/body/tutorial004_py39.py b/docs_src/body/tutorial004_py39.py
deleted file mode 100644
index 2c157abfa..000000000
--- a/docs_src/body/tutorial004_py39.py
+++ /dev/null
@@ -1,22 +0,0 @@
-from typing import Union
-
-from fastapi import FastAPI
-from pydantic import BaseModel
-
-
-class Item(BaseModel):
- name: str
- description: Union[str, None] = None
- price: float
- tax: Union[float, None] = None
-
-
-app = FastAPI()
-
-
-@app.put("/items/{item_id}")
-async def update_item(item_id: int, item: Item, q: Union[str, None] = None):
- result = {"item_id": item_id, **item.model_dump()}
- if q:
- result.update({"q": q})
- return result
diff --git a/docs_src/body_fields/tutorial001_an_py39.py b/docs_src/body_fields/tutorial001_an_py39.py
deleted file mode 100644
index 6ef14470c..000000000
--- a/docs_src/body_fields/tutorial001_an_py39.py
+++ /dev/null
@@ -1,21 +0,0 @@
-from typing import Annotated, Union
-
-from fastapi import Body, FastAPI
-from pydantic import BaseModel, Field
-
-app = FastAPI()
-
-
-class Item(BaseModel):
- name: str
- description: Union[str, None] = Field(
- default=None, title="The description of the item", max_length=300
- )
- price: float = Field(gt=0, description="The price must be greater than zero")
- tax: Union[float, None] = None
-
-
-@app.put("/items/{item_id}")
-async def update_item(item_id: int, item: Annotated[Item, Body(embed=True)]):
- results = {"item_id": item_id, "item": item}
- return results
diff --git a/docs_src/body_fields/tutorial001_py39.py b/docs_src/body_fields/tutorial001_py39.py
deleted file mode 100644
index cbeebd614..000000000
--- a/docs_src/body_fields/tutorial001_py39.py
+++ /dev/null
@@ -1,21 +0,0 @@
-from typing import Union
-
-from fastapi import Body, FastAPI
-from pydantic import BaseModel, Field
-
-app = FastAPI()
-
-
-class Item(BaseModel):
- name: str
- description: Union[str, None] = Field(
- default=None, title="The description of the item", max_length=300
- )
- price: float = Field(gt=0, description="The price must be greater than zero")
- tax: Union[float, None] = None
-
-
-@app.put("/items/{item_id}")
-async def update_item(item_id: int, item: Item = Body(embed=True)):
- results = {"item_id": item_id, "item": item}
- return results
diff --git a/docs_src/body_multiple_params/tutorial001_an_py39.py b/docs_src/body_multiple_params/tutorial001_an_py39.py
deleted file mode 100644
index 1c0ac3a7b..000000000
--- a/docs_src/body_multiple_params/tutorial001_an_py39.py
+++ /dev/null
@@ -1,27 +0,0 @@
-from typing import Annotated, Union
-
-from fastapi import FastAPI, Path
-from pydantic import BaseModel
-
-app = FastAPI()
-
-
-class Item(BaseModel):
- name: str
- description: Union[str, None] = None
- price: float
- tax: Union[float, None] = None
-
-
-@app.put("/items/{item_id}")
-async def update_item(
- item_id: Annotated[int, Path(title="The ID of the item to get", ge=0, le=1000)],
- q: Union[str, None] = None,
- item: Union[Item, None] = None,
-):
- results = {"item_id": item_id}
- if q:
- results.update({"q": q})
- if item:
- results.update({"item": item})
- return results
diff --git a/docs_src/body_multiple_params/tutorial001_py39.py b/docs_src/body_multiple_params/tutorial001_py39.py
deleted file mode 100644
index a73975b3a..000000000
--- a/docs_src/body_multiple_params/tutorial001_py39.py
+++ /dev/null
@@ -1,28 +0,0 @@
-from typing import Union
-
-from fastapi import FastAPI, Path
-from pydantic import BaseModel
-
-app = FastAPI()
-
-
-class Item(BaseModel):
- name: str
- description: Union[str, None] = None
- price: float
- tax: Union[float, None] = None
-
-
-@app.put("/items/{item_id}")
-async def update_item(
- *,
- item_id: int = Path(title="The ID of the item to get", ge=0, le=1000),
- q: Union[str, None] = None,
- item: Union[Item, None] = None,
-):
- results = {"item_id": item_id}
- if q:
- results.update({"q": q})
- if item:
- results.update({"item": item})
- return results
diff --git a/docs_src/body_multiple_params/tutorial002_py39.py b/docs_src/body_multiple_params/tutorial002_py39.py
deleted file mode 100644
index 2d7160ae8..000000000
--- a/docs_src/body_multiple_params/tutorial002_py39.py
+++ /dev/null
@@ -1,24 +0,0 @@
-from typing import Union
-
-from fastapi import FastAPI
-from pydantic import BaseModel
-
-app = FastAPI()
-
-
-class Item(BaseModel):
- name: str
- description: Union[str, None] = None
- price: float
- tax: Union[float, None] = None
-
-
-class User(BaseModel):
- username: str
- full_name: Union[str, None] = None
-
-
-@app.put("/items/{item_id}")
-async def update_item(item_id: int, item: Item, user: User):
- results = {"item_id": item_id, "item": item, "user": user}
- return results
diff --git a/docs_src/body_multiple_params/tutorial003_an_py39.py b/docs_src/body_multiple_params/tutorial003_an_py39.py
deleted file mode 100644
index 042351e0b..000000000
--- a/docs_src/body_multiple_params/tutorial003_an_py39.py
+++ /dev/null
@@ -1,26 +0,0 @@
-from typing import Annotated, Union
-
-from fastapi import Body, FastAPI
-from pydantic import BaseModel
-
-app = FastAPI()
-
-
-class Item(BaseModel):
- name: str
- description: Union[str, None] = None
- price: float
- tax: Union[float, None] = None
-
-
-class User(BaseModel):
- username: str
- full_name: Union[str, None] = None
-
-
-@app.put("/items/{item_id}")
-async def update_item(
- item_id: int, item: Item, user: User, importance: Annotated[int, Body()]
-):
- results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
- return results
diff --git a/docs_src/body_multiple_params/tutorial003_py39.py b/docs_src/body_multiple_params/tutorial003_py39.py
deleted file mode 100644
index cf344e6c5..000000000
--- a/docs_src/body_multiple_params/tutorial003_py39.py
+++ /dev/null
@@ -1,24 +0,0 @@
-from typing import Union
-
-from fastapi import Body, FastAPI
-from pydantic import BaseModel
-
-app = FastAPI()
-
-
-class Item(BaseModel):
- name: str
- description: Union[str, None] = None
- price: float
- tax: Union[float, None] = None
-
-
-class User(BaseModel):
- username: str
- full_name: Union[str, None] = None
-
-
-@app.put("/items/{item_id}")
-async def update_item(item_id: int, item: Item, user: User, importance: int = Body()):
- results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
- return results
diff --git a/docs_src/body_multiple_params/tutorial004_an_py39.py b/docs_src/body_multiple_params/tutorial004_an_py39.py
deleted file mode 100644
index 567427c03..000000000
--- a/docs_src/body_multiple_params/tutorial004_an_py39.py
+++ /dev/null
@@ -1,33 +0,0 @@
-from typing import Annotated, Union
-
-from fastapi import Body, FastAPI
-from pydantic import BaseModel
-
-app = FastAPI()
-
-
-class Item(BaseModel):
- name: str
- description: Union[str, None] = None
- price: float
- tax: Union[float, None] = None
-
-
-class User(BaseModel):
- username: str
- full_name: Union[str, None] = None
-
-
-@app.put("/items/{item_id}")
-async def update_item(
- *,
- item_id: int,
- item: Item,
- user: User,
- importance: Annotated[int, Body(gt=0)],
- q: Union[str, None] = None,
-):
- results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
- if q:
- results.update({"q": q})
- return results
diff --git a/docs_src/body_multiple_params/tutorial004_py39.py b/docs_src/body_multiple_params/tutorial004_py39.py
deleted file mode 100644
index 8ce4c7a97..000000000
--- a/docs_src/body_multiple_params/tutorial004_py39.py
+++ /dev/null
@@ -1,33 +0,0 @@
-from typing import Union
-
-from fastapi import Body, FastAPI
-from pydantic import BaseModel
-
-app = FastAPI()
-
-
-class Item(BaseModel):
- name: str
- description: Union[str, None] = None
- price: float
- tax: Union[float, None] = None
-
-
-class User(BaseModel):
- username: str
- full_name: Union[str, None] = None
-
-
-@app.put("/items/{item_id}")
-async def update_item(
- *,
- item_id: int,
- item: Item,
- user: User,
- importance: int = Body(gt=0),
- q: Union[str, None] = None,
-):
- results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
- if q:
- results.update({"q": q})
- return results
diff --git a/docs_src/body_multiple_params/tutorial005_an_py39.py b/docs_src/body_multiple_params/tutorial005_an_py39.py
deleted file mode 100644
index 9a52425c1..000000000
--- a/docs_src/body_multiple_params/tutorial005_an_py39.py
+++ /dev/null
@@ -1,19 +0,0 @@
-from typing import Annotated, Union
-
-from fastapi import Body, FastAPI
-from pydantic import BaseModel
-
-app = FastAPI()
-
-
-class Item(BaseModel):
- name: str
- description: Union[str, None] = None
- price: float
- tax: Union[float, None] = None
-
-
-@app.put("/items/{item_id}")
-async def update_item(item_id: int, item: Annotated[Item, Body(embed=True)]):
- results = {"item_id": item_id, "item": item}
- return results
diff --git a/docs_src/body_multiple_params/tutorial005_py39.py b/docs_src/body_multiple_params/tutorial005_py39.py
deleted file mode 100644
index 29e6e14b7..000000000
--- a/docs_src/body_multiple_params/tutorial005_py39.py
+++ /dev/null
@@ -1,19 +0,0 @@
-from typing import Union
-
-from fastapi import Body, FastAPI
-from pydantic import BaseModel
-
-app = FastAPI()
-
-
-class Item(BaseModel):
- name: str
- description: Union[str, None] = None
- price: float
- tax: Union[float, None] = None
-
-
-@app.put("/items/{item_id}")
-async def update_item(item_id: int, item: Item = Body(embed=True)):
- results = {"item_id": item_id, "item": item}
- return results
diff --git a/docs_src/body_nested_models/tutorial001_py39.py b/docs_src/body_nested_models/tutorial001_py39.py
deleted file mode 100644
index 37ef6dda5..000000000
--- a/docs_src/body_nested_models/tutorial001_py39.py
+++ /dev/null
@@ -1,20 +0,0 @@
-from typing import Union
-
-from fastapi import FastAPI
-from pydantic import BaseModel
-
-app = FastAPI()
-
-
-class Item(BaseModel):
- name: str
- description: Union[str, None] = None
- price: float
- tax: Union[float, None] = None
- tags: list = []
-
-
-@app.put("/items/{item_id}")
-async def update_item(item_id: int, item: Item):
- results = {"item_id": item_id, "item": item}
- return results
diff --git a/docs_src/body_nested_models/tutorial002_py39.py b/docs_src/body_nested_models/tutorial002_py39.py
deleted file mode 100644
index 8a93a7233..000000000
--- a/docs_src/body_nested_models/tutorial002_py39.py
+++ /dev/null
@@ -1,20 +0,0 @@
-from typing import Union
-
-from fastapi import FastAPI
-from pydantic import BaseModel
-
-app = FastAPI()
-
-
-class Item(BaseModel):
- name: str
- description: Union[str, None] = None
- price: float
- tax: Union[float, None] = None
- tags: list[str] = []
-
-
-@app.put("/items/{item_id}")
-async def update_item(item_id: int, item: Item):
- results = {"item_id": item_id, "item": item}
- return results
diff --git a/docs_src/body_nested_models/tutorial003_py39.py b/docs_src/body_nested_models/tutorial003_py39.py
deleted file mode 100644
index b590ece36..000000000
--- a/docs_src/body_nested_models/tutorial003_py39.py
+++ /dev/null
@@ -1,20 +0,0 @@
-from typing import Union
-
-from fastapi import FastAPI
-from pydantic import BaseModel
-
-app = FastAPI()
-
-
-class Item(BaseModel):
- name: str
- description: Union[str, None] = None
- price: float
- tax: Union[float, None] = None
- tags: set[str] = set()
-
-
-@app.put("/items/{item_id}")
-async def update_item(item_id: int, item: Item):
- results = {"item_id": item_id, "item": item}
- return results
diff --git a/docs_src/body_nested_models/tutorial004_py39.py b/docs_src/body_nested_models/tutorial004_py39.py
deleted file mode 100644
index dc2b175fb..000000000
--- a/docs_src/body_nested_models/tutorial004_py39.py
+++ /dev/null
@@ -1,26 +0,0 @@
-from typing import Union
-
-from fastapi import FastAPI
-from pydantic import BaseModel
-
-app = FastAPI()
-
-
-class Image(BaseModel):
- url: str
- name: str
-
-
-class Item(BaseModel):
- name: str
- description: Union[str, None] = None
- price: float
- tax: Union[float, None] = None
- tags: set[str] = set()
- image: Union[Image, None] = None
-
-
-@app.put("/items/{item_id}")
-async def update_item(item_id: int, item: Item):
- results = {"item_id": item_id, "item": item}
- return results
diff --git a/docs_src/body_nested_models/tutorial005_py39.py b/docs_src/body_nested_models/tutorial005_py39.py
deleted file mode 100644
index 47db90008..000000000
--- a/docs_src/body_nested_models/tutorial005_py39.py
+++ /dev/null
@@ -1,26 +0,0 @@
-from typing import Union
-
-from fastapi import FastAPI
-from pydantic import BaseModel, HttpUrl
-
-app = FastAPI()
-
-
-class Image(BaseModel):
- url: HttpUrl
- name: str
-
-
-class Item(BaseModel):
- name: str
- description: Union[str, None] = None
- price: float
- tax: Union[float, None] = None
- tags: set[str] = set()
- image: Union[Image, None] = None
-
-
-@app.put("/items/{item_id}")
-async def update_item(item_id: int, item: Item):
- results = {"item_id": item_id, "item": item}
- return results
diff --git a/docs_src/body_nested_models/tutorial006_py39.py b/docs_src/body_nested_models/tutorial006_py39.py
deleted file mode 100644
index b14409703..000000000
--- a/docs_src/body_nested_models/tutorial006_py39.py
+++ /dev/null
@@ -1,26 +0,0 @@
-from typing import Union
-
-from fastapi import FastAPI
-from pydantic import BaseModel, HttpUrl
-
-app = FastAPI()
-
-
-class Image(BaseModel):
- url: HttpUrl
- name: str
-
-
-class Item(BaseModel):
- name: str
- description: Union[str, None] = None
- price: float
- tax: Union[float, None] = None
- tags: set[str] = set()
- images: Union[list[Image], None] = None
-
-
-@app.put("/items/{item_id}")
-async def update_item(item_id: int, item: Item):
- results = {"item_id": item_id, "item": item}
- return results
diff --git a/docs_src/body_nested_models/tutorial007_py39.py b/docs_src/body_nested_models/tutorial007_py39.py
deleted file mode 100644
index 59cf01e23..000000000
--- a/docs_src/body_nested_models/tutorial007_py39.py
+++ /dev/null
@@ -1,32 +0,0 @@
-from typing import Union
-
-from fastapi import FastAPI
-from pydantic import BaseModel, HttpUrl
-
-app = FastAPI()
-
-
-class Image(BaseModel):
- url: HttpUrl
- name: str
-
-
-class Item(BaseModel):
- name: str
- description: Union[str, None] = None
- price: float
- tax: Union[float, None] = None
- tags: set[str] = set()
- images: Union[list[Image], None] = None
-
-
-class Offer(BaseModel):
- name: str
- description: Union[str, None] = None
- price: float
- items: list[Item]
-
-
-@app.post("/offers/")
-async def create_offer(offer: Offer):
- return offer
diff --git a/docs_src/body_nested_models/tutorial008_py310.py b/docs_src/body_nested_models/tutorial008_py310.py
new file mode 100644
index 000000000..854a7a5a4
--- /dev/null
+++ b/docs_src/body_nested_models/tutorial008_py310.py
@@ -0,0 +1,14 @@
+from fastapi import FastAPI
+from pydantic import BaseModel, HttpUrl
+
+app = FastAPI()
+
+
+class Image(BaseModel):
+ url: HttpUrl
+ name: str
+
+
+@app.post("/images/multiple/")
+async def create_multiple_images(images: list[Image]):
+ return images
diff --git a/docs_src/body_nested_models/tutorial009_py310.py b/docs_src/body_nested_models/tutorial009_py310.py
new file mode 100644
index 000000000..59c1e5082
--- /dev/null
+++ b/docs_src/body_nested_models/tutorial009_py310.py
@@ -0,0 +1,8 @@
+from fastapi import FastAPI
+
+app = FastAPI()
+
+
+@app.post("/index-weights/")
+async def create_index_weights(weights: dict[int, float]):
+ return weights
diff --git a/docs_src/body_updates/tutorial001_py39.py b/docs_src/body_updates/tutorial001_py39.py
deleted file mode 100644
index 999bcdb82..000000000
--- a/docs_src/body_updates/tutorial001_py39.py
+++ /dev/null
@@ -1,34 +0,0 @@
-from typing import Union
-
-from fastapi import FastAPI
-from fastapi.encoders import jsonable_encoder
-from pydantic import BaseModel
-
-app = FastAPI()
-
-
-class Item(BaseModel):
- name: Union[str, None] = None
- description: Union[str, None] = None
- price: Union[float, None] = None
- tax: float = 10.5
- tags: list[str] = []
-
-
-items = {
- "foo": {"name": "Foo", "price": 50.2},
- "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
- "baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
-}
-
-
-@app.get("/items/{item_id}", response_model=Item)
-async def read_item(item_id: str):
- return items[item_id]
-
-
-@app.put("/items/{item_id}", response_model=Item)
-async def update_item(item_id: str, item: Item):
- update_item_encoded = jsonable_encoder(item)
- items[item_id] = update_item_encoded
- return update_item_encoded
diff --git a/docs_src/body_updates/tutorial002_py39.py b/docs_src/body_updates/tutorial002_py39.py
deleted file mode 100644
index 3714b5a55..000000000
--- a/docs_src/body_updates/tutorial002_py39.py
+++ /dev/null
@@ -1,37 +0,0 @@
-from typing import Union
-
-from fastapi import FastAPI
-from fastapi.encoders import jsonable_encoder
-from pydantic import BaseModel
-
-app = FastAPI()
-
-
-class Item(BaseModel):
- name: Union[str, None] = None
- description: Union[str, None] = None
- price: Union[float, None] = None
- tax: float = 10.5
- tags: list[str] = []
-
-
-items = {
- "foo": {"name": "Foo", "price": 50.2},
- "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
- "baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
-}
-
-
-@app.get("/items/{item_id}", response_model=Item)
-async def read_item(item_id: str):
- return items[item_id]
-
-
-@app.patch("/items/{item_id}")
-async def update_item(item_id: str, item: Item) -> Item:
- stored_item_data = items[item_id]
- stored_item_model = Item(**stored_item_data)
- update_data = item.model_dump(exclude_unset=True)
- updated_item = stored_item_model.model_copy(update=update_data)
- items[item_id] = jsonable_encoder(updated_item)
- return updated_item
diff --git a/docs_src/conditional_openapi/tutorial001_py310.py b/docs_src/conditional_openapi/tutorial001_py310.py
new file mode 100644
index 000000000..eedb0d274
--- /dev/null
+++ b/docs_src/conditional_openapi/tutorial001_py310.py
@@ -0,0 +1,16 @@
+from fastapi import FastAPI
+from pydantic_settings import BaseSettings
+
+
+class Settings(BaseSettings):
+ openapi_url: str = "/openapi.json"
+
+
+settings = Settings()
+
+app = FastAPI(openapi_url=settings.openapi_url)
+
+
+@app.get("/")
+def root():
+ return {"message": "Hello World"}
diff --git a/docs_src/configure_swagger_ui/tutorial001_py310.py b/docs_src/configure_swagger_ui/tutorial001_py310.py
new file mode 100644
index 000000000..6c24ce758
--- /dev/null
+++ b/docs_src/configure_swagger_ui/tutorial001_py310.py
@@ -0,0 +1,8 @@
+from fastapi import FastAPI
+
+app = FastAPI(swagger_ui_parameters={"syntaxHighlight": False})
+
+
+@app.get("/users/{username}")
+async def read_user(username: str):
+ return {"message": f"Hello {username}"}
diff --git a/docs_src/configure_swagger_ui/tutorial002_py310.py b/docs_src/configure_swagger_ui/tutorial002_py310.py
new file mode 100644
index 000000000..cc75c2196
--- /dev/null
+++ b/docs_src/configure_swagger_ui/tutorial002_py310.py
@@ -0,0 +1,8 @@
+from fastapi import FastAPI
+
+app = FastAPI(swagger_ui_parameters={"syntaxHighlight": {"theme": "obsidian"}})
+
+
+@app.get("/users/{username}")
+async def read_user(username: str):
+ return {"message": f"Hello {username}"}
diff --git a/docs_src/configure_swagger_ui/tutorial003_py310.py b/docs_src/configure_swagger_ui/tutorial003_py310.py
new file mode 100644
index 000000000..b4449f5c6
--- /dev/null
+++ b/docs_src/configure_swagger_ui/tutorial003_py310.py
@@ -0,0 +1,8 @@
+from fastapi import FastAPI
+
+app = FastAPI(swagger_ui_parameters={"deepLinking": False})
+
+
+@app.get("/users/{username}")
+async def read_user(username: str):
+ return {"message": f"Hello {username}"}
diff --git a/docs_src/cookie_param_models/tutorial001_an_py39.py b/docs_src/cookie_param_models/tutorial001_an_py39.py
deleted file mode 100644
index 3d90c2007..000000000
--- a/docs_src/cookie_param_models/tutorial001_an_py39.py
+++ /dev/null
@@ -1,17 +0,0 @@
-from typing import Annotated, Union
-
-from fastapi import Cookie, FastAPI
-from pydantic import BaseModel
-
-app = FastAPI()
-
-
-class Cookies(BaseModel):
- session_id: str
- fatebook_tracker: Union[str, None] = None
- googall_tracker: Union[str, None] = None
-
-
-@app.get("/items/")
-async def read_items(cookies: Annotated[Cookies, Cookie()]):
- return cookies
diff --git a/docs_src/cookie_param_models/tutorial001_py39.py b/docs_src/cookie_param_models/tutorial001_py39.py
deleted file mode 100644
index cc65c43e1..000000000
--- a/docs_src/cookie_param_models/tutorial001_py39.py
+++ /dev/null
@@ -1,17 +0,0 @@
-from typing import Union
-
-from fastapi import Cookie, FastAPI
-from pydantic import BaseModel
-
-app = FastAPI()
-
-
-class Cookies(BaseModel):
- session_id: str
- fatebook_tracker: Union[str, None] = None
- googall_tracker: Union[str, None] = None
-
-
-@app.get("/items/")
-async def read_items(cookies: Cookies = Cookie()):
- return cookies
diff --git a/docs_src/cookie_param_models/tutorial002_an_py39.py b/docs_src/cookie_param_models/tutorial002_an_py39.py
deleted file mode 100644
index a906ce6a1..000000000
--- a/docs_src/cookie_param_models/tutorial002_an_py39.py
+++ /dev/null
@@ -1,19 +0,0 @@
-from typing import Annotated, Union
-
-from fastapi import Cookie, FastAPI
-from pydantic import BaseModel
-
-app = FastAPI()
-
-
-class Cookies(BaseModel):
- model_config = {"extra": "forbid"}
-
- session_id: str
- fatebook_tracker: Union[str, None] = None
- googall_tracker: Union[str, None] = None
-
-
-@app.get("/items/")
-async def read_items(cookies: Annotated[Cookies, Cookie()]):
- return cookies
diff --git a/docs_src/cookie_param_models/tutorial002_py39.py b/docs_src/cookie_param_models/tutorial002_py39.py
deleted file mode 100644
index 9679e890f..000000000
--- a/docs_src/cookie_param_models/tutorial002_py39.py
+++ /dev/null
@@ -1,19 +0,0 @@
-from typing import Union
-
-from fastapi import Cookie, FastAPI
-from pydantic import BaseModel
-
-app = FastAPI()
-
-
-class Cookies(BaseModel):
- model_config = {"extra": "forbid"}
-
- session_id: str
- fatebook_tracker: Union[str, None] = None
- googall_tracker: Union[str, None] = None
-
-
-@app.get("/items/")
-async def read_items(cookies: Cookies = Cookie()):
- return cookies
diff --git a/docs_src/cookie_params/tutorial001_an_py39.py b/docs_src/cookie_params/tutorial001_an_py39.py
deleted file mode 100644
index e18d0a332..000000000
--- a/docs_src/cookie_params/tutorial001_an_py39.py
+++ /dev/null
@@ -1,10 +0,0 @@
-from typing import Annotated, Union
-
-from fastapi import Cookie, FastAPI
-
-app = FastAPI()
-
-
-@app.get("/items/")
-async def read_items(ads_id: Annotated[Union[str, None], Cookie()] = None):
- return {"ads_id": ads_id}
diff --git a/docs_src/cookie_params/tutorial001_py39.py b/docs_src/cookie_params/tutorial001_py39.py
deleted file mode 100644
index c4a497fda..000000000
--- a/docs_src/cookie_params/tutorial001_py39.py
+++ /dev/null
@@ -1,10 +0,0 @@
-from typing import Union
-
-from fastapi import Cookie, FastAPI
-
-app = FastAPI()
-
-
-@app.get("/items/")
-async def read_items(ads_id: Union[str, None] = Cookie(default=None)):
- return {"ads_id": ads_id}
diff --git a/docs_src/cors/tutorial001_py310.py b/docs_src/cors/tutorial001_py310.py
new file mode 100644
index 000000000..d59ab27ac
--- /dev/null
+++ b/docs_src/cors/tutorial001_py310.py
@@ -0,0 +1,24 @@
+from fastapi import FastAPI
+from fastapi.middleware.cors import CORSMiddleware
+
+app = FastAPI()
+
+origins = [
+ "http://localhost.tiangolo.com",
+ "https://localhost.tiangolo.com",
+ "http://localhost",
+ "http://localhost:8080",
+]
+
+app.add_middleware(
+ CORSMiddleware,
+ allow_origins=origins,
+ allow_credentials=True,
+ allow_methods=["*"],
+ allow_headers=["*"],
+)
+
+
+@app.get("/")
+async def main():
+ return {"message": "Hello World"}
diff --git a/docs_src/custom_docs_ui/tutorial001_py310.py b/docs_src/custom_docs_ui/tutorial001_py310.py
new file mode 100644
index 000000000..1cfcce19a
--- /dev/null
+++ b/docs_src/custom_docs_ui/tutorial001_py310.py
@@ -0,0 +1,38 @@
+from fastapi import FastAPI
+from fastapi.openapi.docs import (
+ get_redoc_html,
+ get_swagger_ui_html,
+ get_swagger_ui_oauth2_redirect_html,
+)
+
+app = FastAPI(docs_url=None, redoc_url=None)
+
+
+@app.get("/docs", include_in_schema=False)
+async def custom_swagger_ui_html():
+ return get_swagger_ui_html(
+ openapi_url=app.openapi_url,
+ title=app.title + " - Swagger UI",
+ oauth2_redirect_url=app.swagger_ui_oauth2_redirect_url,
+ swagger_js_url="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js",
+ swagger_css_url="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css",
+ )
+
+
+@app.get(app.swagger_ui_oauth2_redirect_url, include_in_schema=False)
+async def swagger_ui_redirect():
+ return get_swagger_ui_oauth2_redirect_html()
+
+
+@app.get("/redoc", include_in_schema=False)
+async def redoc_html():
+ return get_redoc_html(
+ openapi_url=app.openapi_url,
+ title=app.title + " - ReDoc",
+ redoc_js_url="https://unpkg.com/redoc@2/bundles/redoc.standalone.js",
+ )
+
+
+@app.get("/users/{username}")
+async def read_user(username: str):
+ return {"message": f"Hello {username}"}
diff --git a/docs_src/custom_docs_ui/tutorial002_py310.py b/docs_src/custom_docs_ui/tutorial002_py310.py
new file mode 100644
index 000000000..23ea368f8
--- /dev/null
+++ b/docs_src/custom_docs_ui/tutorial002_py310.py
@@ -0,0 +1,41 @@
+from fastapi import FastAPI
+from fastapi.openapi.docs import (
+ get_redoc_html,
+ get_swagger_ui_html,
+ get_swagger_ui_oauth2_redirect_html,
+)
+from fastapi.staticfiles import StaticFiles
+
+app = FastAPI(docs_url=None, redoc_url=None)
+
+app.mount("/static", StaticFiles(directory="static"), name="static")
+
+
+@app.get("/docs", include_in_schema=False)
+async def custom_swagger_ui_html():
+ return get_swagger_ui_html(
+ openapi_url=app.openapi_url,
+ title=app.title + " - Swagger UI",
+ oauth2_redirect_url=app.swagger_ui_oauth2_redirect_url,
+ swagger_js_url="/static/swagger-ui-bundle.js",
+ swagger_css_url="/static/swagger-ui.css",
+ )
+
+
+@app.get(app.swagger_ui_oauth2_redirect_url, include_in_schema=False)
+async def swagger_ui_redirect():
+ return get_swagger_ui_oauth2_redirect_html()
+
+
+@app.get("/redoc", include_in_schema=False)
+async def redoc_html():
+ return get_redoc_html(
+ openapi_url=app.openapi_url,
+ title=app.title + " - ReDoc",
+ redoc_js_url="/static/redoc.standalone.js",
+ )
+
+
+@app.get("/users/{username}")
+async def read_user(username: str):
+ return {"message": f"Hello {username}"}
diff --git a/docs_src/custom_request_and_route/tutorial001_an_py39.py b/docs_src/custom_request_and_route/tutorial001_an_py39.py
deleted file mode 100644
index 076727e64..000000000
--- a/docs_src/custom_request_and_route/tutorial001_an_py39.py
+++ /dev/null
@@ -1,35 +0,0 @@
-import gzip
-from typing import Annotated, Callable
-
-from fastapi import Body, FastAPI, Request, Response
-from fastapi.routing import APIRoute
-
-
-class GzipRequest(Request):
- async def body(self) -> bytes:
- if not hasattr(self, "_body"):
- body = await super().body()
- if "gzip" in self.headers.getlist("Content-Encoding"):
- body = gzip.decompress(body)
- self._body = body
- return self._body
-
-
-class GzipRoute(APIRoute):
- def get_route_handler(self) -> Callable:
- original_route_handler = super().get_route_handler()
-
- async def custom_route_handler(request: Request) -> Response:
- request = GzipRequest(request.scope, request.receive)
- return await original_route_handler(request)
-
- return custom_route_handler
-
-
-app = FastAPI()
-app.router.route_class = GzipRoute
-
-
-@app.post("/sum")
-async def sum_numbers(numbers: Annotated[list[int], Body()]):
- return {"sum": sum(numbers)}
diff --git a/docs_src/custom_request_and_route/tutorial001_py39.py b/docs_src/custom_request_and_route/tutorial001_py39.py
deleted file mode 100644
index 54b20b942..000000000
--- a/docs_src/custom_request_and_route/tutorial001_py39.py
+++ /dev/null
@@ -1,35 +0,0 @@
-import gzip
-from typing import Callable
-
-from fastapi import Body, FastAPI, Request, Response
-from fastapi.routing import APIRoute
-
-
-class GzipRequest(Request):
- async def body(self) -> bytes:
- if not hasattr(self, "_body"):
- body = await super().body()
- if "gzip" in self.headers.getlist("Content-Encoding"):
- body = gzip.decompress(body)
- self._body = body
- return self._body
-
-
-class GzipRoute(APIRoute):
- def get_route_handler(self) -> Callable:
- original_route_handler = super().get_route_handler()
-
- async def custom_route_handler(request: Request) -> Response:
- request = GzipRequest(request.scope, request.receive)
- return await original_route_handler(request)
-
- return custom_route_handler
-
-
-app = FastAPI()
-app.router.route_class = GzipRoute
-
-
-@app.post("/sum")
-async def sum_numbers(numbers: list[int] = Body()):
- return {"sum": sum(numbers)}
diff --git a/docs_src/custom_request_and_route/tutorial002_an_py39.py b/docs_src/custom_request_and_route/tutorial002_an_py39.py
deleted file mode 100644
index e7de09de4..000000000
--- a/docs_src/custom_request_and_route/tutorial002_an_py39.py
+++ /dev/null
@@ -1,29 +0,0 @@
-from typing import Annotated, Callable
-
-from fastapi import Body, FastAPI, HTTPException, Request, Response
-from fastapi.exceptions import RequestValidationError
-from fastapi.routing import APIRoute
-
-
-class ValidationErrorLoggingRoute(APIRoute):
- def get_route_handler(self) -> Callable:
- original_route_handler = super().get_route_handler()
-
- async def custom_route_handler(request: Request) -> Response:
- try:
- return await original_route_handler(request)
- except RequestValidationError as exc:
- body = await request.body()
- detail = {"errors": exc.errors(), "body": body.decode()}
- raise HTTPException(status_code=422, detail=detail)
-
- return custom_route_handler
-
-
-app = FastAPI()
-app.router.route_class = ValidationErrorLoggingRoute
-
-
-@app.post("/")
-async def sum_numbers(numbers: Annotated[list[int], Body()]):
- return sum(numbers)
diff --git a/docs_src/custom_request_and_route/tutorial002_py39.py b/docs_src/custom_request_and_route/tutorial002_py39.py
deleted file mode 100644
index c4e474828..000000000
--- a/docs_src/custom_request_and_route/tutorial002_py39.py
+++ /dev/null
@@ -1,29 +0,0 @@
-from typing import Callable
-
-from fastapi import Body, FastAPI, HTTPException, Request, Response
-from fastapi.exceptions import RequestValidationError
-from fastapi.routing import APIRoute
-
-
-class ValidationErrorLoggingRoute(APIRoute):
- def get_route_handler(self) -> Callable:
- original_route_handler = super().get_route_handler()
-
- async def custom_route_handler(request: Request) -> Response:
- try:
- return await original_route_handler(request)
- except RequestValidationError as exc:
- body = await request.body()
- detail = {"errors": exc.errors(), "body": body.decode()}
- raise HTTPException(status_code=422, detail=detail)
-
- return custom_route_handler
-
-
-app = FastAPI()
-app.router.route_class = ValidationErrorLoggingRoute
-
-
-@app.post("/")
-async def sum_numbers(numbers: list[int] = Body()):
- return sum(numbers)
diff --git a/docs_src/custom_request_and_route/tutorial003_py39.py b/docs_src/custom_request_and_route/tutorial003_py39.py
deleted file mode 100644
index aabe76068..000000000
--- a/docs_src/custom_request_and_route/tutorial003_py39.py
+++ /dev/null
@@ -1,39 +0,0 @@
-import time
-from typing import Callable
-
-from fastapi import APIRouter, FastAPI, Request, Response
-from fastapi.routing import APIRoute
-
-
-class TimedRoute(APIRoute):
- def get_route_handler(self) -> Callable:
- original_route_handler = super().get_route_handler()
-
- async def custom_route_handler(request: Request) -> Response:
- before = time.time()
- response: Response = await original_route_handler(request)
- duration = time.time() - before
- response.headers["X-Response-Time"] = str(duration)
- print(f"route duration: {duration}")
- print(f"route response: {response}")
- print(f"route response headers: {response.headers}")
- return response
-
- return custom_route_handler
-
-
-app = FastAPI()
-router = APIRouter(route_class=TimedRoute)
-
-
-@app.get("/")
-async def not_timed():
- return {"message": "Not timed"}
-
-
-@router.get("/timed")
-async def timed():
- return {"message": "It's the time of my life"}
-
-
-app.include_router(router)
diff --git a/docs_src/custom_response/tutorial001_py310.py b/docs_src/custom_response/tutorial001_py310.py
new file mode 100644
index 000000000..0f09bdf77
--- /dev/null
+++ b/docs_src/custom_response/tutorial001_py310.py
@@ -0,0 +1,9 @@
+from fastapi import FastAPI
+from fastapi.responses import UJSONResponse
+
+app = FastAPI()
+
+
+@app.get("/items/", response_class=UJSONResponse)
+async def read_items():
+ return [{"item_id": "Foo"}]
diff --git a/docs_src/custom_response/tutorial001b_py310.py b/docs_src/custom_response/tutorial001b_py310.py
new file mode 100644
index 000000000..95e6ca763
--- /dev/null
+++ b/docs_src/custom_response/tutorial001b_py310.py
@@ -0,0 +1,9 @@
+from fastapi import FastAPI
+from fastapi.responses import ORJSONResponse
+
+app = FastAPI()
+
+
+@app.get("/items/", response_class=ORJSONResponse)
+async def read_items():
+ return ORJSONResponse([{"item_id": "Foo"}])
diff --git a/docs_src/custom_response/tutorial002_py310.py b/docs_src/custom_response/tutorial002_py310.py
new file mode 100644
index 000000000..23c495867
--- /dev/null
+++ b/docs_src/custom_response/tutorial002_py310.py
@@ -0,0 +1,18 @@
+from fastapi import FastAPI
+from fastapi.responses import HTMLResponse
+
+app = FastAPI()
+
+
+@app.get("/items/", response_class=HTMLResponse)
+async def read_items():
+ return """
+
+
+ + FastAPI framework, high performance, easy to learn, fast to code, ready for production +
+ + +--- + +**Documentation**: https://fastapi.tiangolo.com + +**Source Code**: https://github.com/fastapi/fastapi + +--- + +FastAPI is a modern, fast (high-performance), web framework for building APIs with Python based on standard Python type hints. + +## `fastapi-slim` + +⚠️ Do not install this package. ⚠️ + +This package, `fastapi-slim`, does nothing other than depend on `fastapi`. + +All the functionality has been integrated into `fastapi`. + +The only reason this package exists is as a migration path for old projects that used to depend on `fastapi-slim`, so that they can get the latest version of `fastapi`. + +You **should not** install this package. + +Install instead: + +```bash +pip install fastapi +``` + +This package is deprecated and will stop receiving any updates and published versions. + +## License + +This project is licensed under the terms of the MIT license. diff --git a/fastapi/__init__.py b/fastapi/__init__.py index 95e57e2eb..de5a0be38 100644 --- a/fastapi/__init__.py +++ b/fastapi/__init__.py @@ -1,6 +1,6 @@ """FastAPI framework, high performance, easy to learn, fast to code, ready for production""" -__version__ = "0.128.6" +__version__ = "0.129.0" from starlette import status as status diff --git a/fastapi/_compat/shared.py b/fastapi/_compat/shared.py index c009da8fd..9d76dabe6 100644 --- a/fastapi/_compat/shared.py +++ b/fastapi/_compat/shared.py @@ -1,4 +1,3 @@ -import sys import types import typing import warnings @@ -8,27 +7,26 @@ from dataclasses import is_dataclass from typing import ( Annotated, Any, + TypeGuard, TypeVar, Union, + get_args, + get_origin, ) from fastapi.types import UnionType from pydantic import BaseModel from pydantic.version import VERSION as PYDANTIC_VERSION from starlette.datastructures import UploadFile -from typing_extensions import TypeGuard, get_args, get_origin _T = TypeVar("_T") # Copy from Pydantic: pydantic/_internal/_typing_extra.py -if sys.version_info < (3, 10): - WithArgsTypes: tuple[Any, ...] = (typing._GenericAlias, types.GenericAlias) # type: ignore[attr-defined] -else: - WithArgsTypes: tuple[Any, ...] = ( - typing._GenericAlias, # type: ignore[attr-defined] - types.GenericAlias, - types.UnionType, - ) # pyright: ignore[reportAttributeAccessIssue] +WithArgsTypes: tuple[Any, ...] = ( + typing._GenericAlias, # type: ignore[attr-defined] + types.GenericAlias, + types.UnionType, +) # pyright: ignore[reportAttributeAccessIssue] PYDANTIC_VERSION_MINOR_TUPLE = tuple(int(x) for x in PYDANTIC_VERSION.split(".")[:2]) @@ -47,7 +45,7 @@ sequence_types: tuple[type[Any], ...] = tuple(sequence_annotation_to_type.keys() # Copy of Pydantic: pydantic/_internal/_utils.py with added TypeGuard def lenient_issubclass( - cls: Any, class_or_tuple: Union[type[_T], tuple[type[_T], ...], None] + cls: Any, class_or_tuple: type[_T] | tuple[type[_T], ...] | None ) -> TypeGuard[type[_T]]: try: return isinstance(cls, type) and issubclass(cls, class_or_tuple) # type: ignore[arg-type] @@ -57,13 +55,13 @@ def lenient_issubclass( raise # pragma: no cover -def _annotation_is_sequence(annotation: Union[type[Any], None]) -> bool: +def _annotation_is_sequence(annotation: type[Any] | None) -> bool: if lenient_issubclass(annotation, (str, bytes)): return False return lenient_issubclass(annotation, sequence_types) -def field_annotation_is_sequence(annotation: Union[type[Any], None]) -> bool: +def field_annotation_is_sequence(annotation: type[Any] | None) -> bool: origin = get_origin(annotation) if origin is Union or origin is UnionType: for arg in get_args(annotation): @@ -79,7 +77,7 @@ def value_is_sequence(value: Any) -> bool: return isinstance(value, sequence_types) and not isinstance(value, (str, bytes)) -def _annotation_is_complex(annotation: Union[type[Any], None]) -> bool: +def _annotation_is_complex(annotation: type[Any] | None) -> bool: return ( lenient_issubclass(annotation, (BaseModel, Mapping, UploadFile)) or _annotation_is_sequence(annotation) @@ -87,7 +85,7 @@ def _annotation_is_complex(annotation: Union[type[Any], None]) -> bool: ) -def field_annotation_is_complex(annotation: Union[type[Any], None]) -> bool: +def field_annotation_is_complex(annotation: type[Any] | None) -> bool: origin = get_origin(annotation) if origin is Union or origin is UnionType: return any(field_annotation_is_complex(arg) for arg in get_args(annotation)) @@ -108,7 +106,7 @@ def field_annotation_is_scalar(annotation: Any) -> bool: return annotation is Ellipsis or not field_annotation_is_complex(annotation) -def field_annotation_is_scalar_sequence(annotation: Union[type[Any], None]) -> bool: +def field_annotation_is_scalar_sequence(annotation: type[Any] | None) -> bool: origin = get_origin(annotation) if origin is Union or origin is UnionType: at_least_one_scalar_sequence = False diff --git a/fastapi/_compat/v2.py b/fastapi/_compat/v2.py index 87b9fb47f..b83bc1b55 100644 --- a/fastapi/_compat/v2.py +++ b/fastapi/_compat/v2.py @@ -8,8 +8,11 @@ from functools import lru_cache from typing import ( Annotated, Any, + Literal, Union, cast, + get_args, + get_origin, ) from fastapi._compat import lenient_issubclass, shared @@ -32,7 +35,6 @@ from pydantic_core import Url as Url from pydantic_core.core_schema import ( with_info_plain_validator_function as with_info_plain_validator_function, ) -from typing_extensions import Literal, get_args, get_origin RequiredParam = PydanticUndefined Undefined = PydanticUndefined @@ -83,7 +85,7 @@ class ModelField: field_info: FieldInfo name: str mode: Literal["validation", "serialization"] = "validation" - config: Union[ConfigDict, None] = None + config: ConfigDict | None = None @property def alias(self) -> str: @@ -91,14 +93,14 @@ class ModelField: return a if a is not None else self.name @property - def validation_alias(self) -> Union[str, None]: + def validation_alias(self) -> str | None: va = self.field_info.validation_alias if isinstance(va, str) and va: return va return None @property - def serialization_alias(self) -> Union[str, None]: + def serialization_alias(self) -> str | None: sa = self.field_info.serialization_alias return sa or None @@ -143,7 +145,7 @@ class ModelField: value: Any, values: dict[str, Any] = {}, # noqa: B006 *, - loc: tuple[Union[int, str], ...] = (), + loc: tuple[int | str, ...] = (), ) -> tuple[Any, list[dict[str, Any]]]: try: return ( @@ -160,8 +162,8 @@ class ModelField: value: Any, *, mode: Literal["json", "python"] = "json", - include: Union[IncEx, None] = None, - exclude: Union[IncEx, None] = None, + include: IncEx | None = None, + exclude: IncEx | None = None, by_alias: bool = True, exclude_unset: bool = False, exclude_defaults: bool = False, @@ -202,7 +204,7 @@ def get_schema_from_model_field( ], separate_input_output_schemas: bool = True, ) -> dict[str, Any]: - override_mode: Union[Literal["validation"], None] = ( + override_mode: Literal["validation"] | None = ( None if (separate_input_output_schemas or _has_computed_fields(field)) else "validation" @@ -318,7 +320,7 @@ def serialize_sequence_value(*, field: ModelField, value: Any) -> Sequence[Any]: return shared.sequence_annotation_to_type[origin_type](value) # type: ignore[no-any-return,index] -def get_missing_field_error(loc: tuple[Union[int, str], ...]) -> dict[str, Any]: +def get_missing_field_error(loc: tuple[int | str, ...]) -> dict[str, Any]: error = ValidationError.from_exception_data( "Field required", [{"type": "missing", "loc": loc, "input": {}}] ).errors(include_url=False)[0] @@ -360,7 +362,7 @@ def get_cached_model_fields(model: type[BaseModel]) -> list[ModelField]: # Duplicate of several schema functions from Pydantic v1 to make them compatible with # Pydantic v2 and allow mixing the models -TypeModelOrEnum = Union[type["BaseModel"], type[Enum]] +TypeModelOrEnum = type["BaseModel"] | type[Enum] TypeModelSet = set[TypeModelOrEnum] @@ -377,7 +379,7 @@ def get_model_name_map(unique_models: TypeModelSet) -> dict[TypeModelOrEnum, str def get_flat_models_from_model( - model: type["BaseModel"], known_models: Union[TypeModelSet, None] = None + model: type["BaseModel"], known_models: TypeModelSet | None = None ) -> TypeModelSet: known_models = known_models or set() fields = get_model_fields(model) @@ -426,7 +428,7 @@ def get_flat_models_from_fields( def _regenerate_error_with_loc( - *, errors: Sequence[Any], loc_prefix: tuple[Union[str, int], ...] + *, errors: Sequence[Any], loc_prefix: tuple[str | int, ...] ) -> list[dict[str, Any]]: updated_loc_errors: list[Any] = [ {**err, "loc": loc_prefix + err.get("loc", ())} for err in errors diff --git a/fastapi/applications.py b/fastapi/applications.py index 340cabfc2..41d86143e 100644 --- a/fastapi/applications.py +++ b/fastapi/applications.py @@ -1,12 +1,9 @@ -from collections.abc import Awaitable, Coroutine, Sequence +from collections.abc import Awaitable, Callable, Coroutine, Sequence from enum import Enum from typing import ( Annotated, Any, - Callable, - Optional, TypeVar, - Union, ) from annotated_doc import Doc @@ -77,7 +74,7 @@ class FastAPI(Starlette): ), ] = False, routes: Annotated[ - Optional[list[BaseRoute]], + list[BaseRoute] | None, Doc( """ **Note**: you probably shouldn't use this parameter, it is inherited @@ -120,7 +117,7 @@ class FastAPI(Starlette): ), ] = "FastAPI", summary: Annotated[ - Optional[str], + str | None, Doc( """ A short summary of the API. @@ -203,7 +200,7 @@ class FastAPI(Starlette): ), ] = "0.1.0", openapi_url: Annotated[ - Optional[str], + str | None, Doc( """ The URL where the OpenAPI schema will be served from. @@ -226,7 +223,7 @@ class FastAPI(Starlette): ), ] = "/openapi.json", openapi_tags: Annotated[ - Optional[list[dict[str, Any]]], + list[dict[str, Any]] | None, Doc( """ A list of tags used by OpenAPI, these are the same `tags` you can set @@ -286,7 +283,7 @@ class FastAPI(Starlette): ), ] = None, servers: Annotated[ - Optional[list[dict[str, Union[str, Any]]]], + list[dict[str, str | Any]] | None, Doc( """ A `list` of `dict`s with connectivity information to a target server. @@ -335,7 +332,7 @@ class FastAPI(Starlette): ), ] = None, dependencies: Annotated[ - Optional[Sequence[Depends]], + Sequence[Depends] | None, Doc( """ A list of global dependencies, they will be applied to each @@ -402,7 +399,7 @@ class FastAPI(Starlette): ), ] = True, docs_url: Annotated[ - Optional[str], + str | None, Doc( """ The path to the automatic interactive API documentation. @@ -426,7 +423,7 @@ class FastAPI(Starlette): ), ] = "/docs", redoc_url: Annotated[ - Optional[str], + str | None, Doc( """ The path to the alternative automatic interactive API documentation @@ -450,7 +447,7 @@ class FastAPI(Starlette): ), ] = "/redoc", swagger_ui_oauth2_redirect_url: Annotated[ - Optional[str], + str | None, Doc( """ The OAuth2 redirect endpoint for the Swagger UI. @@ -463,7 +460,7 @@ class FastAPI(Starlette): ), ] = "/docs/oauth2-redirect", swagger_ui_init_oauth: Annotated[ - Optional[dict[str, Any]], + dict[str, Any] | None, Doc( """ OAuth2 configuration for the Swagger UI, by default shown at `/docs`. @@ -474,7 +471,7 @@ class FastAPI(Starlette): ), ] = None, middleware: Annotated[ - Optional[Sequence[Middleware]], + Sequence[Middleware] | None, Doc( """ List of middleware to be added when creating the application. @@ -488,12 +485,11 @@ class FastAPI(Starlette): ), ] = None, exception_handlers: Annotated[ - Optional[ - dict[ - Union[int, type[Exception]], - Callable[[Request, Any], Coroutine[Any, Any, Response]], - ] - ], + dict[ + int | type[Exception], + Callable[[Request, Any], Coroutine[Any, Any, Response]], + ] + | None, Doc( """ A dictionary with handlers for exceptions. @@ -507,7 +503,7 @@ class FastAPI(Starlette): ), ] = None, on_startup: Annotated[ - Optional[Sequence[Callable[[], Any]]], + Sequence[Callable[[], Any]] | None, Doc( """ A list of startup event handler functions. @@ -519,7 +515,7 @@ class FastAPI(Starlette): ), ] = None, on_shutdown: Annotated[ - Optional[Sequence[Callable[[], Any]]], + Sequence[Callable[[], Any]] | None, Doc( """ A list of shutdown event handler functions. @@ -532,7 +528,7 @@ class FastAPI(Starlette): ), ] = None, lifespan: Annotated[ - Optional[Lifespan[AppType]], + Lifespan[AppType] | None, Doc( """ A `Lifespan` context manager handler. This replaces `startup` and @@ -544,7 +540,7 @@ class FastAPI(Starlette): ), ] = None, terms_of_service: Annotated[ - Optional[str], + str | None, Doc( """ A URL to the Terms of Service for your API. @@ -563,7 +559,7 @@ class FastAPI(Starlette): ), ] = None, contact: Annotated[ - Optional[dict[str, Union[str, Any]]], + dict[str, str | Any] | None, Doc( """ A dictionary with the contact information for the exposed API. @@ -596,7 +592,7 @@ class FastAPI(Starlette): ), ] = None, license_info: Annotated[ - Optional[dict[str, Union[str, Any]]], + dict[str, str | Any] | None, Doc( """ A dictionary with the license information for the exposed API. @@ -685,7 +681,7 @@ class FastAPI(Starlette): ), ] = True, responses: Annotated[ - Optional[dict[Union[int, str], dict[str, Any]]], + dict[int | str, dict[str, Any]] | None, Doc( """ Additional responses to be shown in OpenAPI. @@ -701,7 +697,7 @@ class FastAPI(Starlette): ), ] = None, callbacks: Annotated[ - Optional[list[BaseRoute]], + list[BaseRoute] | None, Doc( """ OpenAPI callbacks that should apply to all *path operations*. @@ -714,7 +710,7 @@ class FastAPI(Starlette): ), ] = None, webhooks: Annotated[ - Optional[routing.APIRouter], + routing.APIRouter | None, Doc( """ Add OpenAPI webhooks. This is similar to `callbacks` but it doesn't @@ -730,7 +726,7 @@ class FastAPI(Starlette): ), ] = None, deprecated: Annotated[ - Optional[bool], + bool | None, Doc( """ Mark all *path operations* as deprecated. You probably don't need it, @@ -758,7 +754,7 @@ class FastAPI(Starlette): ), ] = True, swagger_ui_parameters: Annotated[ - Optional[dict[str, Any]], + dict[str, Any] | None, Doc( """ Parameters to configure Swagger UI, the autogenerated interactive API @@ -819,7 +815,7 @@ class FastAPI(Starlette): ), ] = True, openapi_external_docs: Annotated[ - Optional[dict[str, Any]], + dict[str, Any] | None, Doc( """ This field allows you to provide additional external documentation links. @@ -905,7 +901,7 @@ class FastAPI(Starlette): """ ), ] = "3.1.0" - self.openapi_schema: Optional[dict[str, Any]] = None + self.openapi_schema: dict[str, Any] | None = None if self.openapi_url: assert self.title, "A title must be provided for OpenAPI, e.g.: 'My API'" assert self.version, "A version must be provided for OpenAPI, e.g.: '2.1.0'" @@ -980,7 +976,7 @@ class FastAPI(Starlette): generate_unique_id_function=generate_unique_id_function, ) self.exception_handlers: dict[ - Any, Callable[[Request, Any], Union[Response, Awaitable[Response]]] + Any, Callable[[Request, Any], Response | Awaitable[Response]] ] = {} if exception_handlers is None else dict(exception_handlers) self.exception_handlers.setdefault(HTTPException, http_exception_handler) self.exception_handlers.setdefault( @@ -995,7 +991,7 @@ class FastAPI(Starlette): self.user_middleware: list[Middleware] = ( [] if middleware is None else list(middleware) ) - self.middleware_stack: Union[ASGIApp, None] = None + self.middleware_stack: ASGIApp | None = None self.setup() def build_middleware_stack(self) -> ASGIApp: @@ -1143,28 +1139,26 @@ class FastAPI(Starlette): endpoint: Callable[..., Any], *, response_model: Any = Default(None), - status_code: Optional[int] = None, - tags: Optional[list[Union[str, Enum]]] = None, - dependencies: Optional[Sequence[Depends]] = None, - summary: Optional[str] = None, - description: Optional[str] = None, + status_code: int | None = None, + tags: list[str | Enum] | None = None, + dependencies: Sequence[Depends] | None = None, + summary: str | None = None, + description: str | None = None, response_description: str = "Successful Response", - responses: Optional[dict[Union[int, str], dict[str, Any]]] = None, - deprecated: Optional[bool] = None, - methods: Optional[list[str]] = None, - operation_id: Optional[str] = None, - response_model_include: Optional[IncEx] = None, - response_model_exclude: Optional[IncEx] = None, + responses: dict[int | str, dict[str, Any]] | None = None, + deprecated: bool | None = None, + methods: list[str] | None = None, + operation_id: str | None = None, + response_model_include: IncEx | None = None, + response_model_exclude: IncEx | None = None, response_model_by_alias: bool = True, response_model_exclude_unset: bool = False, response_model_exclude_defaults: bool = False, response_model_exclude_none: bool = False, include_in_schema: bool = True, - response_class: Union[type[Response], DefaultPlaceholder] = Default( - JSONResponse - ), - name: Optional[str] = None, - openapi_extra: Optional[dict[str, Any]] = None, + response_class: type[Response] | DefaultPlaceholder = Default(JSONResponse), + name: str | None = None, + openapi_extra: dict[str, Any] | None = None, generate_unique_id_function: Callable[[routing.APIRoute], str] = Default( generate_unique_id ), @@ -1201,26 +1195,26 @@ class FastAPI(Starlette): path: str, *, response_model: Any = Default(None), - status_code: Optional[int] = None, - tags: Optional[list[Union[str, Enum]]] = None, - dependencies: Optional[Sequence[Depends]] = None, - summary: Optional[str] = None, - description: Optional[str] = None, + status_code: int | None = None, + tags: list[str | Enum] | None = None, + dependencies: Sequence[Depends] | None = None, + summary: str | None = None, + description: str | None = None, response_description: str = "Successful Response", - responses: Optional[dict[Union[int, str], dict[str, Any]]] = None, - deprecated: Optional[bool] = None, - methods: Optional[list[str]] = None, - operation_id: Optional[str] = None, - response_model_include: Optional[IncEx] = None, - response_model_exclude: Optional[IncEx] = None, + responses: dict[int | str, dict[str, Any]] | None = None, + deprecated: bool | None = None, + methods: list[str] | None = None, + operation_id: str | None = None, + response_model_include: IncEx | None = None, + response_model_exclude: IncEx | None = None, response_model_by_alias: bool = True, response_model_exclude_unset: bool = False, response_model_exclude_defaults: bool = False, response_model_exclude_none: bool = False, include_in_schema: bool = True, response_class: type[Response] = Default(JSONResponse), - name: Optional[str] = None, - openapi_extra: Optional[dict[str, Any]] = None, + name: str | None = None, + openapi_extra: dict[str, Any] | None = None, generate_unique_id_function: Callable[[routing.APIRoute], str] = Default( generate_unique_id ), @@ -1260,9 +1254,9 @@ class FastAPI(Starlette): self, path: str, endpoint: Callable[..., Any], - name: Optional[str] = None, + name: str | None = None, *, - dependencies: Optional[Sequence[Depends]] = None, + dependencies: Sequence[Depends] | None = None, ) -> None: self.router.add_api_websocket_route( path, @@ -1282,7 +1276,7 @@ class FastAPI(Starlette): ), ], name: Annotated[ - Optional[str], + str | None, Doc( """ A name for the WebSocket. Only used internally. @@ -1291,7 +1285,7 @@ class FastAPI(Starlette): ] = None, *, dependencies: Annotated[ - Optional[Sequence[Depends]], + Sequence[Depends] | None, Doc( """ A list of dependencies (using `Depends()`) to be used for this @@ -1342,7 +1336,7 @@ class FastAPI(Starlette): *, prefix: Annotated[str, Doc("An optional path prefix for the router.")] = "", tags: Annotated[ - Optional[list[Union[str, Enum]]], + list[str | Enum] | None, Doc( """ A list of tags to be applied to all the *path operations* in this @@ -1356,7 +1350,7 @@ class FastAPI(Starlette): ), ] = None, dependencies: Annotated[ - Optional[Sequence[Depends]], + Sequence[Depends] | None, Doc( """ A list of dependencies (using `Depends()`) to be applied to all the @@ -1384,7 +1378,7 @@ class FastAPI(Starlette): ), ] = None, responses: Annotated[ - Optional[dict[Union[int, str], dict[str, Any]]], + dict[int | str, dict[str, Any]] | None, Doc( """ Additional responses to be shown in OpenAPI. @@ -1400,7 +1394,7 @@ class FastAPI(Starlette): ), ] = None, deprecated: Annotated[ - Optional[bool], + bool | None, Doc( """ Mark all the *path operations* in this router as deprecated. @@ -1479,7 +1473,7 @@ class FastAPI(Starlette): ), ] = Default(JSONResponse), callbacks: Annotated[ - Optional[list[BaseRoute]], + list[BaseRoute] | None, Doc( """ List of *path operations* that will be used as OpenAPI callbacks. @@ -1589,7 +1583,7 @@ class FastAPI(Starlette): ), ] = Default(None), status_code: Annotated[ - Optional[int], + int | None, Doc( """ The default status code to be used for the response. @@ -1602,7 +1596,7 @@ class FastAPI(Starlette): ), ] = None, tags: Annotated[ - Optional[list[Union[str, Enum]]], + list[str | Enum] | None, Doc( """ A list of tags to be applied to the *path operation*. @@ -1615,7 +1609,7 @@ class FastAPI(Starlette): ), ] = None, dependencies: Annotated[ - Optional[Sequence[Depends]], + Sequence[Depends] | None, Doc( """ A list of dependencies (using `Depends()`) to be applied to the @@ -1627,7 +1621,7 @@ class FastAPI(Starlette): ), ] = None, summary: Annotated[ - Optional[str], + str | None, Doc( """ A summary for the *path operation*. @@ -1640,7 +1634,7 @@ class FastAPI(Starlette): ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ A description for the *path operation*. @@ -1668,7 +1662,7 @@ class FastAPI(Starlette): ), ] = "Successful Response", responses: Annotated[ - Optional[dict[Union[int, str], dict[str, Any]]], + dict[int | str, dict[str, Any]] | None, Doc( """ Additional responses that could be returned by this *path operation*. @@ -1678,7 +1672,7 @@ class FastAPI(Starlette): ), ] = None, deprecated: Annotated[ - Optional[bool], + bool | None, Doc( """ Mark this *path operation* as deprecated. @@ -1688,7 +1682,7 @@ class FastAPI(Starlette): ), ] = None, operation_id: Annotated[ - Optional[str], + str | None, Doc( """ Custom operation ID to be used by this *path operation*. @@ -1708,7 +1702,7 @@ class FastAPI(Starlette): ), ] = None, response_model_include: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to include only certain fields in the @@ -1720,7 +1714,7 @@ class FastAPI(Starlette): ), ] = None, response_model_exclude: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to exclude certain fields in the @@ -1822,7 +1816,7 @@ class FastAPI(Starlette): ), ] = Default(JSONResponse), name: Annotated[ - Optional[str], + str | None, Doc( """ Name for this *path operation*. Only used internally. @@ -1830,7 +1824,7 @@ class FastAPI(Starlette): ), ] = None, callbacks: Annotated[ - Optional[list[BaseRoute]], + list[BaseRoute] | None, Doc( """ List of *path operations* that will be used as OpenAPI callbacks. @@ -1846,7 +1840,7 @@ class FastAPI(Starlette): ), ] = None, openapi_extra: Annotated[ - Optional[dict[str, Any]], + dict[str, Any] | None, Doc( """ Extra metadata to be included in the OpenAPI schema for this *path @@ -1962,7 +1956,7 @@ class FastAPI(Starlette): ), ] = Default(None), status_code: Annotated[ - Optional[int], + int | None, Doc( """ The default status code to be used for the response. @@ -1975,7 +1969,7 @@ class FastAPI(Starlette): ), ] = None, tags: Annotated[ - Optional[list[Union[str, Enum]]], + list[str | Enum] | None, Doc( """ A list of tags to be applied to the *path operation*. @@ -1988,7 +1982,7 @@ class FastAPI(Starlette): ), ] = None, dependencies: Annotated[ - Optional[Sequence[Depends]], + Sequence[Depends] | None, Doc( """ A list of dependencies (using `Depends()`) to be applied to the @@ -2000,7 +1994,7 @@ class FastAPI(Starlette): ), ] = None, summary: Annotated[ - Optional[str], + str | None, Doc( """ A summary for the *path operation*. @@ -2013,7 +2007,7 @@ class FastAPI(Starlette): ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ A description for the *path operation*. @@ -2041,7 +2035,7 @@ class FastAPI(Starlette): ), ] = "Successful Response", responses: Annotated[ - Optional[dict[Union[int, str], dict[str, Any]]], + dict[int | str, dict[str, Any]] | None, Doc( """ Additional responses that could be returned by this *path operation*. @@ -2051,7 +2045,7 @@ class FastAPI(Starlette): ), ] = None, deprecated: Annotated[ - Optional[bool], + bool | None, Doc( """ Mark this *path operation* as deprecated. @@ -2061,7 +2055,7 @@ class FastAPI(Starlette): ), ] = None, operation_id: Annotated[ - Optional[str], + str | None, Doc( """ Custom operation ID to be used by this *path operation*. @@ -2081,7 +2075,7 @@ class FastAPI(Starlette): ), ] = None, response_model_include: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to include only certain fields in the @@ -2093,7 +2087,7 @@ class FastAPI(Starlette): ), ] = None, response_model_exclude: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to exclude certain fields in the @@ -2195,7 +2189,7 @@ class FastAPI(Starlette): ), ] = Default(JSONResponse), name: Annotated[ - Optional[str], + str | None, Doc( """ Name for this *path operation*. Only used internally. @@ -2203,7 +2197,7 @@ class FastAPI(Starlette): ), ] = None, callbacks: Annotated[ - Optional[list[BaseRoute]], + list[BaseRoute] | None, Doc( """ List of *path operations* that will be used as OpenAPI callbacks. @@ -2219,7 +2213,7 @@ class FastAPI(Starlette): ), ] = None, openapi_extra: Annotated[ - Optional[dict[str, Any]], + dict[str, Any] | None, Doc( """ Extra metadata to be included in the OpenAPI schema for this *path @@ -2340,7 +2334,7 @@ class FastAPI(Starlette): ), ] = Default(None), status_code: Annotated[ - Optional[int], + int | None, Doc( """ The default status code to be used for the response. @@ -2353,7 +2347,7 @@ class FastAPI(Starlette): ), ] = None, tags: Annotated[ - Optional[list[Union[str, Enum]]], + list[str | Enum] | None, Doc( """ A list of tags to be applied to the *path operation*. @@ -2366,7 +2360,7 @@ class FastAPI(Starlette): ), ] = None, dependencies: Annotated[ - Optional[Sequence[Depends]], + Sequence[Depends] | None, Doc( """ A list of dependencies (using `Depends()`) to be applied to the @@ -2378,7 +2372,7 @@ class FastAPI(Starlette): ), ] = None, summary: Annotated[ - Optional[str], + str | None, Doc( """ A summary for the *path operation*. @@ -2391,7 +2385,7 @@ class FastAPI(Starlette): ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ A description for the *path operation*. @@ -2419,7 +2413,7 @@ class FastAPI(Starlette): ), ] = "Successful Response", responses: Annotated[ - Optional[dict[Union[int, str], dict[str, Any]]], + dict[int | str, dict[str, Any]] | None, Doc( """ Additional responses that could be returned by this *path operation*. @@ -2429,7 +2423,7 @@ class FastAPI(Starlette): ), ] = None, deprecated: Annotated[ - Optional[bool], + bool | None, Doc( """ Mark this *path operation* as deprecated. @@ -2439,7 +2433,7 @@ class FastAPI(Starlette): ), ] = None, operation_id: Annotated[ - Optional[str], + str | None, Doc( """ Custom operation ID to be used by this *path operation*. @@ -2459,7 +2453,7 @@ class FastAPI(Starlette): ), ] = None, response_model_include: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to include only certain fields in the @@ -2471,7 +2465,7 @@ class FastAPI(Starlette): ), ] = None, response_model_exclude: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to exclude certain fields in the @@ -2573,7 +2567,7 @@ class FastAPI(Starlette): ), ] = Default(JSONResponse), name: Annotated[ - Optional[str], + str | None, Doc( """ Name for this *path operation*. Only used internally. @@ -2581,7 +2575,7 @@ class FastAPI(Starlette): ), ] = None, callbacks: Annotated[ - Optional[list[BaseRoute]], + list[BaseRoute] | None, Doc( """ List of *path operations* that will be used as OpenAPI callbacks. @@ -2597,7 +2591,7 @@ class FastAPI(Starlette): ), ] = None, openapi_extra: Annotated[ - Optional[dict[str, Any]], + dict[str, Any] | None, Doc( """ Extra metadata to be included in the OpenAPI schema for this *path @@ -2718,7 +2712,7 @@ class FastAPI(Starlette): ), ] = Default(None), status_code: Annotated[ - Optional[int], + int | None, Doc( """ The default status code to be used for the response. @@ -2731,7 +2725,7 @@ class FastAPI(Starlette): ), ] = None, tags: Annotated[ - Optional[list[Union[str, Enum]]], + list[str | Enum] | None, Doc( """ A list of tags to be applied to the *path operation*. @@ -2744,7 +2738,7 @@ class FastAPI(Starlette): ), ] = None, dependencies: Annotated[ - Optional[Sequence[Depends]], + Sequence[Depends] | None, Doc( """ A list of dependencies (using `Depends()`) to be applied to the @@ -2756,7 +2750,7 @@ class FastAPI(Starlette): ), ] = None, summary: Annotated[ - Optional[str], + str | None, Doc( """ A summary for the *path operation*. @@ -2769,7 +2763,7 @@ class FastAPI(Starlette): ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ A description for the *path operation*. @@ -2797,7 +2791,7 @@ class FastAPI(Starlette): ), ] = "Successful Response", responses: Annotated[ - Optional[dict[Union[int, str], dict[str, Any]]], + dict[int | str, dict[str, Any]] | None, Doc( """ Additional responses that could be returned by this *path operation*. @@ -2807,7 +2801,7 @@ class FastAPI(Starlette): ), ] = None, deprecated: Annotated[ - Optional[bool], + bool | None, Doc( """ Mark this *path operation* as deprecated. @@ -2817,7 +2811,7 @@ class FastAPI(Starlette): ), ] = None, operation_id: Annotated[ - Optional[str], + str | None, Doc( """ Custom operation ID to be used by this *path operation*. @@ -2837,7 +2831,7 @@ class FastAPI(Starlette): ), ] = None, response_model_include: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to include only certain fields in the @@ -2849,7 +2843,7 @@ class FastAPI(Starlette): ), ] = None, response_model_exclude: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to exclude certain fields in the @@ -2951,7 +2945,7 @@ class FastAPI(Starlette): ), ] = Default(JSONResponse), name: Annotated[ - Optional[str], + str | None, Doc( """ Name for this *path operation*. Only used internally. @@ -2959,7 +2953,7 @@ class FastAPI(Starlette): ), ] = None, callbacks: Annotated[ - Optional[list[BaseRoute]], + list[BaseRoute] | None, Doc( """ List of *path operations* that will be used as OpenAPI callbacks. @@ -2975,7 +2969,7 @@ class FastAPI(Starlette): ), ] = None, openapi_extra: Annotated[ - Optional[dict[str, Any]], + dict[str, Any] | None, Doc( """ Extra metadata to be included in the OpenAPI schema for this *path @@ -3091,7 +3085,7 @@ class FastAPI(Starlette): ), ] = Default(None), status_code: Annotated[ - Optional[int], + int | None, Doc( """ The default status code to be used for the response. @@ -3104,7 +3098,7 @@ class FastAPI(Starlette): ), ] = None, tags: Annotated[ - Optional[list[Union[str, Enum]]], + list[str | Enum] | None, Doc( """ A list of tags to be applied to the *path operation*. @@ -3117,7 +3111,7 @@ class FastAPI(Starlette): ), ] = None, dependencies: Annotated[ - Optional[Sequence[Depends]], + Sequence[Depends] | None, Doc( """ A list of dependencies (using `Depends()`) to be applied to the @@ -3129,7 +3123,7 @@ class FastAPI(Starlette): ), ] = None, summary: Annotated[ - Optional[str], + str | None, Doc( """ A summary for the *path operation*. @@ -3142,7 +3136,7 @@ class FastAPI(Starlette): ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ A description for the *path operation*. @@ -3170,7 +3164,7 @@ class FastAPI(Starlette): ), ] = "Successful Response", responses: Annotated[ - Optional[dict[Union[int, str], dict[str, Any]]], + dict[int | str, dict[str, Any]] | None, Doc( """ Additional responses that could be returned by this *path operation*. @@ -3180,7 +3174,7 @@ class FastAPI(Starlette): ), ] = None, deprecated: Annotated[ - Optional[bool], + bool | None, Doc( """ Mark this *path operation* as deprecated. @@ -3190,7 +3184,7 @@ class FastAPI(Starlette): ), ] = None, operation_id: Annotated[ - Optional[str], + str | None, Doc( """ Custom operation ID to be used by this *path operation*. @@ -3210,7 +3204,7 @@ class FastAPI(Starlette): ), ] = None, response_model_include: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to include only certain fields in the @@ -3222,7 +3216,7 @@ class FastAPI(Starlette): ), ] = None, response_model_exclude: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to exclude certain fields in the @@ -3324,7 +3318,7 @@ class FastAPI(Starlette): ), ] = Default(JSONResponse), name: Annotated[ - Optional[str], + str | None, Doc( """ Name for this *path operation*. Only used internally. @@ -3332,7 +3326,7 @@ class FastAPI(Starlette): ), ] = None, callbacks: Annotated[ - Optional[list[BaseRoute]], + list[BaseRoute] | None, Doc( """ List of *path operations* that will be used as OpenAPI callbacks. @@ -3348,7 +3342,7 @@ class FastAPI(Starlette): ), ] = None, openapi_extra: Annotated[ - Optional[dict[str, Any]], + dict[str, Any] | None, Doc( """ Extra metadata to be included in the OpenAPI schema for this *path @@ -3464,7 +3458,7 @@ class FastAPI(Starlette): ), ] = Default(None), status_code: Annotated[ - Optional[int], + int | None, Doc( """ The default status code to be used for the response. @@ -3477,7 +3471,7 @@ class FastAPI(Starlette): ), ] = None, tags: Annotated[ - Optional[list[Union[str, Enum]]], + list[str | Enum] | None, Doc( """ A list of tags to be applied to the *path operation*. @@ -3490,7 +3484,7 @@ class FastAPI(Starlette): ), ] = None, dependencies: Annotated[ - Optional[Sequence[Depends]], + Sequence[Depends] | None, Doc( """ A list of dependencies (using `Depends()`) to be applied to the @@ -3502,7 +3496,7 @@ class FastAPI(Starlette): ), ] = None, summary: Annotated[ - Optional[str], + str | None, Doc( """ A summary for the *path operation*. @@ -3515,7 +3509,7 @@ class FastAPI(Starlette): ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ A description for the *path operation*. @@ -3543,7 +3537,7 @@ class FastAPI(Starlette): ), ] = "Successful Response", responses: Annotated[ - Optional[dict[Union[int, str], dict[str, Any]]], + dict[int | str, dict[str, Any]] | None, Doc( """ Additional responses that could be returned by this *path operation*. @@ -3553,7 +3547,7 @@ class FastAPI(Starlette): ), ] = None, deprecated: Annotated[ - Optional[bool], + bool | None, Doc( """ Mark this *path operation* as deprecated. @@ -3563,7 +3557,7 @@ class FastAPI(Starlette): ), ] = None, operation_id: Annotated[ - Optional[str], + str | None, Doc( """ Custom operation ID to be used by this *path operation*. @@ -3583,7 +3577,7 @@ class FastAPI(Starlette): ), ] = None, response_model_include: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to include only certain fields in the @@ -3595,7 +3589,7 @@ class FastAPI(Starlette): ), ] = None, response_model_exclude: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to exclude certain fields in the @@ -3697,7 +3691,7 @@ class FastAPI(Starlette): ), ] = Default(JSONResponse), name: Annotated[ - Optional[str], + str | None, Doc( """ Name for this *path operation*. Only used internally. @@ -3705,7 +3699,7 @@ class FastAPI(Starlette): ), ] = None, callbacks: Annotated[ - Optional[list[BaseRoute]], + list[BaseRoute] | None, Doc( """ List of *path operations* that will be used as OpenAPI callbacks. @@ -3721,7 +3715,7 @@ class FastAPI(Starlette): ), ] = None, openapi_extra: Annotated[ - Optional[dict[str, Any]], + dict[str, Any] | None, Doc( """ Extra metadata to be included in the OpenAPI schema for this *path @@ -3837,7 +3831,7 @@ class FastAPI(Starlette): ), ] = Default(None), status_code: Annotated[ - Optional[int], + int | None, Doc( """ The default status code to be used for the response. @@ -3850,7 +3844,7 @@ class FastAPI(Starlette): ), ] = None, tags: Annotated[ - Optional[list[Union[str, Enum]]], + list[str | Enum] | None, Doc( """ A list of tags to be applied to the *path operation*. @@ -3863,7 +3857,7 @@ class FastAPI(Starlette): ), ] = None, dependencies: Annotated[ - Optional[Sequence[Depends]], + Sequence[Depends] | None, Doc( """ A list of dependencies (using `Depends()`) to be applied to the @@ -3875,7 +3869,7 @@ class FastAPI(Starlette): ), ] = None, summary: Annotated[ - Optional[str], + str | None, Doc( """ A summary for the *path operation*. @@ -3888,7 +3882,7 @@ class FastAPI(Starlette): ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ A description for the *path operation*. @@ -3916,7 +3910,7 @@ class FastAPI(Starlette): ), ] = "Successful Response", responses: Annotated[ - Optional[dict[Union[int, str], dict[str, Any]]], + dict[int | str, dict[str, Any]] | None, Doc( """ Additional responses that could be returned by this *path operation*. @@ -3926,7 +3920,7 @@ class FastAPI(Starlette): ), ] = None, deprecated: Annotated[ - Optional[bool], + bool | None, Doc( """ Mark this *path operation* as deprecated. @@ -3936,7 +3930,7 @@ class FastAPI(Starlette): ), ] = None, operation_id: Annotated[ - Optional[str], + str | None, Doc( """ Custom operation ID to be used by this *path operation*. @@ -3956,7 +3950,7 @@ class FastAPI(Starlette): ), ] = None, response_model_include: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to include only certain fields in the @@ -3968,7 +3962,7 @@ class FastAPI(Starlette): ), ] = None, response_model_exclude: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to exclude certain fields in the @@ -4070,7 +4064,7 @@ class FastAPI(Starlette): ), ] = Default(JSONResponse), name: Annotated[ - Optional[str], + str | None, Doc( """ Name for this *path operation*. Only used internally. @@ -4078,7 +4072,7 @@ class FastAPI(Starlette): ), ] = None, callbacks: Annotated[ - Optional[list[BaseRoute]], + list[BaseRoute] | None, Doc( """ List of *path operations* that will be used as OpenAPI callbacks. @@ -4094,7 +4088,7 @@ class FastAPI(Starlette): ), ] = None, openapi_extra: Annotated[ - Optional[dict[str, Any]], + dict[str, Any] | None, Doc( """ Extra metadata to be included in the OpenAPI schema for this *path @@ -4215,7 +4209,7 @@ class FastAPI(Starlette): ), ] = Default(None), status_code: Annotated[ - Optional[int], + int | None, Doc( """ The default status code to be used for the response. @@ -4228,7 +4222,7 @@ class FastAPI(Starlette): ), ] = None, tags: Annotated[ - Optional[list[Union[str, Enum]]], + list[str | Enum] | None, Doc( """ A list of tags to be applied to the *path operation*. @@ -4241,7 +4235,7 @@ class FastAPI(Starlette): ), ] = None, dependencies: Annotated[ - Optional[Sequence[Depends]], + Sequence[Depends] | None, Doc( """ A list of dependencies (using `Depends()`) to be applied to the @@ -4253,7 +4247,7 @@ class FastAPI(Starlette): ), ] = None, summary: Annotated[ - Optional[str], + str | None, Doc( """ A summary for the *path operation*. @@ -4266,7 +4260,7 @@ class FastAPI(Starlette): ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ A description for the *path operation*. @@ -4294,7 +4288,7 @@ class FastAPI(Starlette): ), ] = "Successful Response", responses: Annotated[ - Optional[dict[Union[int, str], dict[str, Any]]], + dict[int | str, dict[str, Any]] | None, Doc( """ Additional responses that could be returned by this *path operation*. @@ -4304,7 +4298,7 @@ class FastAPI(Starlette): ), ] = None, deprecated: Annotated[ - Optional[bool], + bool | None, Doc( """ Mark this *path operation* as deprecated. @@ -4314,7 +4308,7 @@ class FastAPI(Starlette): ), ] = None, operation_id: Annotated[ - Optional[str], + str | None, Doc( """ Custom operation ID to be used by this *path operation*. @@ -4334,7 +4328,7 @@ class FastAPI(Starlette): ), ] = None, response_model_include: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to include only certain fields in the @@ -4346,7 +4340,7 @@ class FastAPI(Starlette): ), ] = None, response_model_exclude: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to exclude certain fields in the @@ -4448,7 +4442,7 @@ class FastAPI(Starlette): ), ] = Default(JSONResponse), name: Annotated[ - Optional[str], + str | None, Doc( """ Name for this *path operation*. Only used internally. @@ -4456,7 +4450,7 @@ class FastAPI(Starlette): ), ] = None, callbacks: Annotated[ - Optional[list[BaseRoute]], + list[BaseRoute] | None, Doc( """ List of *path operations* that will be used as OpenAPI callbacks. @@ -4472,7 +4466,7 @@ class FastAPI(Starlette): ), ] = None, openapi_extra: Annotated[ - Optional[dict[str, Any]], + dict[str, Any] | None, Doc( """ Extra metadata to be included in the OpenAPI schema for this *path @@ -4541,7 +4535,7 @@ class FastAPI(Starlette): ) def websocket_route( - self, path: str, name: Union[str, None] = None + self, path: str, name: str | None = None ) -> Callable[[DecoratedCallable], DecoratedCallable]: def decorator(func: DecoratedCallable) -> DecoratedCallable: self.router.add_websocket_route(path, func, name=name) @@ -4627,7 +4621,7 @@ class FastAPI(Starlette): def exception_handler( self, exc_class_or_status_code: Annotated[ - Union[int, type[Exception]], + int | type[Exception], Doc( """ The Exception class this would handle, or a status code. diff --git a/fastapi/background.py b/fastapi/background.py index 20803ba67..7677058c4 100644 --- a/fastapi/background.py +++ b/fastapi/background.py @@ -1,4 +1,5 @@ -from typing import Annotated, Any, Callable +from collections.abc import Callable +from typing import Annotated, Any from annotated_doc import Doc from starlette.background import BackgroundTasks as StarletteBackgroundTasks diff --git a/fastapi/datastructures.py b/fastapi/datastructures.py index 2bf5fdb26..c04b5f0f3 100644 --- a/fastapi/datastructures.py +++ b/fastapi/datastructures.py @@ -1,10 +1,8 @@ -from collections.abc import Mapping +from collections.abc import Callable, Mapping from typing import ( Annotated, Any, BinaryIO, - Callable, - Optional, TypeVar, cast, ) @@ -58,11 +56,11 @@ class UploadFile(StarletteUploadFile): BinaryIO, Doc("The standard Python file object (non-async)."), ] - filename: Annotated[Optional[str], Doc("The original file name.")] - size: Annotated[Optional[int], Doc("The size of the file in bytes.")] + filename: Annotated[str | None, Doc("The original file name.")] + size: Annotated[int | None, Doc("The size of the file in bytes.")] headers: Annotated[Headers, Doc("The headers of the request.")] content_type: Annotated[ - Optional[str], Doc("The content type of the request, from the headers.") + str | None, Doc("The content type of the request, from the headers.") ] async def write( diff --git a/fastapi/dependencies/models.py b/fastapi/dependencies/models.py index 58392326d..25ffb0d2d 100644 --- a/fastapi/dependencies/models.py +++ b/fastapi/dependencies/models.py @@ -1,13 +1,13 @@ import inspect import sys +from collections.abc import Callable from dataclasses import dataclass, field from functools import cached_property, partial -from typing import Any, Callable, Optional, Union +from typing import Any, Literal from fastapi._compat import ModelField from fastapi.security.base import SecurityBase from fastapi.types import DependencyCacheKey -from typing_extensions import Literal if sys.version_info >= (3, 13): # pragma: no cover from inspect import iscoroutinefunction @@ -15,7 +15,7 @@ else: # pragma: no cover from asyncio import iscoroutinefunction -def _unwrapped_call(call: Optional[Callable[..., Any]]) -> Any: +def _unwrapped_call(call: Callable[..., Any] | None) -> Any: if call is None: return call # pragma: no cover unwrapped = inspect.unwrap(_impartial(call)) @@ -36,19 +36,19 @@ class Dependant: cookie_params: list[ModelField] = field(default_factory=list) body_params: list[ModelField] = field(default_factory=list) dependencies: list["Dependant"] = field(default_factory=list) - name: Optional[str] = None - call: Optional[Callable[..., Any]] = None - request_param_name: Optional[str] = None - websocket_param_name: Optional[str] = None - http_connection_param_name: Optional[str] = None - response_param_name: Optional[str] = None - background_tasks_param_name: Optional[str] = None - security_scopes_param_name: Optional[str] = None - own_oauth_scopes: Optional[list[str]] = None - parent_oauth_scopes: Optional[list[str]] = None + name: str | None = None + call: Callable[..., Any] | None = None + request_param_name: str | None = None + websocket_param_name: str | None = None + http_connection_param_name: str | None = None + response_param_name: str | None = None + background_tasks_param_name: str | None = None + security_scopes_param_name: str | None = None + own_oauth_scopes: list[str] | None = None + parent_oauth_scopes: list[str] | None = None use_cache: bool = True - path: Optional[str] = None - scope: Union[Literal["function", "request"], None] = None + path: str | None = None + scope: Literal["function", "request"] | None = None @cached_property def oauth_scopes(self) -> list[str]: @@ -185,7 +185,7 @@ class Dependant: return False @cached_property - def computed_scope(self) -> Union[str, None]: + def computed_scope(self) -> str | None: if self.scope: return self.scope if self.is_gen_callable or self.is_async_gen_callable: diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py index 23d8cd9fb..ab18ec2db 100644 --- a/fastapi/dependencies/utils.py +++ b/fastapi/dependencies/utils.py @@ -1,18 +1,19 @@ import dataclasses import inspect import sys -from collections.abc import Mapping, Sequence +from collections.abc import Callable, Mapping, Sequence from contextlib import AsyncExitStack, contextmanager from copy import copy, deepcopy from dataclasses import dataclass from typing import ( Annotated, Any, - Callable, ForwardRef, - Optional, + Literal, Union, cast, + get_args, + get_origin, ) from fastapi import params @@ -63,7 +64,6 @@ from starlette.datastructures import ( from starlette.requests import HTTPConnection, Request from starlette.responses import Response from starlette.websockets import WebSocket -from typing_extensions import Literal, get_args, get_origin from typing_inspection.typing_objects import is_typealiastype multipart_not_installed_error = ( @@ -127,8 +127,8 @@ def get_flat_dependant( dependant: Dependant, *, skip_repeats: bool = False, - visited: Optional[list[DependencyCacheKey]] = None, - parent_oauth_scopes: Optional[list[str]] = None, + visited: list[DependencyCacheKey] | None = None, + parent_oauth_scopes: list[str] | None = None, ) -> Dependant: if visited is None: visited = [] @@ -199,20 +199,17 @@ def get_flat_params(dependant: Dependant) -> list[ModelField]: def _get_signature(call: Callable[..., Any]) -> inspect.Signature: - if sys.version_info >= (3, 10): - try: - signature = inspect.signature(call, eval_str=True) - except NameError: - # Handle type annotations with if TYPE_CHECKING, not used by FastAPI - # e.g. dependency return types - if sys.version_info >= (3, 14): - from annotationlib import Format + try: + signature = inspect.signature(call, eval_str=True) + except NameError: + # Handle type annotations with if TYPE_CHECKING, not used by FastAPI + # e.g. dependency return types + if sys.version_info >= (3, 14): + from annotationlib import Format - signature = inspect.signature(call, annotation_format=Format.FORWARDREF) - else: - signature = inspect.signature(call) - else: - signature = inspect.signature(call) + signature = inspect.signature(call, annotation_format=Format.FORWARDREF) + else: + signature = inspect.signature(call) return signature @@ -258,11 +255,11 @@ def get_dependant( *, path: str, call: Callable[..., Any], - name: Optional[str] = None, - own_oauth_scopes: Optional[list[str]] = None, - parent_oauth_scopes: Optional[list[str]] = None, + name: str | None = None, + own_oauth_scopes: list[str] | None = None, + parent_oauth_scopes: list[str] | None = None, use_cache: bool = True, - scope: Union[Literal["function", "request"], None] = None, + scope: Literal["function", "request"] | None = None, ) -> Dependant: dependant = Dependant( call=call, @@ -331,7 +328,7 @@ def get_dependant( def add_non_field_param_to_dependency( *, param_name: str, type_annotation: Any, dependant: Dependant -) -> Optional[bool]: +) -> bool | None: if lenient_issubclass(type_annotation, Request): dependant.request_param_name = param_name return True @@ -356,8 +353,8 @@ def add_non_field_param_to_dependency( @dataclass class ParamDetails: type_annotation: Any - depends: Optional[params.Depends] - field: Optional[ModelField] + depends: params.Depends | None + field: ModelField | None def analyze_param( @@ -399,7 +396,7 @@ def analyze_param( ) ] if fastapi_specific_annotations: - fastapi_annotation: Union[FieldInfo, params.Depends, None] = ( + fastapi_annotation: FieldInfo | params.Depends | None = ( fastapi_specific_annotations[-1] ) else: @@ -560,20 +557,20 @@ async def _solve_generator( class SolvedDependency: values: dict[str, Any] errors: list[Any] - background_tasks: Optional[StarletteBackgroundTasks] + background_tasks: StarletteBackgroundTasks | None response: Response dependency_cache: dict[DependencyCacheKey, Any] async def solve_dependencies( *, - request: Union[Request, WebSocket], + request: Request | WebSocket, dependant: Dependant, - body: Optional[Union[dict[str, Any], FormData]] = None, - background_tasks: Optional[StarletteBackgroundTasks] = None, - response: Optional[Response] = None, - dependency_overrides_provider: Optional[Any] = None, - dependency_cache: Optional[dict[DependencyCacheKey, Any]] = None, + body: dict[str, Any] | FormData | None = None, + background_tasks: StarletteBackgroundTasks | None = None, + response: Response | None = None, + dependency_overrides_provider: Any | None = None, + dependency_cache: dict[DependencyCacheKey, Any] | None = None, # TODO: remove this parameter later, no longer used, not removing it yet as some # people might be monkey patching this function (although that's not supported) async_exit_stack: AsyncExitStack, @@ -721,7 +718,7 @@ def _is_json_field(field: ModelField) -> bool: def _get_multidict_value( - field: ModelField, values: Mapping[str, Any], alias: Union[str, None] = None + field: ModelField, values: Mapping[str, Any], alias: str | None = None ) -> Any: alias = alias or get_validation_alias(field) if ( @@ -753,7 +750,7 @@ def _get_multidict_value( def request_params_to_args( fields: Sequence[ModelField], - received_params: Union[Mapping[str, Any], QueryParams, Headers], + received_params: Mapping[str, Any] | QueryParams | Headers, ) -> tuple[dict[str, Any], list[Any]]: values: dict[str, Any] = {} errors: list[dict[str, Any]] = [] @@ -901,7 +898,7 @@ async def _extract_form_body( ): # For types assert isinstance(value, sequence_types) - results: list[Union[bytes, str]] = [] + results: list[bytes | str] = [] for sub_value in value: results.append(await sub_value.read()) value = serialize_sequence_value(field=field, value=results) @@ -920,7 +917,7 @@ async def _extract_form_body( async def request_body_to_args( body_fields: list[ModelField], - received_body: Optional[Union[dict[str, Any], FormData]], + received_body: dict[str, Any] | FormData | None, embed_body_fields: bool, ) -> tuple[dict[str, Any], list[dict[str, Any]]]: values: dict[str, Any] = {} @@ -950,7 +947,7 @@ async def request_body_to_args( return {first_field.name: v_}, errors_ for field in body_fields: loc = ("body", get_validation_alias(field)) - value: Optional[Any] = None + value: Any | None = None if body_to_process is not None: try: value = body_to_process.get(get_validation_alias(field)) @@ -970,7 +967,7 @@ async def request_body_to_args( def get_body_field( *, flat_dependant: Dependant, name: str, embed_body_fields: bool -) -> Optional[ModelField]: +) -> ModelField | None: """ Get a ModelField representing the request body for a path operation, combining all body parameters into a single field if necessary. diff --git a/fastapi/encoders.py b/fastapi/encoders.py index b4661be4f..e20255c11 100644 --- a/fastapi/encoders.py +++ b/fastapi/encoders.py @@ -1,6 +1,7 @@ import dataclasses import datetime from collections import defaultdict, deque +from collections.abc import Callable from decimal import Decimal from enum import Enum from ipaddress import ( @@ -14,7 +15,7 @@ from ipaddress import ( from pathlib import Path, PurePath from re import Pattern from types import GeneratorType -from typing import Annotated, Any, Callable, Optional, Union +from typing import Annotated, Any from uuid import UUID from annotated_doc import Doc @@ -33,13 +34,13 @@ from ._compat import ( # Taken from Pydantic v1 as is -def isoformat(o: Union[datetime.date, datetime.time]) -> str: +def isoformat(o: datetime.date | datetime.time) -> str: return o.isoformat() # Adapted from Pydantic v1 # TODO: pv2 should this return strings instead? -def decimal_encoder(dec_value: Decimal) -> Union[int, float]: +def decimal_encoder(dec_value: Decimal) -> int | float: """ Encodes a Decimal as int if there's no exponent, otherwise float @@ -118,7 +119,7 @@ def jsonable_encoder( ), ], include: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Pydantic's `include` parameter, passed to Pydantic models to set the @@ -127,7 +128,7 @@ def jsonable_encoder( ), ] = None, exclude: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Pydantic's `exclude` parameter, passed to Pydantic models to set the @@ -177,7 +178,7 @@ def jsonable_encoder( ), ] = False, custom_encoder: Annotated[ - Optional[dict[Any, Callable[[Any], Any]]], + dict[Any, Callable[[Any], Any]] | None, Doc( """ Pydantic's `custom_encoder` parameter, passed to Pydantic models to define diff --git a/fastapi/exceptions.py b/fastapi/exceptions.py index 9924d0e2b..d7065c52f 100644 --- a/fastapi/exceptions.py +++ b/fastapi/exceptions.py @@ -1,5 +1,5 @@ from collections.abc import Mapping, Sequence -from typing import Annotated, Any, Optional, TypedDict, Union +from typing import Annotated, Any, TypedDict from annotated_doc import Doc from pydantic import BaseModel, create_model @@ -68,7 +68,7 @@ class HTTPException(StarletteHTTPException): ), ] = None, headers: Annotated[ - Optional[Mapping[str, str]], + Mapping[str, str] | None, Doc( """ Any headers to send to the client in the response. @@ -137,7 +137,7 @@ class WebSocketException(StarletteWebSocketException): ), ], reason: Annotated[ - Union[str, None], + str | None, Doc( """ The reason to close the WebSocket connection. @@ -176,7 +176,7 @@ class ValidationException(Exception): self, errors: Sequence[Any], *, - endpoint_ctx: Optional[EndpointContext] = None, + endpoint_ctx: EndpointContext | None = None, ) -> None: self._errors = errors self.endpoint_ctx = endpoint_ctx @@ -215,7 +215,7 @@ class RequestValidationError(ValidationException): errors: Sequence[Any], *, body: Any = None, - endpoint_ctx: Optional[EndpointContext] = None, + endpoint_ctx: EndpointContext | None = None, ) -> None: super().__init__(errors, endpoint_ctx=endpoint_ctx) self.body = body @@ -226,7 +226,7 @@ class WebSocketRequestValidationError(ValidationException): self, errors: Sequence[Any], *, - endpoint_ctx: Optional[EndpointContext] = None, + endpoint_ctx: EndpointContext | None = None, ) -> None: super().__init__(errors, endpoint_ctx=endpoint_ctx) @@ -237,7 +237,7 @@ class ResponseValidationError(ValidationException): errors: Sequence[Any], *, body: Any = None, - endpoint_ctx: Optional[EndpointContext] = None, + endpoint_ctx: EndpointContext | None = None, ) -> None: super().__init__(errors, endpoint_ctx=endpoint_ctx) self.body = body diff --git a/fastapi/openapi/docs.py b/fastapi/openapi/docs.py index bb387c609..b845f87c1 100644 --- a/fastapi/openapi/docs.py +++ b/fastapi/openapi/docs.py @@ -1,5 +1,5 @@ import json -from typing import Annotated, Any, Optional +from typing import Annotated, Any from annotated_doc import Doc from fastapi.encoders import jsonable_encoder @@ -85,7 +85,7 @@ def get_swagger_ui_html( ), ] = "https://fastapi.tiangolo.com/img/favicon.png", oauth2_redirect_url: Annotated[ - Optional[str], + str | None, Doc( """ The OAuth2 redirect URL, it is normally automatically handled by FastAPI. @@ -96,7 +96,7 @@ def get_swagger_ui_html( ), ] = None, init_oauth: Annotated[ - Optional[dict[str, Any]], + dict[str, Any] | None, Doc( """ A dictionary with Swagger UI OAuth2 initialization configurations. @@ -107,7 +107,7 @@ def get_swagger_ui_html( ), ] = None, swagger_ui_parameters: Annotated[ - Optional[dict[str, Any]], + dict[str, Any] | None, Doc( """ Configuration parameters for Swagger UI. diff --git a/fastapi/openapi/models.py b/fastapi/openapi/models.py index 095990639..d7950241f 100644 --- a/fastapi/openapi/models.py +++ b/fastapi/openapi/models.py @@ -1,6 +1,6 @@ -from collections.abc import Iterable, Mapping +from collections.abc import Callable, Iterable, Mapping from enum import Enum -from typing import Annotated, Any, Callable, Optional, Union +from typing import Annotated, Any, Literal, Optional, Union from fastapi._compat import with_info_plain_validator_function from fastapi.logger import logger @@ -10,7 +10,7 @@ from pydantic import ( Field, GetJsonSchemaHandler, ) -from typing_extensions import Literal, TypedDict +from typing_extensions import TypedDict from typing_extensions import deprecated as typing_deprecated try: @@ -59,37 +59,37 @@ class BaseModelWithConfig(BaseModel): class Contact(BaseModelWithConfig): - name: Optional[str] = None - url: Optional[AnyUrl] = None - email: Optional[EmailStr] = None + name: str | None = None + url: AnyUrl | None = None + email: EmailStr | None = None class License(BaseModelWithConfig): name: str - identifier: Optional[str] = None - url: Optional[AnyUrl] = None + identifier: str | None = None + url: AnyUrl | None = None class Info(BaseModelWithConfig): title: str - summary: Optional[str] = None - description: Optional[str] = None - termsOfService: Optional[str] = None - contact: Optional[Contact] = None - license: Optional[License] = None + summary: str | None = None + description: str | None = None + termsOfService: str | None = None + contact: Contact | None = None + license: License | None = None version: str class ServerVariable(BaseModelWithConfig): - enum: Annotated[Optional[list[str]], Field(min_length=1)] = None + enum: Annotated[list[str] | None, Field(min_length=1)] = None default: str - description: Optional[str] = None + description: str | None = None class Server(BaseModelWithConfig): - url: Union[AnyUrl, str] - description: Optional[str] = None - variables: Optional[dict[str, ServerVariable]] = None + url: AnyUrl | str + description: str | None = None + variables: dict[str, ServerVariable] | None = None class Reference(BaseModel): @@ -98,19 +98,19 @@ class Reference(BaseModel): class Discriminator(BaseModel): propertyName: str - mapping: Optional[dict[str, str]] = None + mapping: dict[str, str] | None = None class XML(BaseModelWithConfig): - name: Optional[str] = None - namespace: Optional[str] = None - prefix: Optional[str] = None - attribute: Optional[bool] = None - wrapped: Optional[bool] = None + name: str | None = None + namespace: str | None = None + prefix: str | None = None + attribute: bool | None = None + wrapped: bool | None = None class ExternalDocumentation(BaseModelWithConfig): - description: Optional[str] = None + description: str | None = None url: AnyUrl @@ -123,80 +123,80 @@ SchemaType = Literal[ class Schema(BaseModelWithConfig): # Ref: JSON Schema 2020-12: https://json-schema.org/draft/2020-12/json-schema-core.html#name-the-json-schema-core-vocabu # Core Vocabulary - schema_: Optional[str] = Field(default=None, alias="$schema") - vocabulary: Optional[str] = Field(default=None, alias="$vocabulary") - id: Optional[str] = Field(default=None, alias="$id") - anchor: Optional[str] = Field(default=None, alias="$anchor") - dynamicAnchor: Optional[str] = Field(default=None, alias="$dynamicAnchor") - ref: Optional[str] = Field(default=None, alias="$ref") - dynamicRef: Optional[str] = Field(default=None, alias="$dynamicRef") - defs: Optional[dict[str, "SchemaOrBool"]] = Field(default=None, alias="$defs") - comment: Optional[str] = Field(default=None, alias="$comment") + schema_: str | None = Field(default=None, alias="$schema") + vocabulary: str | None = Field(default=None, alias="$vocabulary") + id: str | None = Field(default=None, alias="$id") + anchor: str | None = Field(default=None, alias="$anchor") + dynamicAnchor: str | None = Field(default=None, alias="$dynamicAnchor") + ref: str | None = Field(default=None, alias="$ref") + dynamicRef: str | None = Field(default=None, alias="$dynamicRef") + defs: dict[str, "SchemaOrBool"] | None = Field(default=None, alias="$defs") + comment: str | None = Field(default=None, alias="$comment") # Ref: JSON Schema 2020-12: https://json-schema.org/draft/2020-12/json-schema-core.html#name-a-vocabulary-for-applying-s # A Vocabulary for Applying Subschemas - allOf: Optional[list["SchemaOrBool"]] = None - anyOf: Optional[list["SchemaOrBool"]] = None - oneOf: Optional[list["SchemaOrBool"]] = None + allOf: list["SchemaOrBool"] | None = None + anyOf: list["SchemaOrBool"] | None = None + oneOf: list["SchemaOrBool"] | None = None not_: Optional["SchemaOrBool"] = Field(default=None, alias="not") if_: Optional["SchemaOrBool"] = Field(default=None, alias="if") then: Optional["SchemaOrBool"] = None else_: Optional["SchemaOrBool"] = Field(default=None, alias="else") - dependentSchemas: Optional[dict[str, "SchemaOrBool"]] = None - prefixItems: Optional[list["SchemaOrBool"]] = None + dependentSchemas: dict[str, "SchemaOrBool"] | None = None + prefixItems: list["SchemaOrBool"] | None = None items: Optional["SchemaOrBool"] = None contains: Optional["SchemaOrBool"] = None - properties: Optional[dict[str, "SchemaOrBool"]] = None - patternProperties: Optional[dict[str, "SchemaOrBool"]] = None + properties: dict[str, "SchemaOrBool"] | None = None + patternProperties: dict[str, "SchemaOrBool"] | None = None additionalProperties: Optional["SchemaOrBool"] = None propertyNames: Optional["SchemaOrBool"] = None unevaluatedItems: Optional["SchemaOrBool"] = None unevaluatedProperties: Optional["SchemaOrBool"] = None # Ref: JSON Schema Validation 2020-12: https://json-schema.org/draft/2020-12/json-schema-validation.html#name-a-vocabulary-for-structural # A Vocabulary for Structural Validation - type: Optional[Union[SchemaType, list[SchemaType]]] = None - enum: Optional[list[Any]] = None - const: Optional[Any] = None - multipleOf: Optional[float] = Field(default=None, gt=0) - maximum: Optional[float] = None - exclusiveMaximum: Optional[float] = None - minimum: Optional[float] = None - exclusiveMinimum: Optional[float] = None - maxLength: Optional[int] = Field(default=None, ge=0) - minLength: Optional[int] = Field(default=None, ge=0) - pattern: Optional[str] = None - maxItems: Optional[int] = Field(default=None, ge=0) - minItems: Optional[int] = Field(default=None, ge=0) - uniqueItems: Optional[bool] = None - maxContains: Optional[int] = Field(default=None, ge=0) - minContains: Optional[int] = Field(default=None, ge=0) - maxProperties: Optional[int] = Field(default=None, ge=0) - minProperties: Optional[int] = Field(default=None, ge=0) - required: Optional[list[str]] = None - dependentRequired: Optional[dict[str, set[str]]] = None + type: SchemaType | list[SchemaType] | None = None + enum: list[Any] | None = None + const: Any | None = None + multipleOf: float | None = Field(default=None, gt=0) + maximum: float | None = None + exclusiveMaximum: float | None = None + minimum: float | None = None + exclusiveMinimum: float | None = None + maxLength: int | None = Field(default=None, ge=0) + minLength: int | None = Field(default=None, ge=0) + pattern: str | None = None + maxItems: int | None = Field(default=None, ge=0) + minItems: int | None = Field(default=None, ge=0) + uniqueItems: bool | None = None + maxContains: int | None = Field(default=None, ge=0) + minContains: int | None = Field(default=None, ge=0) + maxProperties: int | None = Field(default=None, ge=0) + minProperties: int | None = Field(default=None, ge=0) + required: list[str] | None = None + dependentRequired: dict[str, set[str]] | None = None # Ref: JSON Schema Validation 2020-12: https://json-schema.org/draft/2020-12/json-schema-validation.html#name-vocabularies-for-semantic-c # Vocabularies for Semantic Content With "format" - format: Optional[str] = None + format: str | None = None # Ref: JSON Schema Validation 2020-12: https://json-schema.org/draft/2020-12/json-schema-validation.html#name-a-vocabulary-for-the-conten # A Vocabulary for the Contents of String-Encoded Data - contentEncoding: Optional[str] = None - contentMediaType: Optional[str] = None + contentEncoding: str | None = None + contentMediaType: str | None = None contentSchema: Optional["SchemaOrBool"] = None # Ref: JSON Schema Validation 2020-12: https://json-schema.org/draft/2020-12/json-schema-validation.html#name-a-vocabulary-for-basic-meta # A Vocabulary for Basic Meta-Data Annotations - title: Optional[str] = None - description: Optional[str] = None - default: Optional[Any] = None - deprecated: Optional[bool] = None - readOnly: Optional[bool] = None - writeOnly: Optional[bool] = None - examples: Optional[list[Any]] = None + title: str | None = None + description: str | None = None + default: Any | None = None + deprecated: bool | None = None + readOnly: bool | None = None + writeOnly: bool | None = None + examples: list[Any] | None = None # Ref: OpenAPI 3.1.0: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#schema-object # Schema Object - discriminator: Optional[Discriminator] = None - xml: Optional[XML] = None - externalDocs: Optional[ExternalDocumentation] = None + discriminator: Discriminator | None = None + xml: XML | None = None + externalDocs: ExternalDocumentation | None = None example: Annotated[ - Optional[Any], + Any | None, typing_deprecated( "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " "although still supported. Use examples instead." @@ -206,14 +206,14 @@ class Schema(BaseModelWithConfig): # Ref: https://json-schema.org/draft/2020-12/json-schema-core.html#name-json-schema-documents # A JSON Schema MUST be an object or a boolean. -SchemaOrBool = Union[Schema, bool] +SchemaOrBool = Schema | bool class Example(TypedDict, total=False): - summary: Optional[str] - description: Optional[str] - value: Optional[Any] - externalValue: Optional[AnyUrl] + summary: str | None + description: str | None + value: Any | None + externalValue: AnyUrl | None __pydantic_config__ = {"extra": "allow"} # type: ignore[misc] @@ -226,33 +226,33 @@ class ParameterInType(Enum): class Encoding(BaseModelWithConfig): - contentType: Optional[str] = None - headers: Optional[dict[str, Union["Header", Reference]]] = None - style: Optional[str] = None - explode: Optional[bool] = None - allowReserved: Optional[bool] = None + contentType: str | None = None + headers: dict[str, Union["Header", Reference]] | None = None + style: str | None = None + explode: bool | None = None + allowReserved: bool | None = None class MediaType(BaseModelWithConfig): - schema_: Optional[Union[Schema, Reference]] = Field(default=None, alias="schema") - example: Optional[Any] = None - examples: Optional[dict[str, Union[Example, Reference]]] = None - encoding: Optional[dict[str, Encoding]] = None + schema_: Schema | Reference | None = Field(default=None, alias="schema") + example: Any | None = None + examples: dict[str, Example | Reference] | None = None + encoding: dict[str, Encoding] | None = None class ParameterBase(BaseModelWithConfig): - description: Optional[str] = None - required: Optional[bool] = None - deprecated: Optional[bool] = None + description: str | None = None + required: bool | None = None + deprecated: bool | None = None # Serialization rules for simple scenarios - style: Optional[str] = None - explode: Optional[bool] = None - allowReserved: Optional[bool] = None - schema_: Optional[Union[Schema, Reference]] = Field(default=None, alias="schema") - example: Optional[Any] = None - examples: Optional[dict[str, Union[Example, Reference]]] = None + style: str | None = None + explode: bool | None = None + allowReserved: bool | None = None + schema_: Schema | Reference | None = Field(default=None, alias="schema") + example: Any | None = None + examples: dict[str, Example | Reference] | None = None # Serialization rules for more complex scenarios - content: Optional[dict[str, MediaType]] = None + content: dict[str, MediaType] | None = None class Parameter(ParameterBase): @@ -265,57 +265,57 @@ class Header(ParameterBase): class RequestBody(BaseModelWithConfig): - description: Optional[str] = None + description: str | None = None content: dict[str, MediaType] - required: Optional[bool] = None + required: bool | None = None class Link(BaseModelWithConfig): - operationRef: Optional[str] = None - operationId: Optional[str] = None - parameters: Optional[dict[str, Union[Any, str]]] = None - requestBody: Optional[Union[Any, str]] = None - description: Optional[str] = None - server: Optional[Server] = None + operationRef: str | None = None + operationId: str | None = None + parameters: dict[str, Any | str] | None = None + requestBody: Any | str | None = None + description: str | None = None + server: Server | None = None class Response(BaseModelWithConfig): description: str - headers: Optional[dict[str, Union[Header, Reference]]] = None - content: Optional[dict[str, MediaType]] = None - links: Optional[dict[str, Union[Link, Reference]]] = None + headers: dict[str, Header | Reference] | None = None + content: dict[str, MediaType] | None = None + links: dict[str, Link | Reference] | None = None class Operation(BaseModelWithConfig): - tags: Optional[list[str]] = None - summary: Optional[str] = None - description: Optional[str] = None - externalDocs: Optional[ExternalDocumentation] = None - operationId: Optional[str] = None - parameters: Optional[list[Union[Parameter, Reference]]] = None - requestBody: Optional[Union[RequestBody, Reference]] = None + tags: list[str] | None = None + summary: str | None = None + description: str | None = None + externalDocs: ExternalDocumentation | None = None + operationId: str | None = None + parameters: list[Parameter | Reference] | None = None + requestBody: RequestBody | Reference | None = None # Using Any for Specification Extensions - responses: Optional[dict[str, Union[Response, Any]]] = None - callbacks: Optional[dict[str, Union[dict[str, "PathItem"], Reference]]] = None - deprecated: Optional[bool] = None - security: Optional[list[dict[str, list[str]]]] = None - servers: Optional[list[Server]] = None + responses: dict[str, Response | Any] | None = None + callbacks: dict[str, dict[str, "PathItem"] | Reference] | None = None + deprecated: bool | None = None + security: list[dict[str, list[str]]] | None = None + servers: list[Server] | None = None class PathItem(BaseModelWithConfig): - ref: Optional[str] = Field(default=None, alias="$ref") - summary: Optional[str] = None - description: Optional[str] = None - get: Optional[Operation] = None - put: Optional[Operation] = None - post: Optional[Operation] = None - delete: Optional[Operation] = None - options: Optional[Operation] = None - head: Optional[Operation] = None - patch: Optional[Operation] = None - trace: Optional[Operation] = None - servers: Optional[list[Server]] = None - parameters: Optional[list[Union[Parameter, Reference]]] = None + ref: str | None = Field(default=None, alias="$ref") + summary: str | None = None + description: str | None = None + get: Operation | None = None + put: Operation | None = None + post: Operation | None = None + delete: Operation | None = None + options: Operation | None = None + head: Operation | None = None + patch: Operation | None = None + trace: Operation | None = None + servers: list[Server] | None = None + parameters: list[Parameter | Reference] | None = None class SecuritySchemeType(Enum): @@ -327,7 +327,7 @@ class SecuritySchemeType(Enum): class SecurityBase(BaseModelWithConfig): type_: SecuritySchemeType = Field(alias="type") - description: Optional[str] = None + description: str | None = None class APIKeyIn(Enum): @@ -349,11 +349,11 @@ class HTTPBase(SecurityBase): class HTTPBearer(HTTPBase): scheme: Literal["bearer"] = "bearer" - bearerFormat: Optional[str] = None + bearerFormat: str | None = None class OAuthFlow(BaseModelWithConfig): - refreshUrl: Optional[str] = None + refreshUrl: str | None = None scopes: dict[str, str] = {} @@ -375,10 +375,10 @@ class OAuthFlowAuthorizationCode(OAuthFlow): class OAuthFlows(BaseModelWithConfig): - implicit: Optional[OAuthFlowImplicit] = None - password: Optional[OAuthFlowPassword] = None - clientCredentials: Optional[OAuthFlowClientCredentials] = None - authorizationCode: Optional[OAuthFlowAuthorizationCode] = None + implicit: OAuthFlowImplicit | None = None + password: OAuthFlowPassword | None = None + clientCredentials: OAuthFlowClientCredentials | None = None + authorizationCode: OAuthFlowAuthorizationCode | None = None class OAuth2(SecurityBase): @@ -393,41 +393,41 @@ class OpenIdConnect(SecurityBase): openIdConnectUrl: str -SecurityScheme = Union[APIKey, HTTPBase, OAuth2, OpenIdConnect, HTTPBearer] +SecurityScheme = APIKey | HTTPBase | OAuth2 | OpenIdConnect | HTTPBearer class Components(BaseModelWithConfig): - schemas: Optional[dict[str, Union[Schema, Reference]]] = None - responses: Optional[dict[str, Union[Response, Reference]]] = None - parameters: Optional[dict[str, Union[Parameter, Reference]]] = None - examples: Optional[dict[str, Union[Example, Reference]]] = None - requestBodies: Optional[dict[str, Union[RequestBody, Reference]]] = None - headers: Optional[dict[str, Union[Header, Reference]]] = None - securitySchemes: Optional[dict[str, Union[SecurityScheme, Reference]]] = None - links: Optional[dict[str, Union[Link, Reference]]] = None + schemas: dict[str, Schema | Reference] | None = None + responses: dict[str, Response | Reference] | None = None + parameters: dict[str, Parameter | Reference] | None = None + examples: dict[str, Example | Reference] | None = None + requestBodies: dict[str, RequestBody | Reference] | None = None + headers: dict[str, Header | Reference] | None = None + securitySchemes: dict[str, SecurityScheme | Reference] | None = None + links: dict[str, Link | Reference] | None = None # Using Any for Specification Extensions - callbacks: Optional[dict[str, Union[dict[str, PathItem], Reference, Any]]] = None - pathItems: Optional[dict[str, Union[PathItem, Reference]]] = None + callbacks: dict[str, dict[str, PathItem] | Reference | Any] | None = None + pathItems: dict[str, PathItem | Reference] | None = None class Tag(BaseModelWithConfig): name: str - description: Optional[str] = None - externalDocs: Optional[ExternalDocumentation] = None + description: str | None = None + externalDocs: ExternalDocumentation | None = None class OpenAPI(BaseModelWithConfig): openapi: str info: Info - jsonSchemaDialect: Optional[str] = None - servers: Optional[list[Server]] = None + jsonSchemaDialect: str | None = None + servers: list[Server] | None = None # Using Any for Specification Extensions - paths: Optional[dict[str, Union[PathItem, Any]]] = None - webhooks: Optional[dict[str, Union[PathItem, Reference]]] = None - components: Optional[Components] = None - security: Optional[list[dict[str, list[str]]]] = None - tags: Optional[list[Tag]] = None - externalDocs: Optional[ExternalDocumentation] = None + paths: dict[str, PathItem | Any] | None = None + webhooks: dict[str, PathItem | Reference] | None = None + components: Components | None = None + security: list[dict[str, list[str]]] | None = None + tags: list[Tag] | None = None + externalDocs: ExternalDocumentation | None = None Schema.model_rebuild() diff --git a/fastapi/openapi/utils.py b/fastapi/openapi/utils.py index bcad0be75..812003aee 100644 --- a/fastapi/openapi/utils.py +++ b/fastapi/openapi/utils.py @@ -3,7 +3,7 @@ import http.client import inspect import warnings from collections.abc import Sequence -from typing import Any, Optional, Union, cast +from typing import Any, Literal, cast from fastapi import routing from fastapi._compat import ( @@ -38,7 +38,6 @@ from fastapi.utils import ( from pydantic import BaseModel from starlette.responses import JSONResponse from starlette.routing import BaseRoute -from typing_extensions import Literal validation_error_definition = { "title": "ValidationError", @@ -180,13 +179,13 @@ def _get_openapi_operation_parameters( def get_openapi_operation_request_body( *, - body_field: Optional[ModelField], + body_field: ModelField | None, model_name_map: ModelNameMap, field_mapping: dict[ tuple[ModelField, Literal["validation", "serialization"]], dict[str, Any] ], separate_input_output_schemas: bool = True, -) -> Optional[dict[str, Any]]: +) -> dict[str, Any] | None: if not body_field: return None assert isinstance(body_field, ModelField) @@ -279,7 +278,7 @@ def get_openapi_path( else: current_response_class = route.response_class assert current_response_class, "A response class is needed to generate OpenAPI" - route_response_media_type: Optional[str] = current_response_class.media_type + route_response_media_type: str | None = current_response_class.media_type if route.include_in_schema: for method in route.methods: operation = get_openapi_operation_metadata( @@ -393,7 +392,7 @@ def get_openapi_path( "An additional response must be a dict" ) field = route.response_fields.get(additional_status_code) - additional_field_schema: Optional[dict[str, Any]] = None + additional_field_schema: dict[str, Any] | None = None if field: additional_field_schema = get_schema_from_model_field( field=field, @@ -408,7 +407,7 @@ def get_openapi_path( .setdefault("schema", {}) ) deep_dict_update(additional_schema, additional_field_schema) - status_text: Optional[str] = status_code_ranges.get( + status_text: str | None = status_code_ranges.get( str(additional_status_code).upper() ) or http.client.responses.get(int(additional_status_code)) description = ( @@ -482,17 +481,17 @@ def get_openapi( title: str, version: str, openapi_version: str = "3.1.0", - summary: Optional[str] = None, - description: Optional[str] = None, + summary: str | None = None, + description: str | None = None, routes: Sequence[BaseRoute], - webhooks: Optional[Sequence[BaseRoute]] = None, - tags: Optional[list[dict[str, Any]]] = None, - servers: Optional[list[dict[str, Union[str, Any]]]] = None, - terms_of_service: Optional[str] = None, - contact: Optional[dict[str, Union[str, Any]]] = None, - license_info: Optional[dict[str, Union[str, Any]]] = None, + webhooks: Sequence[BaseRoute] | None = None, + tags: list[dict[str, Any]] | None = None, + servers: list[dict[str, str | Any]] | None = None, + terms_of_service: str | None = None, + contact: dict[str, str | Any] | None = None, + license_info: dict[str, str | Any] | None = None, separate_input_output_schemas: bool = True, - external_docs: Optional[dict[str, Any]] = None, + external_docs: dict[str, Any] | None = None, ) -> dict[str, Any]: info: dict[str, Any] = {"title": title, "version": version} if summary: diff --git a/fastapi/param_functions.py b/fastapi/param_functions.py index 9bd92be4c..4be504f43 100644 --- a/fastapi/param_functions.py +++ b/fastapi/param_functions.py @@ -1,12 +1,12 @@ -from collections.abc import Sequence -from typing import Annotated, Any, Callable, Optional, Union +from collections.abc import Callable, Sequence +from typing import Annotated, Any, Literal from annotated_doc import Doc from fastapi import params from fastapi._compat import Undefined from fastapi.openapi.models import Example from pydantic import AliasChoices, AliasPath -from typing_extensions import Literal, deprecated +from typing_extensions import deprecated _Unset: Any = Undefined @@ -25,7 +25,7 @@ def Path( # noqa: N802 ] = ..., *, default_factory: Annotated[ - Union[Callable[[], Any], None], + Callable[[], Any] | None, Doc( """ A callable to generate the default value. @@ -36,7 +36,7 @@ def Path( # noqa: N802 ), ] = _Unset, alias: Annotated[ - Optional[str], + str | None, Doc( """ An alternative name for the parameter field. @@ -48,7 +48,7 @@ def Path( # noqa: N802 ), ] = None, alias_priority: Annotated[ - Union[int, None], + int | None, Doc( """ Priority of the alias. This affects whether an alias generator is used. @@ -56,7 +56,7 @@ def Path( # noqa: N802 ), ] = _Unset, validation_alias: Annotated[ - Union[str, AliasPath, AliasChoices, None], + str | AliasPath | AliasChoices | None, Doc( """ 'Whitelist' validation step. The parameter field will be the single one @@ -65,7 +65,7 @@ def Path( # noqa: N802 ), ] = None, serialization_alias: Annotated[ - Union[str, None], + str | None, Doc( """ 'Blacklist' validation step. The vanilla parameter field will be the @@ -75,7 +75,7 @@ def Path( # noqa: N802 ), ] = None, title: Annotated[ - Optional[str], + str | None, Doc( """ Human-readable title. @@ -86,7 +86,7 @@ def Path( # noqa: N802 ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ Human-readable description. @@ -94,7 +94,7 @@ def Path( # noqa: N802 ), ] = None, gt: Annotated[ - Optional[float], + float | None, Doc( """ Greater than. If set, value must be greater than this. Only applicable to @@ -106,7 +106,7 @@ def Path( # noqa: N802 ), ] = None, ge: Annotated[ - Optional[float], + float | None, Doc( """ Greater than or equal. If set, value must be greater than or equal to @@ -118,7 +118,7 @@ def Path( # noqa: N802 ), ] = None, lt: Annotated[ - Optional[float], + float | None, Doc( """ Less than. If set, value must be less than this. Only applicable to numbers. @@ -129,7 +129,7 @@ def Path( # noqa: N802 ), ] = None, le: Annotated[ - Optional[float], + float | None, Doc( """ Less than or equal. If set, value must be less than or equal to this. @@ -141,7 +141,7 @@ def Path( # noqa: N802 ), ] = None, min_length: Annotated[ - Optional[int], + int | None, Doc( """ Minimum length for strings. @@ -149,7 +149,7 @@ def Path( # noqa: N802 ), ] = None, max_length: Annotated[ - Optional[int], + int | None, Doc( """ Maximum length for strings. @@ -157,7 +157,7 @@ def Path( # noqa: N802 ), ] = None, pattern: Annotated[ - Optional[str], + str | None, Doc( """ RegEx pattern for strings. @@ -165,7 +165,7 @@ def Path( # noqa: N802 ), ] = None, regex: Annotated[ - Optional[str], + str | None, Doc( """ RegEx pattern for strings. @@ -176,7 +176,7 @@ def Path( # noqa: N802 ), ] = None, discriminator: Annotated[ - Union[str, None], + str | None, Doc( """ Parameter field name for discriminating the type in a tagged union. @@ -184,7 +184,7 @@ def Path( # noqa: N802 ), ] = None, strict: Annotated[ - Union[bool, None], + bool | None, Doc( """ If `True`, strict validation is applied to the field. @@ -192,7 +192,7 @@ def Path( # noqa: N802 ), ] = _Unset, multiple_of: Annotated[ - Union[float, None], + float | None, Doc( """ Value must be a multiple of this. Only applicable to numbers. @@ -200,7 +200,7 @@ def Path( # noqa: N802 ), ] = _Unset, allow_inf_nan: Annotated[ - Union[bool, None], + bool | None, Doc( """ Allow `inf`, `-inf`, `nan`. Only applicable to numbers. @@ -208,7 +208,7 @@ def Path( # noqa: N802 ), ] = _Unset, max_digits: Annotated[ - Union[int, None], + int | None, Doc( """ Maximum number of allow digits for strings. @@ -216,7 +216,7 @@ def Path( # noqa: N802 ), ] = _Unset, decimal_places: Annotated[ - Union[int, None], + int | None, Doc( """ Maximum number of decimal places allowed for numbers. @@ -224,7 +224,7 @@ def Path( # noqa: N802 ), ] = _Unset, examples: Annotated[ - Optional[list[Any]], + list[Any] | None, Doc( """ Example values for this field. @@ -235,14 +235,14 @@ def Path( # noqa: N802 ), ] = None, example: Annotated[ - Optional[Any], + Any | None, deprecated( "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " "although still supported. Use examples instead." ), ] = _Unset, openapi_examples: Annotated[ - Optional[dict[str, Example]], + dict[str, Example] | None, Doc( """ OpenAPI-specific examples. @@ -259,7 +259,7 @@ def Path( # noqa: N802 ), ] = None, deprecated: Annotated[ - Union[deprecated, str, bool, None], + deprecated | str | bool | None, Doc( """ Mark this parameter field as deprecated. @@ -280,7 +280,7 @@ def Path( # noqa: N802 ), ] = True, json_schema_extra: Annotated[ - Union[dict[str, Any], None], + dict[str, Any] | None, Doc( """ Any additional JSON schema data. @@ -369,7 +369,7 @@ def Query( # noqa: N802 ] = Undefined, *, default_factory: Annotated[ - Union[Callable[[], Any], None], + Callable[[], Any] | None, Doc( """ A callable to generate the default value. @@ -380,7 +380,7 @@ def Query( # noqa: N802 ), ] = _Unset, alias: Annotated[ - Optional[str], + str | None, Doc( """ An alternative name for the parameter field. @@ -395,7 +395,7 @@ def Query( # noqa: N802 ), ] = None, alias_priority: Annotated[ - Union[int, None], + int | None, Doc( """ Priority of the alias. This affects whether an alias generator is used. @@ -403,7 +403,7 @@ def Query( # noqa: N802 ), ] = _Unset, validation_alias: Annotated[ - Union[str, AliasPath, AliasChoices, None], + str | AliasPath | AliasChoices | None, Doc( """ 'Whitelist' validation step. The parameter field will be the single one @@ -412,7 +412,7 @@ def Query( # noqa: N802 ), ] = None, serialization_alias: Annotated[ - Union[str, None], + str | None, Doc( """ 'Blacklist' validation step. The vanilla parameter field will be the @@ -422,7 +422,7 @@ def Query( # noqa: N802 ), ] = None, title: Annotated[ - Optional[str], + str | None, Doc( """ Human-readable title. @@ -433,7 +433,7 @@ def Query( # noqa: N802 ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ Human-readable description. @@ -444,7 +444,7 @@ def Query( # noqa: N802 ), ] = None, gt: Annotated[ - Optional[float], + float | None, Doc( """ Greater than. If set, value must be greater than this. Only applicable to @@ -456,7 +456,7 @@ def Query( # noqa: N802 ), ] = None, ge: Annotated[ - Optional[float], + float | None, Doc( """ Greater than or equal. If set, value must be greater than or equal to @@ -468,7 +468,7 @@ def Query( # noqa: N802 ), ] = None, lt: Annotated[ - Optional[float], + float | None, Doc( """ Less than. If set, value must be less than this. Only applicable to numbers. @@ -479,7 +479,7 @@ def Query( # noqa: N802 ), ] = None, le: Annotated[ - Optional[float], + float | None, Doc( """ Less than or equal. If set, value must be less than or equal to this. @@ -491,7 +491,7 @@ def Query( # noqa: N802 ), ] = None, min_length: Annotated[ - Optional[int], + int | None, Doc( """ Minimum length for strings. @@ -502,7 +502,7 @@ def Query( # noqa: N802 ), ] = None, max_length: Annotated[ - Optional[int], + int | None, Doc( """ Maximum length for strings. @@ -513,7 +513,7 @@ def Query( # noqa: N802 ), ] = None, pattern: Annotated[ - Optional[str], + str | None, Doc( """ RegEx pattern for strings. @@ -524,7 +524,7 @@ def Query( # noqa: N802 ), ] = None, regex: Annotated[ - Optional[str], + str | None, Doc( """ RegEx pattern for strings. @@ -535,7 +535,7 @@ def Query( # noqa: N802 ), ] = None, discriminator: Annotated[ - Union[str, None], + str | None, Doc( """ Parameter field name for discriminating the type in a tagged union. @@ -543,7 +543,7 @@ def Query( # noqa: N802 ), ] = None, strict: Annotated[ - Union[bool, None], + bool | None, Doc( """ If `True`, strict validation is applied to the field. @@ -551,7 +551,7 @@ def Query( # noqa: N802 ), ] = _Unset, multiple_of: Annotated[ - Union[float, None], + float | None, Doc( """ Value must be a multiple of this. Only applicable to numbers. @@ -559,7 +559,7 @@ def Query( # noqa: N802 ), ] = _Unset, allow_inf_nan: Annotated[ - Union[bool, None], + bool | None, Doc( """ Allow `inf`, `-inf`, `nan`. Only applicable to numbers. @@ -567,7 +567,7 @@ def Query( # noqa: N802 ), ] = _Unset, max_digits: Annotated[ - Union[int, None], + int | None, Doc( """ Maximum number of allow digits for strings. @@ -575,7 +575,7 @@ def Query( # noqa: N802 ), ] = _Unset, decimal_places: Annotated[ - Union[int, None], + int | None, Doc( """ Maximum number of decimal places allowed for numbers. @@ -583,7 +583,7 @@ def Query( # noqa: N802 ), ] = _Unset, examples: Annotated[ - Optional[list[Any]], + list[Any] | None, Doc( """ Example values for this field. @@ -594,14 +594,14 @@ def Query( # noqa: N802 ), ] = None, example: Annotated[ - Optional[Any], + Any | None, deprecated( "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " "although still supported. Use examples instead." ), ] = _Unset, openapi_examples: Annotated[ - Optional[dict[str, Example]], + dict[str, Example] | None, Doc( """ OpenAPI-specific examples. @@ -618,7 +618,7 @@ def Query( # noqa: N802 ), ] = None, deprecated: Annotated[ - Union[deprecated, str, bool, None], + deprecated | str | bool | None, Doc( """ Mark this parameter field as deprecated. @@ -645,7 +645,7 @@ def Query( # noqa: N802 ), ] = True, json_schema_extra: Annotated[ - Union[dict[str, Any], None], + dict[str, Any] | None, Doc( """ Any additional JSON schema data. @@ -710,7 +710,7 @@ def Header( # noqa: N802 ] = Undefined, *, default_factory: Annotated[ - Union[Callable[[], Any], None], + Callable[[], Any] | None, Doc( """ A callable to generate the default value. @@ -721,7 +721,7 @@ def Header( # noqa: N802 ), ] = _Unset, alias: Annotated[ - Optional[str], + str | None, Doc( """ An alternative name for the parameter field. @@ -733,7 +733,7 @@ def Header( # noqa: N802 ), ] = None, alias_priority: Annotated[ - Union[int, None], + int | None, Doc( """ Priority of the alias. This affects whether an alias generator is used. @@ -741,7 +741,7 @@ def Header( # noqa: N802 ), ] = _Unset, validation_alias: Annotated[ - Union[str, AliasPath, AliasChoices, None], + str | AliasPath | AliasChoices | None, Doc( """ 'Whitelist' validation step. The parameter field will be the single one @@ -750,7 +750,7 @@ def Header( # noqa: N802 ), ] = None, serialization_alias: Annotated[ - Union[str, None], + str | None, Doc( """ 'Blacklist' validation step. The vanilla parameter field will be the @@ -771,7 +771,7 @@ def Header( # noqa: N802 ), ] = True, title: Annotated[ - Optional[str], + str | None, Doc( """ Human-readable title. @@ -779,7 +779,7 @@ def Header( # noqa: N802 ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ Human-readable description. @@ -787,7 +787,7 @@ def Header( # noqa: N802 ), ] = None, gt: Annotated[ - Optional[float], + float | None, Doc( """ Greater than. If set, value must be greater than this. Only applicable to @@ -796,7 +796,7 @@ def Header( # noqa: N802 ), ] = None, ge: Annotated[ - Optional[float], + float | None, Doc( """ Greater than or equal. If set, value must be greater than or equal to @@ -805,7 +805,7 @@ def Header( # noqa: N802 ), ] = None, lt: Annotated[ - Optional[float], + float | None, Doc( """ Less than. If set, value must be less than this. Only applicable to numbers. @@ -813,7 +813,7 @@ def Header( # noqa: N802 ), ] = None, le: Annotated[ - Optional[float], + float | None, Doc( """ Less than or equal. If set, value must be less than or equal to this. @@ -822,7 +822,7 @@ def Header( # noqa: N802 ), ] = None, min_length: Annotated[ - Optional[int], + int | None, Doc( """ Minimum length for strings. @@ -830,7 +830,7 @@ def Header( # noqa: N802 ), ] = None, max_length: Annotated[ - Optional[int], + int | None, Doc( """ Maximum length for strings. @@ -838,7 +838,7 @@ def Header( # noqa: N802 ), ] = None, pattern: Annotated[ - Optional[str], + str | None, Doc( """ RegEx pattern for strings. @@ -846,7 +846,7 @@ def Header( # noqa: N802 ), ] = None, regex: Annotated[ - Optional[str], + str | None, Doc( """ RegEx pattern for strings. @@ -857,7 +857,7 @@ def Header( # noqa: N802 ), ] = None, discriminator: Annotated[ - Union[str, None], + str | None, Doc( """ Parameter field name for discriminating the type in a tagged union. @@ -865,7 +865,7 @@ def Header( # noqa: N802 ), ] = None, strict: Annotated[ - Union[bool, None], + bool | None, Doc( """ If `True`, strict validation is applied to the field. @@ -873,7 +873,7 @@ def Header( # noqa: N802 ), ] = _Unset, multiple_of: Annotated[ - Union[float, None], + float | None, Doc( """ Value must be a multiple of this. Only applicable to numbers. @@ -881,7 +881,7 @@ def Header( # noqa: N802 ), ] = _Unset, allow_inf_nan: Annotated[ - Union[bool, None], + bool | None, Doc( """ Allow `inf`, `-inf`, `nan`. Only applicable to numbers. @@ -889,7 +889,7 @@ def Header( # noqa: N802 ), ] = _Unset, max_digits: Annotated[ - Union[int, None], + int | None, Doc( """ Maximum number of allow digits for strings. @@ -897,7 +897,7 @@ def Header( # noqa: N802 ), ] = _Unset, decimal_places: Annotated[ - Union[int, None], + int | None, Doc( """ Maximum number of decimal places allowed for numbers. @@ -905,7 +905,7 @@ def Header( # noqa: N802 ), ] = _Unset, examples: Annotated[ - Optional[list[Any]], + list[Any] | None, Doc( """ Example values for this field. @@ -916,14 +916,14 @@ def Header( # noqa: N802 ), ] = None, example: Annotated[ - Optional[Any], + Any | None, deprecated( "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " "although still supported. Use examples instead." ), ] = _Unset, openapi_examples: Annotated[ - Optional[dict[str, Example]], + dict[str, Example] | None, Doc( """ OpenAPI-specific examples. @@ -940,7 +940,7 @@ def Header( # noqa: N802 ), ] = None, deprecated: Annotated[ - Union[deprecated, str, bool, None], + deprecated | str | bool | None, Doc( """ Mark this parameter field as deprecated. @@ -961,7 +961,7 @@ def Header( # noqa: N802 ), ] = True, json_schema_extra: Annotated[ - Union[dict[str, Any], None], + dict[str, Any] | None, Doc( """ Any additional JSON schema data. @@ -1027,7 +1027,7 @@ def Cookie( # noqa: N802 ] = Undefined, *, default_factory: Annotated[ - Union[Callable[[], Any], None], + Callable[[], Any] | None, Doc( """ A callable to generate the default value. @@ -1038,7 +1038,7 @@ def Cookie( # noqa: N802 ), ] = _Unset, alias: Annotated[ - Optional[str], + str | None, Doc( """ An alternative name for the parameter field. @@ -1050,7 +1050,7 @@ def Cookie( # noqa: N802 ), ] = None, alias_priority: Annotated[ - Union[int, None], + int | None, Doc( """ Priority of the alias. This affects whether an alias generator is used. @@ -1058,7 +1058,7 @@ def Cookie( # noqa: N802 ), ] = _Unset, validation_alias: Annotated[ - Union[str, AliasPath, AliasChoices, None], + str | AliasPath | AliasChoices | None, Doc( """ 'Whitelist' validation step. The parameter field will be the single one @@ -1067,7 +1067,7 @@ def Cookie( # noqa: N802 ), ] = None, serialization_alias: Annotated[ - Union[str, None], + str | None, Doc( """ 'Blacklist' validation step. The vanilla parameter field will be the @@ -1077,7 +1077,7 @@ def Cookie( # noqa: N802 ), ] = None, title: Annotated[ - Optional[str], + str | None, Doc( """ Human-readable title. @@ -1085,7 +1085,7 @@ def Cookie( # noqa: N802 ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ Human-readable description. @@ -1093,7 +1093,7 @@ def Cookie( # noqa: N802 ), ] = None, gt: Annotated[ - Optional[float], + float | None, Doc( """ Greater than. If set, value must be greater than this. Only applicable to @@ -1102,7 +1102,7 @@ def Cookie( # noqa: N802 ), ] = None, ge: Annotated[ - Optional[float], + float | None, Doc( """ Greater than or equal. If set, value must be greater than or equal to @@ -1111,7 +1111,7 @@ def Cookie( # noqa: N802 ), ] = None, lt: Annotated[ - Optional[float], + float | None, Doc( """ Less than. If set, value must be less than this. Only applicable to numbers. @@ -1119,7 +1119,7 @@ def Cookie( # noqa: N802 ), ] = None, le: Annotated[ - Optional[float], + float | None, Doc( """ Less than or equal. If set, value must be less than or equal to this. @@ -1128,7 +1128,7 @@ def Cookie( # noqa: N802 ), ] = None, min_length: Annotated[ - Optional[int], + int | None, Doc( """ Minimum length for strings. @@ -1136,7 +1136,7 @@ def Cookie( # noqa: N802 ), ] = None, max_length: Annotated[ - Optional[int], + int | None, Doc( """ Maximum length for strings. @@ -1144,7 +1144,7 @@ def Cookie( # noqa: N802 ), ] = None, pattern: Annotated[ - Optional[str], + str | None, Doc( """ RegEx pattern for strings. @@ -1152,7 +1152,7 @@ def Cookie( # noqa: N802 ), ] = None, regex: Annotated[ - Optional[str], + str | None, Doc( """ RegEx pattern for strings. @@ -1163,7 +1163,7 @@ def Cookie( # noqa: N802 ), ] = None, discriminator: Annotated[ - Union[str, None], + str | None, Doc( """ Parameter field name for discriminating the type in a tagged union. @@ -1171,7 +1171,7 @@ def Cookie( # noqa: N802 ), ] = None, strict: Annotated[ - Union[bool, None], + bool | None, Doc( """ If `True`, strict validation is applied to the field. @@ -1179,7 +1179,7 @@ def Cookie( # noqa: N802 ), ] = _Unset, multiple_of: Annotated[ - Union[float, None], + float | None, Doc( """ Value must be a multiple of this. Only applicable to numbers. @@ -1187,7 +1187,7 @@ def Cookie( # noqa: N802 ), ] = _Unset, allow_inf_nan: Annotated[ - Union[bool, None], + bool | None, Doc( """ Allow `inf`, `-inf`, `nan`. Only applicable to numbers. @@ -1195,7 +1195,7 @@ def Cookie( # noqa: N802 ), ] = _Unset, max_digits: Annotated[ - Union[int, None], + int | None, Doc( """ Maximum number of allow digits for strings. @@ -1203,7 +1203,7 @@ def Cookie( # noqa: N802 ), ] = _Unset, decimal_places: Annotated[ - Union[int, None], + int | None, Doc( """ Maximum number of decimal places allowed for numbers. @@ -1211,7 +1211,7 @@ def Cookie( # noqa: N802 ), ] = _Unset, examples: Annotated[ - Optional[list[Any]], + list[Any] | None, Doc( """ Example values for this field. @@ -1222,14 +1222,14 @@ def Cookie( # noqa: N802 ), ] = None, example: Annotated[ - Optional[Any], + Any | None, deprecated( "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " "although still supported. Use examples instead." ), ] = _Unset, openapi_examples: Annotated[ - Optional[dict[str, Example]], + dict[str, Example] | None, Doc( """ OpenAPI-specific examples. @@ -1246,7 +1246,7 @@ def Cookie( # noqa: N802 ), ] = None, deprecated: Annotated[ - Union[deprecated, str, bool, None], + deprecated | str | bool | None, Doc( """ Mark this parameter field as deprecated. @@ -1267,7 +1267,7 @@ def Cookie( # noqa: N802 ), ] = True, json_schema_extra: Annotated[ - Union[dict[str, Any], None], + dict[str, Any] | None, Doc( """ Any additional JSON schema data. @@ -1332,7 +1332,7 @@ def Body( # noqa: N802 ] = Undefined, *, default_factory: Annotated[ - Union[Callable[[], Any], None], + Callable[[], Any] | None, Doc( """ A callable to generate the default value. @@ -1343,7 +1343,7 @@ def Body( # noqa: N802 ), ] = _Unset, embed: Annotated[ - Union[bool, None], + bool | None, Doc( """ When `embed` is `True`, the parameter will be expected in a JSON body as a @@ -1366,7 +1366,7 @@ def Body( # noqa: N802 ), ] = "application/json", alias: Annotated[ - Optional[str], + str | None, Doc( """ An alternative name for the parameter field. @@ -1378,7 +1378,7 @@ def Body( # noqa: N802 ), ] = None, alias_priority: Annotated[ - Union[int, None], + int | None, Doc( """ Priority of the alias. This affects whether an alias generator is used. @@ -1386,7 +1386,7 @@ def Body( # noqa: N802 ), ] = _Unset, validation_alias: Annotated[ - Union[str, AliasPath, AliasChoices, None], + str | AliasPath | AliasChoices | None, Doc( """ 'Whitelist' validation step. The parameter field will be the single one @@ -1395,7 +1395,7 @@ def Body( # noqa: N802 ), ] = None, serialization_alias: Annotated[ - Union[str, None], + str | None, Doc( """ 'Blacklist' validation step. The vanilla parameter field will be the @@ -1405,7 +1405,7 @@ def Body( # noqa: N802 ), ] = None, title: Annotated[ - Optional[str], + str | None, Doc( """ Human-readable title. @@ -1413,7 +1413,7 @@ def Body( # noqa: N802 ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ Human-readable description. @@ -1421,7 +1421,7 @@ def Body( # noqa: N802 ), ] = None, gt: Annotated[ - Optional[float], + float | None, Doc( """ Greater than. If set, value must be greater than this. Only applicable to @@ -1430,7 +1430,7 @@ def Body( # noqa: N802 ), ] = None, ge: Annotated[ - Optional[float], + float | None, Doc( """ Greater than or equal. If set, value must be greater than or equal to @@ -1439,7 +1439,7 @@ def Body( # noqa: N802 ), ] = None, lt: Annotated[ - Optional[float], + float | None, Doc( """ Less than. If set, value must be less than this. Only applicable to numbers. @@ -1447,7 +1447,7 @@ def Body( # noqa: N802 ), ] = None, le: Annotated[ - Optional[float], + float | None, Doc( """ Less than or equal. If set, value must be less than or equal to this. @@ -1456,7 +1456,7 @@ def Body( # noqa: N802 ), ] = None, min_length: Annotated[ - Optional[int], + int | None, Doc( """ Minimum length for strings. @@ -1464,7 +1464,7 @@ def Body( # noqa: N802 ), ] = None, max_length: Annotated[ - Optional[int], + int | None, Doc( """ Maximum length for strings. @@ -1472,7 +1472,7 @@ def Body( # noqa: N802 ), ] = None, pattern: Annotated[ - Optional[str], + str | None, Doc( """ RegEx pattern for strings. @@ -1480,7 +1480,7 @@ def Body( # noqa: N802 ), ] = None, regex: Annotated[ - Optional[str], + str | None, Doc( """ RegEx pattern for strings. @@ -1491,7 +1491,7 @@ def Body( # noqa: N802 ), ] = None, discriminator: Annotated[ - Union[str, None], + str | None, Doc( """ Parameter field name for discriminating the type in a tagged union. @@ -1499,7 +1499,7 @@ def Body( # noqa: N802 ), ] = None, strict: Annotated[ - Union[bool, None], + bool | None, Doc( """ If `True`, strict validation is applied to the field. @@ -1507,7 +1507,7 @@ def Body( # noqa: N802 ), ] = _Unset, multiple_of: Annotated[ - Union[float, None], + float | None, Doc( """ Value must be a multiple of this. Only applicable to numbers. @@ -1515,7 +1515,7 @@ def Body( # noqa: N802 ), ] = _Unset, allow_inf_nan: Annotated[ - Union[bool, None], + bool | None, Doc( """ Allow `inf`, `-inf`, `nan`. Only applicable to numbers. @@ -1523,7 +1523,7 @@ def Body( # noqa: N802 ), ] = _Unset, max_digits: Annotated[ - Union[int, None], + int | None, Doc( """ Maximum number of allow digits for strings. @@ -1531,7 +1531,7 @@ def Body( # noqa: N802 ), ] = _Unset, decimal_places: Annotated[ - Union[int, None], + int | None, Doc( """ Maximum number of decimal places allowed for numbers. @@ -1539,7 +1539,7 @@ def Body( # noqa: N802 ), ] = _Unset, examples: Annotated[ - Optional[list[Any]], + list[Any] | None, Doc( """ Example values for this field. @@ -1550,14 +1550,14 @@ def Body( # noqa: N802 ), ] = None, example: Annotated[ - Optional[Any], + Any | None, deprecated( "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " "although still supported. Use examples instead." ), ] = _Unset, openapi_examples: Annotated[ - Optional[dict[str, Example]], + dict[str, Example] | None, Doc( """ OpenAPI-specific examples. @@ -1574,7 +1574,7 @@ def Body( # noqa: N802 ), ] = None, deprecated: Annotated[ - Union[deprecated, str, bool, None], + deprecated | str | bool | None, Doc( """ Mark this parameter field as deprecated. @@ -1595,7 +1595,7 @@ def Body( # noqa: N802 ), ] = True, json_schema_extra: Annotated[ - Union[dict[str, Any], None], + dict[str, Any] | None, Doc( """ Any additional JSON schema data. @@ -1662,7 +1662,7 @@ def Form( # noqa: N802 ] = Undefined, *, default_factory: Annotated[ - Union[Callable[[], Any], None], + Callable[[], Any] | None, Doc( """ A callable to generate the default value. @@ -1682,7 +1682,7 @@ def Form( # noqa: N802 ), ] = "application/x-www-form-urlencoded", alias: Annotated[ - Optional[str], + str | None, Doc( """ An alternative name for the parameter field. @@ -1694,7 +1694,7 @@ def Form( # noqa: N802 ), ] = None, alias_priority: Annotated[ - Union[int, None], + int | None, Doc( """ Priority of the alias. This affects whether an alias generator is used. @@ -1702,7 +1702,7 @@ def Form( # noqa: N802 ), ] = _Unset, validation_alias: Annotated[ - Union[str, AliasPath, AliasChoices, None], + str | AliasPath | AliasChoices | None, Doc( """ 'Whitelist' validation step. The parameter field will be the single one @@ -1711,7 +1711,7 @@ def Form( # noqa: N802 ), ] = None, serialization_alias: Annotated[ - Union[str, None], + str | None, Doc( """ 'Blacklist' validation step. The vanilla parameter field will be the @@ -1721,7 +1721,7 @@ def Form( # noqa: N802 ), ] = None, title: Annotated[ - Optional[str], + str | None, Doc( """ Human-readable title. @@ -1729,7 +1729,7 @@ def Form( # noqa: N802 ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ Human-readable description. @@ -1737,7 +1737,7 @@ def Form( # noqa: N802 ), ] = None, gt: Annotated[ - Optional[float], + float | None, Doc( """ Greater than. If set, value must be greater than this. Only applicable to @@ -1746,7 +1746,7 @@ def Form( # noqa: N802 ), ] = None, ge: Annotated[ - Optional[float], + float | None, Doc( """ Greater than or equal. If set, value must be greater than or equal to @@ -1755,7 +1755,7 @@ def Form( # noqa: N802 ), ] = None, lt: Annotated[ - Optional[float], + float | None, Doc( """ Less than. If set, value must be less than this. Only applicable to numbers. @@ -1763,7 +1763,7 @@ def Form( # noqa: N802 ), ] = None, le: Annotated[ - Optional[float], + float | None, Doc( """ Less than or equal. If set, value must be less than or equal to this. @@ -1772,7 +1772,7 @@ def Form( # noqa: N802 ), ] = None, min_length: Annotated[ - Optional[int], + int | None, Doc( """ Minimum length for strings. @@ -1780,7 +1780,7 @@ def Form( # noqa: N802 ), ] = None, max_length: Annotated[ - Optional[int], + int | None, Doc( """ Maximum length for strings. @@ -1788,7 +1788,7 @@ def Form( # noqa: N802 ), ] = None, pattern: Annotated[ - Optional[str], + str | None, Doc( """ RegEx pattern for strings. @@ -1796,7 +1796,7 @@ def Form( # noqa: N802 ), ] = None, regex: Annotated[ - Optional[str], + str | None, Doc( """ RegEx pattern for strings. @@ -1807,7 +1807,7 @@ def Form( # noqa: N802 ), ] = None, discriminator: Annotated[ - Union[str, None], + str | None, Doc( """ Parameter field name for discriminating the type in a tagged union. @@ -1815,7 +1815,7 @@ def Form( # noqa: N802 ), ] = None, strict: Annotated[ - Union[bool, None], + bool | None, Doc( """ If `True`, strict validation is applied to the field. @@ -1823,7 +1823,7 @@ def Form( # noqa: N802 ), ] = _Unset, multiple_of: Annotated[ - Union[float, None], + float | None, Doc( """ Value must be a multiple of this. Only applicable to numbers. @@ -1831,7 +1831,7 @@ def Form( # noqa: N802 ), ] = _Unset, allow_inf_nan: Annotated[ - Union[bool, None], + bool | None, Doc( """ Allow `inf`, `-inf`, `nan`. Only applicable to numbers. @@ -1839,7 +1839,7 @@ def Form( # noqa: N802 ), ] = _Unset, max_digits: Annotated[ - Union[int, None], + int | None, Doc( """ Maximum number of allow digits for strings. @@ -1847,7 +1847,7 @@ def Form( # noqa: N802 ), ] = _Unset, decimal_places: Annotated[ - Union[int, None], + int | None, Doc( """ Maximum number of decimal places allowed for numbers. @@ -1855,7 +1855,7 @@ def Form( # noqa: N802 ), ] = _Unset, examples: Annotated[ - Optional[list[Any]], + list[Any] | None, Doc( """ Example values for this field. @@ -1866,14 +1866,14 @@ def Form( # noqa: N802 ), ] = None, example: Annotated[ - Optional[Any], + Any | None, deprecated( "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " "although still supported. Use examples instead." ), ] = _Unset, openapi_examples: Annotated[ - Optional[dict[str, Example]], + dict[str, Example] | None, Doc( """ OpenAPI-specific examples. @@ -1890,7 +1890,7 @@ def Form( # noqa: N802 ), ] = None, deprecated: Annotated[ - Union[deprecated, str, bool, None], + deprecated | str | bool | None, Doc( """ Mark this parameter field as deprecated. @@ -1911,7 +1911,7 @@ def Form( # noqa: N802 ), ] = True, json_schema_extra: Annotated[ - Union[dict[str, Any], None], + dict[str, Any] | None, Doc( """ Any additional JSON schema data. @@ -1977,7 +1977,7 @@ def File( # noqa: N802 ] = Undefined, *, default_factory: Annotated[ - Union[Callable[[], Any], None], + Callable[[], Any] | None, Doc( """ A callable to generate the default value. @@ -1997,7 +1997,7 @@ def File( # noqa: N802 ), ] = "multipart/form-data", alias: Annotated[ - Optional[str], + str | None, Doc( """ An alternative name for the parameter field. @@ -2009,7 +2009,7 @@ def File( # noqa: N802 ), ] = None, alias_priority: Annotated[ - Union[int, None], + int | None, Doc( """ Priority of the alias. This affects whether an alias generator is used. @@ -2017,7 +2017,7 @@ def File( # noqa: N802 ), ] = _Unset, validation_alias: Annotated[ - Union[str, AliasPath, AliasChoices, None], + str | AliasPath | AliasChoices | None, Doc( """ 'Whitelist' validation step. The parameter field will be the single one @@ -2026,7 +2026,7 @@ def File( # noqa: N802 ), ] = None, serialization_alias: Annotated[ - Union[str, None], + str | None, Doc( """ 'Blacklist' validation step. The vanilla parameter field will be the @@ -2036,7 +2036,7 @@ def File( # noqa: N802 ), ] = None, title: Annotated[ - Optional[str], + str | None, Doc( """ Human-readable title. @@ -2044,7 +2044,7 @@ def File( # noqa: N802 ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ Human-readable description. @@ -2052,7 +2052,7 @@ def File( # noqa: N802 ), ] = None, gt: Annotated[ - Optional[float], + float | None, Doc( """ Greater than. If set, value must be greater than this. Only applicable to @@ -2061,7 +2061,7 @@ def File( # noqa: N802 ), ] = None, ge: Annotated[ - Optional[float], + float | None, Doc( """ Greater than or equal. If set, value must be greater than or equal to @@ -2070,7 +2070,7 @@ def File( # noqa: N802 ), ] = None, lt: Annotated[ - Optional[float], + float | None, Doc( """ Less than. If set, value must be less than this. Only applicable to numbers. @@ -2078,7 +2078,7 @@ def File( # noqa: N802 ), ] = None, le: Annotated[ - Optional[float], + float | None, Doc( """ Less than or equal. If set, value must be less than or equal to this. @@ -2087,7 +2087,7 @@ def File( # noqa: N802 ), ] = None, min_length: Annotated[ - Optional[int], + int | None, Doc( """ Minimum length for strings. @@ -2095,7 +2095,7 @@ def File( # noqa: N802 ), ] = None, max_length: Annotated[ - Optional[int], + int | None, Doc( """ Maximum length for strings. @@ -2103,7 +2103,7 @@ def File( # noqa: N802 ), ] = None, pattern: Annotated[ - Optional[str], + str | None, Doc( """ RegEx pattern for strings. @@ -2111,7 +2111,7 @@ def File( # noqa: N802 ), ] = None, regex: Annotated[ - Optional[str], + str | None, Doc( """ RegEx pattern for strings. @@ -2122,7 +2122,7 @@ def File( # noqa: N802 ), ] = None, discriminator: Annotated[ - Union[str, None], + str | None, Doc( """ Parameter field name for discriminating the type in a tagged union. @@ -2130,7 +2130,7 @@ def File( # noqa: N802 ), ] = None, strict: Annotated[ - Union[bool, None], + bool | None, Doc( """ If `True`, strict validation is applied to the field. @@ -2138,7 +2138,7 @@ def File( # noqa: N802 ), ] = _Unset, multiple_of: Annotated[ - Union[float, None], + float | None, Doc( """ Value must be a multiple of this. Only applicable to numbers. @@ -2146,7 +2146,7 @@ def File( # noqa: N802 ), ] = _Unset, allow_inf_nan: Annotated[ - Union[bool, None], + bool | None, Doc( """ Allow `inf`, `-inf`, `nan`. Only applicable to numbers. @@ -2154,7 +2154,7 @@ def File( # noqa: N802 ), ] = _Unset, max_digits: Annotated[ - Union[int, None], + int | None, Doc( """ Maximum number of allow digits for strings. @@ -2162,7 +2162,7 @@ def File( # noqa: N802 ), ] = _Unset, decimal_places: Annotated[ - Union[int, None], + int | None, Doc( """ Maximum number of decimal places allowed for numbers. @@ -2170,7 +2170,7 @@ def File( # noqa: N802 ), ] = _Unset, examples: Annotated[ - Optional[list[Any]], + list[Any] | None, Doc( """ Example values for this field. @@ -2181,14 +2181,14 @@ def File( # noqa: N802 ), ] = None, example: Annotated[ - Optional[Any], + Any | None, deprecated( "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " "although still supported. Use examples instead." ), ] = _Unset, openapi_examples: Annotated[ - Optional[dict[str, Example]], + dict[str, Example] | None, Doc( """ OpenAPI-specific examples. @@ -2205,7 +2205,7 @@ def File( # noqa: N802 ), ] = None, deprecated: Annotated[ - Union[deprecated, str, bool, None], + deprecated | str | bool | None, Doc( """ Mark this parameter field as deprecated. @@ -2226,7 +2226,7 @@ def File( # noqa: N802 ), ] = True, json_schema_extra: Annotated[ - Union[dict[str, Any], None], + dict[str, Any] | None, Doc( """ Any additional JSON schema data. @@ -2283,7 +2283,7 @@ def File( # noqa: N802 def Depends( # noqa: N802 dependency: Annotated[ - Optional[Callable[..., Any]], + Callable[..., Any] | None, Doc( """ A "dependable" callable (like a function). @@ -2315,7 +2315,7 @@ def Depends( # noqa: N802 ), ] = True, scope: Annotated[ - Union[Literal["function", "request"], None], + Literal["function", "request"] | None, Doc( """ Mainly for dependencies with `yield`, define when the dependency function @@ -2372,7 +2372,7 @@ def Depends( # noqa: N802 def Security( # noqa: N802 dependency: Annotated[ - Optional[Callable[..., Any]], + Callable[..., Any] | None, Doc( """ A "dependable" callable (like a function). @@ -2387,7 +2387,7 @@ def Security( # noqa: N802 ] = None, *, scopes: Annotated[ - Optional[Sequence[str]], + Sequence[str] | None, Doc( """ OAuth2 scopes required for the *path operation* that uses this Security diff --git a/fastapi/params.py b/fastapi/params.py index 72e797f83..68f987081 100644 --- a/fastapi/params.py +++ b/fastapi/params.py @@ -1,14 +1,14 @@ import warnings -from collections.abc import Sequence +from collections.abc import Callable, Sequence from dataclasses import dataclass from enum import Enum -from typing import Annotated, Any, Callable, Optional, Union +from typing import Annotated, Any, Literal from fastapi.exceptions import FastAPIDeprecationWarning from fastapi.openapi.models import Example from pydantic import AliasChoices, AliasPath from pydantic.fields import FieldInfo -from typing_extensions import Literal, deprecated +from typing_extensions import deprecated from ._compat import ( Undefined, @@ -31,45 +31,45 @@ class Param(FieldInfo): # type: ignore[misc] self, default: Any = Undefined, *, - default_factory: Union[Callable[[], Any], None] = _Unset, - annotation: Optional[Any] = None, - alias: Optional[str] = None, - alias_priority: Union[int, None] = _Unset, - validation_alias: Union[str, AliasPath, AliasChoices, None] = None, - serialization_alias: Union[str, None] = None, - title: Optional[str] = None, - description: Optional[str] = None, - gt: Optional[float] = None, - ge: Optional[float] = None, - lt: Optional[float] = None, - le: Optional[float] = None, - min_length: Optional[int] = None, - max_length: Optional[int] = None, - pattern: Optional[str] = None, + default_factory: Callable[[], Any] | None = _Unset, + annotation: Any | None = None, + alias: str | None = None, + alias_priority: int | None = _Unset, + validation_alias: str | AliasPath | AliasChoices | None = None, + serialization_alias: str | None = None, + title: str | None = None, + description: str | None = None, + gt: float | None = None, + ge: float | None = None, + lt: float | None = None, + le: float | None = None, + min_length: int | None = None, + max_length: int | None = None, + pattern: str | None = None, regex: Annotated[ - Optional[str], + str | None, deprecated( "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." ), ] = None, - discriminator: Union[str, None] = None, - strict: Union[bool, None] = _Unset, - multiple_of: Union[float, None] = _Unset, - allow_inf_nan: Union[bool, None] = _Unset, - max_digits: Union[int, None] = _Unset, - decimal_places: Union[int, None] = _Unset, - examples: Optional[list[Any]] = None, + discriminator: str | None = None, + strict: bool | None = _Unset, + multiple_of: float | None = _Unset, + allow_inf_nan: bool | None = _Unset, + max_digits: int | None = _Unset, + decimal_places: int | None = _Unset, + examples: list[Any] | None = None, example: Annotated[ - Optional[Any], + Any | None, deprecated( "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " "although still supported. Use examples instead." ), ] = _Unset, - openapi_examples: Optional[dict[str, Example]] = None, - deprecated: Union[deprecated, str, bool, None] = None, + openapi_examples: dict[str, Example] | None = None, + deprecated: deprecated | str | bool | None = None, include_in_schema: bool = True, - json_schema_extra: Union[dict[str, Any], None] = None, + json_schema_extra: dict[str, Any] | None = None, **extra: Any, ): if example is not _Unset: @@ -142,45 +142,45 @@ class Path(Param): # type: ignore[misc] self, default: Any = ..., *, - default_factory: Union[Callable[[], Any], None] = _Unset, - annotation: Optional[Any] = None, - alias: Optional[str] = None, - alias_priority: Union[int, None] = _Unset, - validation_alias: Union[str, AliasPath, AliasChoices, None] = None, - serialization_alias: Union[str, None] = None, - title: Optional[str] = None, - description: Optional[str] = None, - gt: Optional[float] = None, - ge: Optional[float] = None, - lt: Optional[float] = None, - le: Optional[float] = None, - min_length: Optional[int] = None, - max_length: Optional[int] = None, - pattern: Optional[str] = None, + default_factory: Callable[[], Any] | None = _Unset, + annotation: Any | None = None, + alias: str | None = None, + alias_priority: int | None = _Unset, + validation_alias: str | AliasPath | AliasChoices | None = None, + serialization_alias: str | None = None, + title: str | None = None, + description: str | None = None, + gt: float | None = None, + ge: float | None = None, + lt: float | None = None, + le: float | None = None, + min_length: int | None = None, + max_length: int | None = None, + pattern: str | None = None, regex: Annotated[ - Optional[str], + str | None, deprecated( "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." ), ] = None, - discriminator: Union[str, None] = None, - strict: Union[bool, None] = _Unset, - multiple_of: Union[float, None] = _Unset, - allow_inf_nan: Union[bool, None] = _Unset, - max_digits: Union[int, None] = _Unset, - decimal_places: Union[int, None] = _Unset, - examples: Optional[list[Any]] = None, + discriminator: str | None = None, + strict: bool | None = _Unset, + multiple_of: float | None = _Unset, + allow_inf_nan: bool | None = _Unset, + max_digits: int | None = _Unset, + decimal_places: int | None = _Unset, + examples: list[Any] | None = None, example: Annotated[ - Optional[Any], + Any | None, deprecated( "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " "although still supported. Use examples instead." ), ] = _Unset, - openapi_examples: Optional[dict[str, Example]] = None, - deprecated: Union[deprecated, str, bool, None] = None, + openapi_examples: dict[str, Example] | None = None, + deprecated: deprecated | str | bool | None = None, include_in_schema: bool = True, - json_schema_extra: Union[dict[str, Any], None] = None, + json_schema_extra: dict[str, Any] | None = None, **extra: Any, ): assert default is ..., "Path parameters cannot have a default value" @@ -226,45 +226,45 @@ class Query(Param): # type: ignore[misc] self, default: Any = Undefined, *, - default_factory: Union[Callable[[], Any], None] = _Unset, - annotation: Optional[Any] = None, - alias: Optional[str] = None, - alias_priority: Union[int, None] = _Unset, - validation_alias: Union[str, AliasPath, AliasChoices, None] = None, - serialization_alias: Union[str, None] = None, - title: Optional[str] = None, - description: Optional[str] = None, - gt: Optional[float] = None, - ge: Optional[float] = None, - lt: Optional[float] = None, - le: Optional[float] = None, - min_length: Optional[int] = None, - max_length: Optional[int] = None, - pattern: Optional[str] = None, + default_factory: Callable[[], Any] | None = _Unset, + annotation: Any | None = None, + alias: str | None = None, + alias_priority: int | None = _Unset, + validation_alias: str | AliasPath | AliasChoices | None = None, + serialization_alias: str | None = None, + title: str | None = None, + description: str | None = None, + gt: float | None = None, + ge: float | None = None, + lt: float | None = None, + le: float | None = None, + min_length: int | None = None, + max_length: int | None = None, + pattern: str | None = None, regex: Annotated[ - Optional[str], + str | None, deprecated( "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." ), ] = None, - discriminator: Union[str, None] = None, - strict: Union[bool, None] = _Unset, - multiple_of: Union[float, None] = _Unset, - allow_inf_nan: Union[bool, None] = _Unset, - max_digits: Union[int, None] = _Unset, - decimal_places: Union[int, None] = _Unset, - examples: Optional[list[Any]] = None, + discriminator: str | None = None, + strict: bool | None = _Unset, + multiple_of: float | None = _Unset, + allow_inf_nan: bool | None = _Unset, + max_digits: int | None = _Unset, + decimal_places: int | None = _Unset, + examples: list[Any] | None = None, example: Annotated[ - Optional[Any], + Any | None, deprecated( "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " "although still supported. Use examples instead." ), ] = _Unset, - openapi_examples: Optional[dict[str, Example]] = None, - deprecated: Union[deprecated, str, bool, None] = None, + openapi_examples: dict[str, Example] | None = None, + deprecated: deprecated | str | bool | None = None, include_in_schema: bool = True, - json_schema_extra: Union[dict[str, Any], None] = None, + json_schema_extra: dict[str, Any] | None = None, **extra: Any, ): super().__init__( @@ -308,46 +308,46 @@ class Header(Param): # type: ignore[misc] self, default: Any = Undefined, *, - default_factory: Union[Callable[[], Any], None] = _Unset, - annotation: Optional[Any] = None, - alias: Optional[str] = None, - alias_priority: Union[int, None] = _Unset, - validation_alias: Union[str, AliasPath, AliasChoices, None] = None, - serialization_alias: Union[str, None] = None, + default_factory: Callable[[], Any] | None = _Unset, + annotation: Any | None = None, + alias: str | None = None, + alias_priority: int | None = _Unset, + validation_alias: str | AliasPath | AliasChoices | None = None, + serialization_alias: str | None = None, convert_underscores: bool = True, - title: Optional[str] = None, - description: Optional[str] = None, - gt: Optional[float] = None, - ge: Optional[float] = None, - lt: Optional[float] = None, - le: Optional[float] = None, - min_length: Optional[int] = None, - max_length: Optional[int] = None, - pattern: Optional[str] = None, + title: str | None = None, + description: str | None = None, + gt: float | None = None, + ge: float | None = None, + lt: float | None = None, + le: float | None = None, + min_length: int | None = None, + max_length: int | None = None, + pattern: str | None = None, regex: Annotated[ - Optional[str], + str | None, deprecated( "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." ), ] = None, - discriminator: Union[str, None] = None, - strict: Union[bool, None] = _Unset, - multiple_of: Union[float, None] = _Unset, - allow_inf_nan: Union[bool, None] = _Unset, - max_digits: Union[int, None] = _Unset, - decimal_places: Union[int, None] = _Unset, - examples: Optional[list[Any]] = None, + discriminator: str | None = None, + strict: bool | None = _Unset, + multiple_of: float | None = _Unset, + allow_inf_nan: bool | None = _Unset, + max_digits: int | None = _Unset, + decimal_places: int | None = _Unset, + examples: list[Any] | None = None, example: Annotated[ - Optional[Any], + Any | None, deprecated( "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " "although still supported. Use examples instead." ), ] = _Unset, - openapi_examples: Optional[dict[str, Example]] = None, - deprecated: Union[deprecated, str, bool, None] = None, + openapi_examples: dict[str, Example] | None = None, + deprecated: deprecated | str | bool | None = None, include_in_schema: bool = True, - json_schema_extra: Union[dict[str, Any], None] = None, + json_schema_extra: dict[str, Any] | None = None, **extra: Any, ): self.convert_underscores = convert_underscores @@ -392,45 +392,45 @@ class Cookie(Param): # type: ignore[misc] self, default: Any = Undefined, *, - default_factory: Union[Callable[[], Any], None] = _Unset, - annotation: Optional[Any] = None, - alias: Optional[str] = None, - alias_priority: Union[int, None] = _Unset, - validation_alias: Union[str, AliasPath, AliasChoices, None] = None, - serialization_alias: Union[str, None] = None, - title: Optional[str] = None, - description: Optional[str] = None, - gt: Optional[float] = None, - ge: Optional[float] = None, - lt: Optional[float] = None, - le: Optional[float] = None, - min_length: Optional[int] = None, - max_length: Optional[int] = None, - pattern: Optional[str] = None, + default_factory: Callable[[], Any] | None = _Unset, + annotation: Any | None = None, + alias: str | None = None, + alias_priority: int | None = _Unset, + validation_alias: str | AliasPath | AliasChoices | None = None, + serialization_alias: str | None = None, + title: str | None = None, + description: str | None = None, + gt: float | None = None, + ge: float | None = None, + lt: float | None = None, + le: float | None = None, + min_length: int | None = None, + max_length: int | None = None, + pattern: str | None = None, regex: Annotated[ - Optional[str], + str | None, deprecated( "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." ), ] = None, - discriminator: Union[str, None] = None, - strict: Union[bool, None] = _Unset, - multiple_of: Union[float, None] = _Unset, - allow_inf_nan: Union[bool, None] = _Unset, - max_digits: Union[int, None] = _Unset, - decimal_places: Union[int, None] = _Unset, - examples: Optional[list[Any]] = None, + discriminator: str | None = None, + strict: bool | None = _Unset, + multiple_of: float | None = _Unset, + allow_inf_nan: bool | None = _Unset, + max_digits: int | None = _Unset, + decimal_places: int | None = _Unset, + examples: list[Any] | None = None, example: Annotated[ - Optional[Any], + Any | None, deprecated( "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " "although still supported. Use examples instead." ), ] = _Unset, - openapi_examples: Optional[dict[str, Example]] = None, - deprecated: Union[deprecated, str, bool, None] = None, + openapi_examples: dict[str, Example] | None = None, + deprecated: deprecated | str | bool | None = None, include_in_schema: bool = True, - json_schema_extra: Union[dict[str, Any], None] = None, + json_schema_extra: dict[str, Any] | None = None, **extra: Any, ): super().__init__( @@ -472,47 +472,47 @@ class Body(FieldInfo): # type: ignore[misc] self, default: Any = Undefined, *, - default_factory: Union[Callable[[], Any], None] = _Unset, - annotation: Optional[Any] = None, - embed: Union[bool, None] = None, + default_factory: Callable[[], Any] | None = _Unset, + annotation: Any | None = None, + embed: bool | None = None, media_type: str = "application/json", - alias: Optional[str] = None, - alias_priority: Union[int, None] = _Unset, - validation_alias: Union[str, AliasPath, AliasChoices, None] = None, - serialization_alias: Union[str, None] = None, - title: Optional[str] = None, - description: Optional[str] = None, - gt: Optional[float] = None, - ge: Optional[float] = None, - lt: Optional[float] = None, - le: Optional[float] = None, - min_length: Optional[int] = None, - max_length: Optional[int] = None, - pattern: Optional[str] = None, + alias: str | None = None, + alias_priority: int | None = _Unset, + validation_alias: str | AliasPath | AliasChoices | None = None, + serialization_alias: str | None = None, + title: str | None = None, + description: str | None = None, + gt: float | None = None, + ge: float | None = None, + lt: float | None = None, + le: float | None = None, + min_length: int | None = None, + max_length: int | None = None, + pattern: str | None = None, regex: Annotated[ - Optional[str], + str | None, deprecated( "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." ), ] = None, - discriminator: Union[str, None] = None, - strict: Union[bool, None] = _Unset, - multiple_of: Union[float, None] = _Unset, - allow_inf_nan: Union[bool, None] = _Unset, - max_digits: Union[int, None] = _Unset, - decimal_places: Union[int, None] = _Unset, - examples: Optional[list[Any]] = None, + discriminator: str | None = None, + strict: bool | None = _Unset, + multiple_of: float | None = _Unset, + allow_inf_nan: bool | None = _Unset, + max_digits: int | None = _Unset, + decimal_places: int | None = _Unset, + examples: list[Any] | None = None, example: Annotated[ - Optional[Any], + Any | None, deprecated( "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " "although still supported. Use examples instead." ), ] = _Unset, - openapi_examples: Optional[dict[str, Example]] = None, - deprecated: Union[deprecated, str, bool, None] = None, + openapi_examples: dict[str, Example] | None = None, + deprecated: deprecated | str | bool | None = None, include_in_schema: bool = True, - json_schema_extra: Union[dict[str, Any], None] = None, + json_schema_extra: dict[str, Any] | None = None, **extra: Any, ): self.embed = embed @@ -584,46 +584,46 @@ class Form(Body): # type: ignore[misc] self, default: Any = Undefined, *, - default_factory: Union[Callable[[], Any], None] = _Unset, - annotation: Optional[Any] = None, + default_factory: Callable[[], Any] | None = _Unset, + annotation: Any | None = None, media_type: str = "application/x-www-form-urlencoded", - alias: Optional[str] = None, - alias_priority: Union[int, None] = _Unset, - validation_alias: Union[str, AliasPath, AliasChoices, None] = None, - serialization_alias: Union[str, None] = None, - title: Optional[str] = None, - description: Optional[str] = None, - gt: Optional[float] = None, - ge: Optional[float] = None, - lt: Optional[float] = None, - le: Optional[float] = None, - min_length: Optional[int] = None, - max_length: Optional[int] = None, - pattern: Optional[str] = None, + alias: str | None = None, + alias_priority: int | None = _Unset, + validation_alias: str | AliasPath | AliasChoices | None = None, + serialization_alias: str | None = None, + title: str | None = None, + description: str | None = None, + gt: float | None = None, + ge: float | None = None, + lt: float | None = None, + le: float | None = None, + min_length: int | None = None, + max_length: int | None = None, + pattern: str | None = None, regex: Annotated[ - Optional[str], + str | None, deprecated( "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." ), ] = None, - discriminator: Union[str, None] = None, - strict: Union[bool, None] = _Unset, - multiple_of: Union[float, None] = _Unset, - allow_inf_nan: Union[bool, None] = _Unset, - max_digits: Union[int, None] = _Unset, - decimal_places: Union[int, None] = _Unset, - examples: Optional[list[Any]] = None, + discriminator: str | None = None, + strict: bool | None = _Unset, + multiple_of: float | None = _Unset, + allow_inf_nan: bool | None = _Unset, + max_digits: int | None = _Unset, + decimal_places: int | None = _Unset, + examples: list[Any] | None = None, example: Annotated[ - Optional[Any], + Any | None, deprecated( "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " "although still supported. Use examples instead." ), ] = _Unset, - openapi_examples: Optional[dict[str, Example]] = None, - deprecated: Union[deprecated, str, bool, None] = None, + openapi_examples: dict[str, Example] | None = None, + deprecated: deprecated | str | bool | None = None, include_in_schema: bool = True, - json_schema_extra: Union[dict[str, Any], None] = None, + json_schema_extra: dict[str, Any] | None = None, **extra: Any, ): super().__init__( @@ -666,46 +666,46 @@ class File(Form): # type: ignore[misc] self, default: Any = Undefined, *, - default_factory: Union[Callable[[], Any], None] = _Unset, - annotation: Optional[Any] = None, + default_factory: Callable[[], Any] | None = _Unset, + annotation: Any | None = None, media_type: str = "multipart/form-data", - alias: Optional[str] = None, - alias_priority: Union[int, None] = _Unset, - validation_alias: Union[str, AliasPath, AliasChoices, None] = None, - serialization_alias: Union[str, None] = None, - title: Optional[str] = None, - description: Optional[str] = None, - gt: Optional[float] = None, - ge: Optional[float] = None, - lt: Optional[float] = None, - le: Optional[float] = None, - min_length: Optional[int] = None, - max_length: Optional[int] = None, - pattern: Optional[str] = None, + alias: str | None = None, + alias_priority: int | None = _Unset, + validation_alias: str | AliasPath | AliasChoices | None = None, + serialization_alias: str | None = None, + title: str | None = None, + description: str | None = None, + gt: float | None = None, + ge: float | None = None, + lt: float | None = None, + le: float | None = None, + min_length: int | None = None, + max_length: int | None = None, + pattern: str | None = None, regex: Annotated[ - Optional[str], + str | None, deprecated( "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." ), ] = None, - discriminator: Union[str, None] = None, - strict: Union[bool, None] = _Unset, - multiple_of: Union[float, None] = _Unset, - allow_inf_nan: Union[bool, None] = _Unset, - max_digits: Union[int, None] = _Unset, - decimal_places: Union[int, None] = _Unset, - examples: Optional[list[Any]] = None, + discriminator: str | None = None, + strict: bool | None = _Unset, + multiple_of: float | None = _Unset, + allow_inf_nan: bool | None = _Unset, + max_digits: int | None = _Unset, + decimal_places: int | None = _Unset, + examples: list[Any] | None = None, example: Annotated[ - Optional[Any], + Any | None, deprecated( "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " "although still supported. Use examples instead." ), ] = _Unset, - openapi_examples: Optional[dict[str, Example]] = None, - deprecated: Union[deprecated, str, bool, None] = None, + openapi_examples: dict[str, Example] | None = None, + deprecated: deprecated | str | bool | None = None, include_in_schema: bool = True, - json_schema_extra: Union[dict[str, Any], None] = None, + json_schema_extra: dict[str, Any] | None = None, **extra: Any, ): super().__init__( @@ -745,11 +745,11 @@ class File(Form): # type: ignore[misc] @dataclass(frozen=True) class Depends: - dependency: Optional[Callable[..., Any]] = None + dependency: Callable[..., Any] | None = None use_cache: bool = True - scope: Union[Literal["function", "request"], None] = None + scope: Literal["function", "request"] | None = None @dataclass(frozen=True) class Security(Depends): - scopes: Optional[Sequence[str]] = None + scopes: Sequence[str] | None = None diff --git a/fastapi/routing.py b/fastapi/routing.py index ed873fda8..ea82ab14a 100644 --- a/fastapi/routing.py +++ b/fastapi/routing.py @@ -7,6 +7,7 @@ import types from collections.abc import ( AsyncIterator, Awaitable, + Callable, Collection, Coroutine, Generator, @@ -23,10 +24,7 @@ from enum import Enum, IntEnum from typing import ( Annotated, Any, - Callable, - Optional, TypeVar, - Union, ) from annotated_doc import Doc @@ -84,7 +82,7 @@ from typing_extensions import deprecated # Copy of starlette.routing.request_response modified to include the # dependencies' AsyncExitStack def request_response( - func: Callable[[Request], Union[Awaitable[Response], Response]], + func: Callable[[Request], Awaitable[Response] | Response], ) -> ASGIApp: """ Takes a function or coroutine `func(request) -> response`, @@ -168,10 +166,10 @@ class _AsyncLiftContextManager(AbstractAsyncContextManager[_T]): async def __aexit__( self, - exc_type: Optional[type[BaseException]], - exc_value: Optional[BaseException], - traceback: Optional[types.TracebackType], - ) -> Optional[bool]: + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: types.TracebackType | None, + ) -> bool | None: return self._cm.__exit__(exc_type, exc_value, traceback) @@ -199,7 +197,7 @@ def _merge_lifespan_context( @asynccontextmanager async def merged_lifespan( app: AppType, - ) -> AsyncIterator[Optional[Mapping[str, Any]]]: + ) -> AsyncIterator[Mapping[str, Any] | None]: async with original_context(app) as maybe_original_state: async with nested_context(app) as maybe_nested_state: if maybe_nested_state is None and maybe_original_state is None: @@ -263,16 +261,16 @@ def _extract_endpoint_context(func: Any) -> EndpointContext: async def serialize_response( *, - field: Optional[ModelField] = None, + field: ModelField | None = None, response_content: Any, - include: Optional[IncEx] = None, - exclude: Optional[IncEx] = None, + include: IncEx | None = None, + exclude: IncEx | None = None, by_alias: bool = True, exclude_unset: bool = False, exclude_defaults: bool = False, exclude_none: bool = False, is_coroutine: bool = True, - endpoint_ctx: Optional[EndpointContext] = None, + endpoint_ctx: EndpointContext | None = None, ) -> Any: if field: if is_coroutine: @@ -318,17 +316,17 @@ async def run_endpoint_function( def get_request_handler( dependant: Dependant, - body_field: Optional[ModelField] = None, - status_code: Optional[int] = None, - response_class: Union[type[Response], DefaultPlaceholder] = Default(JSONResponse), - response_field: Optional[ModelField] = None, - response_model_include: Optional[IncEx] = None, - response_model_exclude: Optional[IncEx] = None, + body_field: ModelField | None = None, + status_code: int | None = None, + response_class: type[Response] | DefaultPlaceholder = Default(JSONResponse), + response_field: ModelField | None = None, + response_model_include: IncEx | None = None, + response_model_exclude: IncEx | None = None, response_model_by_alias: bool = True, response_model_exclude_unset: bool = False, response_model_exclude_defaults: bool = False, response_model_exclude_none: bool = False, - dependency_overrides_provider: Optional[Any] = None, + dependency_overrides_provider: Any | None = None, embed_body_fields: bool = False, ) -> Callable[[Request], Coroutine[Any, Any, Response]]: assert dependant.call is not None, "dependant.call must be a function" @@ -340,7 +338,7 @@ def get_request_handler( actual_response_class = response_class async def app(request: Request) -> Response: - response: Union[Response, None] = None + response: Response | None = None file_stack = request.scope.get("fastapi_middleware_astack") assert isinstance(file_stack, AsyncExitStack), ( "fastapi_middleware_astack not found in request scope" @@ -476,7 +474,7 @@ def get_request_handler( def get_websocket_app( dependant: Dependant, - dependency_overrides_provider: Optional[Any] = None, + dependency_overrides_provider: Any | None = None, embed_body_fields: bool = False, ) -> Callable[[WebSocket], Coroutine[Any, Any, Any]]: async def app(websocket: WebSocket) -> None: @@ -517,9 +515,9 @@ class APIWebSocketRoute(routing.WebSocketRoute): path: str, endpoint: Callable[..., Any], *, - name: Optional[str] = None, - dependencies: Optional[Sequence[params.Depends]] = None, - dependency_overrides_provider: Optional[Any] = None, + name: str | None = None, + dependencies: Sequence[params.Depends] | None = None, + dependency_overrides_provider: Any | None = None, ) -> None: self.path = path self.endpoint = endpoint @@ -560,33 +558,30 @@ class APIRoute(routing.Route): endpoint: Callable[..., Any], *, response_model: Any = Default(None), - status_code: Optional[int] = None, - tags: Optional[list[Union[str, Enum]]] = None, - dependencies: Optional[Sequence[params.Depends]] = None, - summary: Optional[str] = None, - description: Optional[str] = None, + status_code: int | None = None, + tags: list[str | Enum] | None = None, + dependencies: Sequence[params.Depends] | None = None, + summary: str | None = None, + description: str | None = None, response_description: str = "Successful Response", - responses: Optional[dict[Union[int, str], dict[str, Any]]] = None, - deprecated: Optional[bool] = None, - name: Optional[str] = None, - methods: Optional[Union[set[str], list[str]]] = None, - operation_id: Optional[str] = None, - response_model_include: Optional[IncEx] = None, - response_model_exclude: Optional[IncEx] = None, + responses: dict[int | str, dict[str, Any]] | None = None, + deprecated: bool | None = None, + name: str | None = None, + methods: set[str] | list[str] | None = None, + operation_id: str | None = None, + response_model_include: IncEx | None = None, + response_model_exclude: IncEx | None = None, response_model_by_alias: bool = True, response_model_exclude_unset: bool = False, response_model_exclude_defaults: bool = False, response_model_exclude_none: bool = False, include_in_schema: bool = True, - response_class: Union[type[Response], DefaultPlaceholder] = Default( - JSONResponse - ), - dependency_overrides_provider: Optional[Any] = None, - callbacks: Optional[list[BaseRoute]] = None, - openapi_extra: Optional[dict[str, Any]] = None, - generate_unique_id_function: Union[ - Callable[["APIRoute"], str], DefaultPlaceholder - ] = Default(generate_unique_id), + response_class: type[Response] | DefaultPlaceholder = Default(JSONResponse), + dependency_overrides_provider: Any | None = None, + callbacks: list[BaseRoute] | None = None, + openapi_extra: dict[str, Any] | None = None, + generate_unique_id_function: Callable[["APIRoute"], str] + | DefaultPlaceholder = Default(generate_unique_id), ) -> None: self.path = path self.endpoint = endpoint @@ -662,7 +657,7 @@ class APIRoute(routing.Route): ) response_fields[additional_status_code] = response_field if response_fields: - self.response_fields: dict[Union[int, str], ModelField] = response_fields + self.response_fields: dict[int | str, ModelField] = response_fields else: self.response_fields = {} @@ -742,7 +737,7 @@ class APIRouter(routing.Router): *, prefix: Annotated[str, Doc("An optional path prefix for the router.")] = "", tags: Annotated[ - Optional[list[Union[str, Enum]]], + list[str | Enum] | None, Doc( """ A list of tags to be applied to all the *path operations* in this @@ -756,7 +751,7 @@ class APIRouter(routing.Router): ), ] = None, dependencies: Annotated[ - Optional[Sequence[params.Depends]], + Sequence[params.Depends] | None, Doc( """ A list of dependencies (using `Depends()`) to be applied to all the @@ -779,7 +774,7 @@ class APIRouter(routing.Router): ), ] = Default(JSONResponse), responses: Annotated[ - Optional[dict[Union[int, str], dict[str, Any]]], + dict[int | str, dict[str, Any]] | None, Doc( """ Additional responses to be shown in OpenAPI. @@ -795,7 +790,7 @@ class APIRouter(routing.Router): ), ] = None, callbacks: Annotated[ - Optional[list[BaseRoute]], + list[BaseRoute] | None, Doc( """ OpenAPI callbacks that should apply to all *path operations* in this @@ -809,7 +804,7 @@ class APIRouter(routing.Router): ), ] = None, routes: Annotated[ - Optional[list[BaseRoute]], + list[BaseRoute] | None, Doc( """ **Note**: you probably shouldn't use this parameter, it is inherited @@ -840,7 +835,7 @@ class APIRouter(routing.Router): ), ] = True, default: Annotated[ - Optional[ASGIApp], + ASGIApp | None, Doc( """ Default function handler for this router. Used to handle @@ -849,7 +844,7 @@ class APIRouter(routing.Router): ), ] = None, dependency_overrides_provider: Annotated[ - Optional[Any], + Any | None, Doc( """ Only used internally by FastAPI to handle dependency overrides. @@ -871,7 +866,7 @@ class APIRouter(routing.Router): ), ] = APIRoute, on_startup: Annotated[ - Optional[Sequence[Callable[[], Any]]], + Sequence[Callable[[], Any]] | None, Doc( """ A list of startup event handler functions. @@ -883,7 +878,7 @@ class APIRouter(routing.Router): ), ] = None, on_shutdown: Annotated[ - Optional[Sequence[Callable[[], Any]]], + Sequence[Callable[[], Any]] | None, Doc( """ A list of shutdown event handler functions. @@ -898,7 +893,7 @@ class APIRouter(routing.Router): # the generic to Lifespan[AppType] is the type of the top level application # which the router cannot know statically, so we use typing.Any lifespan: Annotated[ - Optional[Lifespan[Any]], + Lifespan[Any] | None, Doc( """ A `Lifespan` context manager handler. This replaces `startup` and @@ -910,7 +905,7 @@ class APIRouter(routing.Router): ), ] = None, deprecated: Annotated[ - Optional[bool], + bool | None, Doc( """ Mark all *path operations* in this router as deprecated. @@ -987,7 +982,7 @@ class APIRouter(routing.Router): ) self.prefix = prefix - self.tags: list[Union[str, Enum]] = tags or [] + self.tags: list[str | Enum] = tags or [] self.dependencies = list(dependencies or []) self.deprecated = deprecated self.include_in_schema = include_in_schema @@ -1001,8 +996,8 @@ class APIRouter(routing.Router): def route( self, path: str, - methods: Optional[Collection[str]] = None, - name: Optional[str] = None, + methods: Collection[str] | None = None, + name: str | None = None, include_in_schema: bool = True, ) -> Callable[[DecoratedCallable], DecoratedCallable]: def decorator(func: DecoratedCallable) -> DecoratedCallable: @@ -1023,33 +1018,30 @@ class APIRouter(routing.Router): endpoint: Callable[..., Any], *, response_model: Any = Default(None), - status_code: Optional[int] = None, - tags: Optional[list[Union[str, Enum]]] = None, - dependencies: Optional[Sequence[params.Depends]] = None, - summary: Optional[str] = None, - description: Optional[str] = None, + status_code: int | None = None, + tags: list[str | Enum] | None = None, + dependencies: Sequence[params.Depends] | None = None, + summary: str | None = None, + description: str | None = None, response_description: str = "Successful Response", - responses: Optional[dict[Union[int, str], dict[str, Any]]] = None, - deprecated: Optional[bool] = None, - methods: Optional[Union[set[str], list[str]]] = None, - operation_id: Optional[str] = None, - response_model_include: Optional[IncEx] = None, - response_model_exclude: Optional[IncEx] = None, + responses: dict[int | str, dict[str, Any]] | None = None, + deprecated: bool | None = None, + methods: set[str] | list[str] | None = None, + operation_id: str | None = None, + response_model_include: IncEx | None = None, + response_model_exclude: IncEx | None = None, response_model_by_alias: bool = True, response_model_exclude_unset: bool = False, response_model_exclude_defaults: bool = False, response_model_exclude_none: bool = False, include_in_schema: bool = True, - response_class: Union[type[Response], DefaultPlaceholder] = Default( - JSONResponse - ), - name: Optional[str] = None, - route_class_override: Optional[type[APIRoute]] = None, - callbacks: Optional[list[BaseRoute]] = None, - openapi_extra: Optional[dict[str, Any]] = None, - generate_unique_id_function: Union[ - Callable[[APIRoute], str], DefaultPlaceholder - ] = Default(generate_unique_id), + response_class: type[Response] | DefaultPlaceholder = Default(JSONResponse), + name: str | None = None, + route_class_override: type[APIRoute] | None = None, + callbacks: list[BaseRoute] | None = None, + openapi_extra: dict[str, Any] | None = None, + generate_unique_id_function: Callable[[APIRoute], str] + | DefaultPlaceholder = Default(generate_unique_id), ) -> None: route_class = route_class_override or self.route_class responses = responses or {} @@ -1104,27 +1096,27 @@ class APIRouter(routing.Router): path: str, *, response_model: Any = Default(None), - status_code: Optional[int] = None, - tags: Optional[list[Union[str, Enum]]] = None, - dependencies: Optional[Sequence[params.Depends]] = None, - summary: Optional[str] = None, - description: Optional[str] = None, + status_code: int | None = None, + tags: list[str | Enum] | None = None, + dependencies: Sequence[params.Depends] | None = None, + summary: str | None = None, + description: str | None = None, response_description: str = "Successful Response", - responses: Optional[dict[Union[int, str], dict[str, Any]]] = None, - deprecated: Optional[bool] = None, - methods: Optional[list[str]] = None, - operation_id: Optional[str] = None, - response_model_include: Optional[IncEx] = None, - response_model_exclude: Optional[IncEx] = None, + responses: dict[int | str, dict[str, Any]] | None = None, + deprecated: bool | None = None, + methods: list[str] | None = None, + operation_id: str | None = None, + response_model_include: IncEx | None = None, + response_model_exclude: IncEx | None = None, response_model_by_alias: bool = True, response_model_exclude_unset: bool = False, response_model_exclude_defaults: bool = False, response_model_exclude_none: bool = False, include_in_schema: bool = True, response_class: type[Response] = Default(JSONResponse), - name: Optional[str] = None, - callbacks: Optional[list[BaseRoute]] = None, - openapi_extra: Optional[dict[str, Any]] = None, + name: str | None = None, + callbacks: list[BaseRoute] | None = None, + openapi_extra: dict[str, Any] | None = None, generate_unique_id_function: Callable[[APIRoute], str] = Default( generate_unique_id ), @@ -1165,9 +1157,9 @@ class APIRouter(routing.Router): self, path: str, endpoint: Callable[..., Any], - name: Optional[str] = None, + name: str | None = None, *, - dependencies: Optional[Sequence[params.Depends]] = None, + dependencies: Sequence[params.Depends] | None = None, ) -> None: current_dependencies = self.dependencies.copy() if dependencies: @@ -1193,7 +1185,7 @@ class APIRouter(routing.Router): ), ], name: Annotated[ - Optional[str], + str | None, Doc( """ A name for the WebSocket. Only used internally. @@ -1202,7 +1194,7 @@ class APIRouter(routing.Router): ] = None, *, dependencies: Annotated[ - Optional[Sequence[params.Depends]], + Sequence[params.Depends] | None, Doc( """ A list of dependencies (using `Depends()`) to be used for this @@ -1250,7 +1242,7 @@ class APIRouter(routing.Router): return decorator def websocket_route( - self, path: str, name: Union[str, None] = None + self, path: str, name: str | None = None ) -> Callable[[DecoratedCallable], DecoratedCallable]: def decorator(func: DecoratedCallable) -> DecoratedCallable: self.add_websocket_route(path, func, name=name) @@ -1264,7 +1256,7 @@ class APIRouter(routing.Router): *, prefix: Annotated[str, Doc("An optional path prefix for the router.")] = "", tags: Annotated[ - Optional[list[Union[str, Enum]]], + list[str | Enum] | None, Doc( """ A list of tags to be applied to all the *path operations* in this @@ -1278,7 +1270,7 @@ class APIRouter(routing.Router): ), ] = None, dependencies: Annotated[ - Optional[Sequence[params.Depends]], + Sequence[params.Depends] | None, Doc( """ A list of dependencies (using `Depends()`) to be applied to all the @@ -1301,7 +1293,7 @@ class APIRouter(routing.Router): ), ] = Default(JSONResponse), responses: Annotated[ - Optional[dict[Union[int, str], dict[str, Any]]], + dict[int | str, dict[str, Any]] | None, Doc( """ Additional responses to be shown in OpenAPI. @@ -1317,7 +1309,7 @@ class APIRouter(routing.Router): ), ] = None, callbacks: Annotated[ - Optional[list[BaseRoute]], + list[BaseRoute] | None, Doc( """ OpenAPI callbacks that should apply to all *path operations* in this @@ -1331,7 +1323,7 @@ class APIRouter(routing.Router): ), ] = None, deprecated: Annotated[ - Optional[bool], + bool | None, Doc( """ Mark all *path operations* in this router as deprecated. @@ -1554,7 +1546,7 @@ class APIRouter(routing.Router): ), ] = Default(None), status_code: Annotated[ - Optional[int], + int | None, Doc( """ The default status code to be used for the response. @@ -1567,7 +1559,7 @@ class APIRouter(routing.Router): ), ] = None, tags: Annotated[ - Optional[list[Union[str, Enum]]], + list[str | Enum] | None, Doc( """ A list of tags to be applied to the *path operation*. @@ -1580,7 +1572,7 @@ class APIRouter(routing.Router): ), ] = None, dependencies: Annotated[ - Optional[Sequence[params.Depends]], + Sequence[params.Depends] | None, Doc( """ A list of dependencies (using `Depends()`) to be applied to the @@ -1592,7 +1584,7 @@ class APIRouter(routing.Router): ), ] = None, summary: Annotated[ - Optional[str], + str | None, Doc( """ A summary for the *path operation*. @@ -1605,7 +1597,7 @@ class APIRouter(routing.Router): ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ A description for the *path operation*. @@ -1633,7 +1625,7 @@ class APIRouter(routing.Router): ), ] = "Successful Response", responses: Annotated[ - Optional[dict[Union[int, str], dict[str, Any]]], + dict[int | str, dict[str, Any]] | None, Doc( """ Additional responses that could be returned by this *path operation*. @@ -1643,7 +1635,7 @@ class APIRouter(routing.Router): ), ] = None, deprecated: Annotated[ - Optional[bool], + bool | None, Doc( """ Mark this *path operation* as deprecated. @@ -1653,7 +1645,7 @@ class APIRouter(routing.Router): ), ] = None, operation_id: Annotated[ - Optional[str], + str | None, Doc( """ Custom operation ID to be used by this *path operation*. @@ -1673,7 +1665,7 @@ class APIRouter(routing.Router): ), ] = None, response_model_include: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to include only certain fields in the @@ -1685,7 +1677,7 @@ class APIRouter(routing.Router): ), ] = None, response_model_exclude: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to exclude certain fields in the @@ -1787,7 +1779,7 @@ class APIRouter(routing.Router): ), ] = Default(JSONResponse), name: Annotated[ - Optional[str], + str | None, Doc( """ Name for this *path operation*. Only used internally. @@ -1795,7 +1787,7 @@ class APIRouter(routing.Router): ), ] = None, callbacks: Annotated[ - Optional[list[BaseRoute]], + list[BaseRoute] | None, Doc( """ List of *path operations* that will be used as OpenAPI callbacks. @@ -1811,7 +1803,7 @@ class APIRouter(routing.Router): ), ] = None, openapi_extra: Annotated[ - Optional[dict[str, Any]], + dict[str, Any] | None, Doc( """ Extra metadata to be included in the OpenAPI schema for this *path @@ -1931,7 +1923,7 @@ class APIRouter(routing.Router): ), ] = Default(None), status_code: Annotated[ - Optional[int], + int | None, Doc( """ The default status code to be used for the response. @@ -1944,7 +1936,7 @@ class APIRouter(routing.Router): ), ] = None, tags: Annotated[ - Optional[list[Union[str, Enum]]], + list[str | Enum] | None, Doc( """ A list of tags to be applied to the *path operation*. @@ -1957,7 +1949,7 @@ class APIRouter(routing.Router): ), ] = None, dependencies: Annotated[ - Optional[Sequence[params.Depends]], + Sequence[params.Depends] | None, Doc( """ A list of dependencies (using `Depends()`) to be applied to the @@ -1969,7 +1961,7 @@ class APIRouter(routing.Router): ), ] = None, summary: Annotated[ - Optional[str], + str | None, Doc( """ A summary for the *path operation*. @@ -1982,7 +1974,7 @@ class APIRouter(routing.Router): ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ A description for the *path operation*. @@ -2010,7 +2002,7 @@ class APIRouter(routing.Router): ), ] = "Successful Response", responses: Annotated[ - Optional[dict[Union[int, str], dict[str, Any]]], + dict[int | str, dict[str, Any]] | None, Doc( """ Additional responses that could be returned by this *path operation*. @@ -2020,7 +2012,7 @@ class APIRouter(routing.Router): ), ] = None, deprecated: Annotated[ - Optional[bool], + bool | None, Doc( """ Mark this *path operation* as deprecated. @@ -2030,7 +2022,7 @@ class APIRouter(routing.Router): ), ] = None, operation_id: Annotated[ - Optional[str], + str | None, Doc( """ Custom operation ID to be used by this *path operation*. @@ -2050,7 +2042,7 @@ class APIRouter(routing.Router): ), ] = None, response_model_include: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to include only certain fields in the @@ -2062,7 +2054,7 @@ class APIRouter(routing.Router): ), ] = None, response_model_exclude: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to exclude certain fields in the @@ -2164,7 +2156,7 @@ class APIRouter(routing.Router): ), ] = Default(JSONResponse), name: Annotated[ - Optional[str], + str | None, Doc( """ Name for this *path operation*. Only used internally. @@ -2172,7 +2164,7 @@ class APIRouter(routing.Router): ), ] = None, callbacks: Annotated[ - Optional[list[BaseRoute]], + list[BaseRoute] | None, Doc( """ List of *path operations* that will be used as OpenAPI callbacks. @@ -2188,7 +2180,7 @@ class APIRouter(routing.Router): ), ] = None, openapi_extra: Annotated[ - Optional[dict[str, Any]], + dict[str, Any] | None, Doc( """ Extra metadata to be included in the OpenAPI schema for this *path @@ -2313,7 +2305,7 @@ class APIRouter(routing.Router): ), ] = Default(None), status_code: Annotated[ - Optional[int], + int | None, Doc( """ The default status code to be used for the response. @@ -2326,7 +2318,7 @@ class APIRouter(routing.Router): ), ] = None, tags: Annotated[ - Optional[list[Union[str, Enum]]], + list[str | Enum] | None, Doc( """ A list of tags to be applied to the *path operation*. @@ -2339,7 +2331,7 @@ class APIRouter(routing.Router): ), ] = None, dependencies: Annotated[ - Optional[Sequence[params.Depends]], + Sequence[params.Depends] | None, Doc( """ A list of dependencies (using `Depends()`) to be applied to the @@ -2351,7 +2343,7 @@ class APIRouter(routing.Router): ), ] = None, summary: Annotated[ - Optional[str], + str | None, Doc( """ A summary for the *path operation*. @@ -2364,7 +2356,7 @@ class APIRouter(routing.Router): ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ A description for the *path operation*. @@ -2392,7 +2384,7 @@ class APIRouter(routing.Router): ), ] = "Successful Response", responses: Annotated[ - Optional[dict[Union[int, str], dict[str, Any]]], + dict[int | str, dict[str, Any]] | None, Doc( """ Additional responses that could be returned by this *path operation*. @@ -2402,7 +2394,7 @@ class APIRouter(routing.Router): ), ] = None, deprecated: Annotated[ - Optional[bool], + bool | None, Doc( """ Mark this *path operation* as deprecated. @@ -2412,7 +2404,7 @@ class APIRouter(routing.Router): ), ] = None, operation_id: Annotated[ - Optional[str], + str | None, Doc( """ Custom operation ID to be used by this *path operation*. @@ -2432,7 +2424,7 @@ class APIRouter(routing.Router): ), ] = None, response_model_include: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to include only certain fields in the @@ -2444,7 +2436,7 @@ class APIRouter(routing.Router): ), ] = None, response_model_exclude: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to exclude certain fields in the @@ -2546,7 +2538,7 @@ class APIRouter(routing.Router): ), ] = Default(JSONResponse), name: Annotated[ - Optional[str], + str | None, Doc( """ Name for this *path operation*. Only used internally. @@ -2554,7 +2546,7 @@ class APIRouter(routing.Router): ), ] = None, callbacks: Annotated[ - Optional[list[BaseRoute]], + list[BaseRoute] | None, Doc( """ List of *path operations* that will be used as OpenAPI callbacks. @@ -2570,7 +2562,7 @@ class APIRouter(routing.Router): ), ] = None, openapi_extra: Annotated[ - Optional[dict[str, Any]], + dict[str, Any] | None, Doc( """ Extra metadata to be included in the OpenAPI schema for this *path @@ -2695,7 +2687,7 @@ class APIRouter(routing.Router): ), ] = Default(None), status_code: Annotated[ - Optional[int], + int | None, Doc( """ The default status code to be used for the response. @@ -2708,7 +2700,7 @@ class APIRouter(routing.Router): ), ] = None, tags: Annotated[ - Optional[list[Union[str, Enum]]], + list[str | Enum] | None, Doc( """ A list of tags to be applied to the *path operation*. @@ -2721,7 +2713,7 @@ class APIRouter(routing.Router): ), ] = None, dependencies: Annotated[ - Optional[Sequence[params.Depends]], + Sequence[params.Depends] | None, Doc( """ A list of dependencies (using `Depends()`) to be applied to the @@ -2733,7 +2725,7 @@ class APIRouter(routing.Router): ), ] = None, summary: Annotated[ - Optional[str], + str | None, Doc( """ A summary for the *path operation*. @@ -2746,7 +2738,7 @@ class APIRouter(routing.Router): ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ A description for the *path operation*. @@ -2774,7 +2766,7 @@ class APIRouter(routing.Router): ), ] = "Successful Response", responses: Annotated[ - Optional[dict[Union[int, str], dict[str, Any]]], + dict[int | str, dict[str, Any]] | None, Doc( """ Additional responses that could be returned by this *path operation*. @@ -2784,7 +2776,7 @@ class APIRouter(routing.Router): ), ] = None, deprecated: Annotated[ - Optional[bool], + bool | None, Doc( """ Mark this *path operation* as deprecated. @@ -2794,7 +2786,7 @@ class APIRouter(routing.Router): ), ] = None, operation_id: Annotated[ - Optional[str], + str | None, Doc( """ Custom operation ID to be used by this *path operation*. @@ -2814,7 +2806,7 @@ class APIRouter(routing.Router): ), ] = None, response_model_include: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to include only certain fields in the @@ -2826,7 +2818,7 @@ class APIRouter(routing.Router): ), ] = None, response_model_exclude: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to exclude certain fields in the @@ -2928,7 +2920,7 @@ class APIRouter(routing.Router): ), ] = Default(JSONResponse), name: Annotated[ - Optional[str], + str | None, Doc( """ Name for this *path operation*. Only used internally. @@ -2936,7 +2928,7 @@ class APIRouter(routing.Router): ), ] = None, callbacks: Annotated[ - Optional[list[BaseRoute]], + list[BaseRoute] | None, Doc( """ List of *path operations* that will be used as OpenAPI callbacks. @@ -2952,7 +2944,7 @@ class APIRouter(routing.Router): ), ] = None, openapi_extra: Annotated[ - Optional[dict[str, Any]], + dict[str, Any] | None, Doc( """ Extra metadata to be included in the OpenAPI schema for this *path @@ -3072,7 +3064,7 @@ class APIRouter(routing.Router): ), ] = Default(None), status_code: Annotated[ - Optional[int], + int | None, Doc( """ The default status code to be used for the response. @@ -3085,7 +3077,7 @@ class APIRouter(routing.Router): ), ] = None, tags: Annotated[ - Optional[list[Union[str, Enum]]], + list[str | Enum] | None, Doc( """ A list of tags to be applied to the *path operation*. @@ -3098,7 +3090,7 @@ class APIRouter(routing.Router): ), ] = None, dependencies: Annotated[ - Optional[Sequence[params.Depends]], + Sequence[params.Depends] | None, Doc( """ A list of dependencies (using `Depends()`) to be applied to the @@ -3110,7 +3102,7 @@ class APIRouter(routing.Router): ), ] = None, summary: Annotated[ - Optional[str], + str | None, Doc( """ A summary for the *path operation*. @@ -3123,7 +3115,7 @@ class APIRouter(routing.Router): ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ A description for the *path operation*. @@ -3151,7 +3143,7 @@ class APIRouter(routing.Router): ), ] = "Successful Response", responses: Annotated[ - Optional[dict[Union[int, str], dict[str, Any]]], + dict[int | str, dict[str, Any]] | None, Doc( """ Additional responses that could be returned by this *path operation*. @@ -3161,7 +3153,7 @@ class APIRouter(routing.Router): ), ] = None, deprecated: Annotated[ - Optional[bool], + bool | None, Doc( """ Mark this *path operation* as deprecated. @@ -3171,7 +3163,7 @@ class APIRouter(routing.Router): ), ] = None, operation_id: Annotated[ - Optional[str], + str | None, Doc( """ Custom operation ID to be used by this *path operation*. @@ -3191,7 +3183,7 @@ class APIRouter(routing.Router): ), ] = None, response_model_include: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to include only certain fields in the @@ -3203,7 +3195,7 @@ class APIRouter(routing.Router): ), ] = None, response_model_exclude: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to exclude certain fields in the @@ -3305,7 +3297,7 @@ class APIRouter(routing.Router): ), ] = Default(JSONResponse), name: Annotated[ - Optional[str], + str | None, Doc( """ Name for this *path operation*. Only used internally. @@ -3313,7 +3305,7 @@ class APIRouter(routing.Router): ), ] = None, callbacks: Annotated[ - Optional[list[BaseRoute]], + list[BaseRoute] | None, Doc( """ List of *path operations* that will be used as OpenAPI callbacks. @@ -3329,7 +3321,7 @@ class APIRouter(routing.Router): ), ] = None, openapi_extra: Annotated[ - Optional[dict[str, Any]], + dict[str, Any] | None, Doc( """ Extra metadata to be included in the OpenAPI schema for this *path @@ -3449,7 +3441,7 @@ class APIRouter(routing.Router): ), ] = Default(None), status_code: Annotated[ - Optional[int], + int | None, Doc( """ The default status code to be used for the response. @@ -3462,7 +3454,7 @@ class APIRouter(routing.Router): ), ] = None, tags: Annotated[ - Optional[list[Union[str, Enum]]], + list[str | Enum] | None, Doc( """ A list of tags to be applied to the *path operation*. @@ -3475,7 +3467,7 @@ class APIRouter(routing.Router): ), ] = None, dependencies: Annotated[ - Optional[Sequence[params.Depends]], + Sequence[params.Depends] | None, Doc( """ A list of dependencies (using `Depends()`) to be applied to the @@ -3487,7 +3479,7 @@ class APIRouter(routing.Router): ), ] = None, summary: Annotated[ - Optional[str], + str | None, Doc( """ A summary for the *path operation*. @@ -3500,7 +3492,7 @@ class APIRouter(routing.Router): ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ A description for the *path operation*. @@ -3528,7 +3520,7 @@ class APIRouter(routing.Router): ), ] = "Successful Response", responses: Annotated[ - Optional[dict[Union[int, str], dict[str, Any]]], + dict[int | str, dict[str, Any]] | None, Doc( """ Additional responses that could be returned by this *path operation*. @@ -3538,7 +3530,7 @@ class APIRouter(routing.Router): ), ] = None, deprecated: Annotated[ - Optional[bool], + bool | None, Doc( """ Mark this *path operation* as deprecated. @@ -3548,7 +3540,7 @@ class APIRouter(routing.Router): ), ] = None, operation_id: Annotated[ - Optional[str], + str | None, Doc( """ Custom operation ID to be used by this *path operation*. @@ -3568,7 +3560,7 @@ class APIRouter(routing.Router): ), ] = None, response_model_include: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to include only certain fields in the @@ -3580,7 +3572,7 @@ class APIRouter(routing.Router): ), ] = None, response_model_exclude: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to exclude certain fields in the @@ -3682,7 +3674,7 @@ class APIRouter(routing.Router): ), ] = Default(JSONResponse), name: Annotated[ - Optional[str], + str | None, Doc( """ Name for this *path operation*. Only used internally. @@ -3690,7 +3682,7 @@ class APIRouter(routing.Router): ), ] = None, callbacks: Annotated[ - Optional[list[BaseRoute]], + list[BaseRoute] | None, Doc( """ List of *path operations* that will be used as OpenAPI callbacks. @@ -3706,7 +3698,7 @@ class APIRouter(routing.Router): ), ] = None, openapi_extra: Annotated[ - Optional[dict[str, Any]], + dict[str, Any] | None, Doc( """ Extra metadata to be included in the OpenAPI schema for this *path @@ -3831,7 +3823,7 @@ class APIRouter(routing.Router): ), ] = Default(None), status_code: Annotated[ - Optional[int], + int | None, Doc( """ The default status code to be used for the response. @@ -3844,7 +3836,7 @@ class APIRouter(routing.Router): ), ] = None, tags: Annotated[ - Optional[list[Union[str, Enum]]], + list[str | Enum] | None, Doc( """ A list of tags to be applied to the *path operation*. @@ -3857,7 +3849,7 @@ class APIRouter(routing.Router): ), ] = None, dependencies: Annotated[ - Optional[Sequence[params.Depends]], + Sequence[params.Depends] | None, Doc( """ A list of dependencies (using `Depends()`) to be applied to the @@ -3869,7 +3861,7 @@ class APIRouter(routing.Router): ), ] = None, summary: Annotated[ - Optional[str], + str | None, Doc( """ A summary for the *path operation*. @@ -3882,7 +3874,7 @@ class APIRouter(routing.Router): ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ A description for the *path operation*. @@ -3910,7 +3902,7 @@ class APIRouter(routing.Router): ), ] = "Successful Response", responses: Annotated[ - Optional[dict[Union[int, str], dict[str, Any]]], + dict[int | str, dict[str, Any]] | None, Doc( """ Additional responses that could be returned by this *path operation*. @@ -3920,7 +3912,7 @@ class APIRouter(routing.Router): ), ] = None, deprecated: Annotated[ - Optional[bool], + bool | None, Doc( """ Mark this *path operation* as deprecated. @@ -3930,7 +3922,7 @@ class APIRouter(routing.Router): ), ] = None, operation_id: Annotated[ - Optional[str], + str | None, Doc( """ Custom operation ID to be used by this *path operation*. @@ -3950,7 +3942,7 @@ class APIRouter(routing.Router): ), ] = None, response_model_include: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to include only certain fields in the @@ -3962,7 +3954,7 @@ class APIRouter(routing.Router): ), ] = None, response_model_exclude: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to exclude certain fields in the @@ -4064,7 +4056,7 @@ class APIRouter(routing.Router): ), ] = Default(JSONResponse), name: Annotated[ - Optional[str], + str | None, Doc( """ Name for this *path operation*. Only used internally. @@ -4072,7 +4064,7 @@ class APIRouter(routing.Router): ), ] = None, callbacks: Annotated[ - Optional[list[BaseRoute]], + list[BaseRoute] | None, Doc( """ List of *path operations* that will be used as OpenAPI callbacks. @@ -4088,7 +4080,7 @@ class APIRouter(routing.Router): ), ] = None, openapi_extra: Annotated[ - Optional[dict[str, Any]], + dict[str, Any] | None, Doc( """ Extra metadata to be included in the OpenAPI schema for this *path @@ -4213,7 +4205,7 @@ class APIRouter(routing.Router): ), ] = Default(None), status_code: Annotated[ - Optional[int], + int | None, Doc( """ The default status code to be used for the response. @@ -4226,7 +4218,7 @@ class APIRouter(routing.Router): ), ] = None, tags: Annotated[ - Optional[list[Union[str, Enum]]], + list[str | Enum] | None, Doc( """ A list of tags to be applied to the *path operation*. @@ -4239,7 +4231,7 @@ class APIRouter(routing.Router): ), ] = None, dependencies: Annotated[ - Optional[Sequence[params.Depends]], + Sequence[params.Depends] | None, Doc( """ A list of dependencies (using `Depends()`) to be applied to the @@ -4251,7 +4243,7 @@ class APIRouter(routing.Router): ), ] = None, summary: Annotated[ - Optional[str], + str | None, Doc( """ A summary for the *path operation*. @@ -4264,7 +4256,7 @@ class APIRouter(routing.Router): ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ A description for the *path operation*. @@ -4292,7 +4284,7 @@ class APIRouter(routing.Router): ), ] = "Successful Response", responses: Annotated[ - Optional[dict[Union[int, str], dict[str, Any]]], + dict[int | str, dict[str, Any]] | None, Doc( """ Additional responses that could be returned by this *path operation*. @@ -4302,7 +4294,7 @@ class APIRouter(routing.Router): ), ] = None, deprecated: Annotated[ - Optional[bool], + bool | None, Doc( """ Mark this *path operation* as deprecated. @@ -4312,7 +4304,7 @@ class APIRouter(routing.Router): ), ] = None, operation_id: Annotated[ - Optional[str], + str | None, Doc( """ Custom operation ID to be used by this *path operation*. @@ -4332,7 +4324,7 @@ class APIRouter(routing.Router): ), ] = None, response_model_include: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to include only certain fields in the @@ -4344,7 +4336,7 @@ class APIRouter(routing.Router): ), ] = None, response_model_exclude: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to exclude certain fields in the @@ -4446,7 +4438,7 @@ class APIRouter(routing.Router): ), ] = Default(JSONResponse), name: Annotated[ - Optional[str], + str | None, Doc( """ Name for this *path operation*. Only used internally. @@ -4454,7 +4446,7 @@ class APIRouter(routing.Router): ), ] = None, callbacks: Annotated[ - Optional[list[BaseRoute]], + list[BaseRoute] | None, Doc( """ List of *path operations* that will be used as OpenAPI callbacks. @@ -4470,7 +4462,7 @@ class APIRouter(routing.Router): ), ] = None, openapi_extra: Annotated[ - Optional[dict[str, Any]], + dict[str, Any] | None, Doc( """ Extra metadata to be included in the OpenAPI schema for this *path diff --git a/fastapi/security/api_key.py b/fastapi/security/api_key.py index 18dfb8e61..89358a913 100644 --- a/fastapi/security/api_key.py +++ b/fastapi/security/api_key.py @@ -1,4 +1,4 @@ -from typing import Annotated, Optional, Union +from typing import Annotated from annotated_doc import Doc from fastapi.openapi.models import APIKey, APIKeyIn @@ -13,8 +13,8 @@ class APIKeyBase(SecurityBase): self, location: APIKeyIn, name: str, - description: Union[str, None], - scheme_name: Union[str, None], + description: str | None, + scheme_name: str | None, auto_error: bool, ): self.auto_error = auto_error @@ -42,7 +42,7 @@ class APIKeyBase(SecurityBase): headers={"WWW-Authenticate": "APIKey"}, ) - def check_api_key(self, api_key: Optional[str]) -> Optional[str]: + def check_api_key(self, api_key: str | None) -> str | None: if not api_key: if self.auto_error: raise self.make_not_authenticated_error() @@ -90,7 +90,7 @@ class APIKeyQuery(APIKeyBase): Doc("Query parameter name."), ], scheme_name: Annotated[ - Optional[str], + str | None, Doc( """ Security scheme name. @@ -100,7 +100,7 @@ class APIKeyQuery(APIKeyBase): ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ Security scheme description. @@ -137,7 +137,7 @@ class APIKeyQuery(APIKeyBase): auto_error=auto_error, ) - async def __call__(self, request: Request) -> Optional[str]: + async def __call__(self, request: Request) -> str | None: api_key = request.query_params.get(self.model.name) return self.check_api_key(api_key) @@ -179,7 +179,7 @@ class APIKeyHeader(APIKeyBase): *, name: Annotated[str, Doc("Header name.")], scheme_name: Annotated[ - Optional[str], + str | None, Doc( """ Security scheme name. @@ -189,7 +189,7 @@ class APIKeyHeader(APIKeyBase): ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ Security scheme description. @@ -225,7 +225,7 @@ class APIKeyHeader(APIKeyBase): auto_error=auto_error, ) - async def __call__(self, request: Request) -> Optional[str]: + async def __call__(self, request: Request) -> str | None: api_key = request.headers.get(self.model.name) return self.check_api_key(api_key) @@ -267,7 +267,7 @@ class APIKeyCookie(APIKeyBase): *, name: Annotated[str, Doc("Cookie name.")], scheme_name: Annotated[ - Optional[str], + str | None, Doc( """ Security scheme name. @@ -277,7 +277,7 @@ class APIKeyCookie(APIKeyBase): ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ Security scheme description. @@ -313,6 +313,6 @@ class APIKeyCookie(APIKeyBase): auto_error=auto_error, ) - async def __call__(self, request: Request) -> Optional[str]: + async def __call__(self, request: Request) -> str | None: api_key = request.cookies.get(self.model.name) return self.check_api_key(api_key) diff --git a/fastapi/security/http.py b/fastapi/security/http.py index b4c3bc6d8..05299323c 100644 --- a/fastapi/security/http.py +++ b/fastapi/security/http.py @@ -1,6 +1,6 @@ import binascii from base64 import b64decode -from typing import Annotated, Optional +from typing import Annotated from annotated_doc import Doc from fastapi.exceptions import HTTPException @@ -71,8 +71,8 @@ class HTTPBase(SecurityBase): self, *, scheme: str, - scheme_name: Optional[str] = None, - description: Optional[str] = None, + scheme_name: str | None = None, + description: str | None = None, auto_error: bool = True, ): self.model: HTTPBaseModel = HTTPBaseModel( @@ -91,9 +91,7 @@ class HTTPBase(SecurityBase): headers=self.make_authenticate_headers(), ) - async def __call__( - self, request: Request - ) -> Optional[HTTPAuthorizationCredentials]: + async def __call__(self, request: Request) -> HTTPAuthorizationCredentials | None: authorization = request.headers.get("Authorization") scheme, credentials = get_authorization_scheme_param(authorization) if not (authorization and scheme and credentials): @@ -143,7 +141,7 @@ class HTTPBasic(HTTPBase): self, *, scheme_name: Annotated[ - Optional[str], + str | None, Doc( """ Security scheme name. @@ -153,7 +151,7 @@ class HTTPBasic(HTTPBase): ), ] = None, realm: Annotated[ - Optional[str], + str | None, Doc( """ HTTP Basic authentication realm. @@ -161,7 +159,7 @@ class HTTPBasic(HTTPBase): ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ Security scheme description. @@ -203,7 +201,7 @@ class HTTPBasic(HTTPBase): async def __call__( # type: ignore self, request: Request - ) -> Optional[HTTPBasicCredentials]: + ) -> HTTPBasicCredentials | None: authorization = request.headers.get("Authorization") scheme, param = get_authorization_scheme_param(authorization) if not authorization or scheme.lower() != "basic": @@ -256,9 +254,9 @@ class HTTPBearer(HTTPBase): def __init__( self, *, - bearerFormat: Annotated[Optional[str], Doc("Bearer token format.")] = None, + bearerFormat: Annotated[str | None, Doc("Bearer token format.")] = None, scheme_name: Annotated[ - Optional[str], + str | None, Doc( """ Security scheme name. @@ -268,7 +266,7 @@ class HTTPBearer(HTTPBase): ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ Security scheme description. @@ -302,9 +300,7 @@ class HTTPBearer(HTTPBase): self.scheme_name = scheme_name or self.__class__.__name__ self.auto_error = auto_error - async def __call__( - self, request: Request - ) -> Optional[HTTPAuthorizationCredentials]: + async def __call__(self, request: Request) -> HTTPAuthorizationCredentials | None: authorization = request.headers.get("Authorization") scheme, credentials = get_authorization_scheme_param(authorization) if not (authorization and scheme and credentials): @@ -362,7 +358,7 @@ class HTTPDigest(HTTPBase): self, *, scheme_name: Annotated[ - Optional[str], + str | None, Doc( """ Security scheme name. @@ -372,7 +368,7 @@ class HTTPDigest(HTTPBase): ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ Security scheme description. @@ -405,9 +401,7 @@ class HTTPDigest(HTTPBase): self.scheme_name = scheme_name or self.__class__.__name__ self.auto_error = auto_error - async def __call__( - self, request: Request - ) -> Optional[HTTPAuthorizationCredentials]: + async def __call__(self, request: Request) -> HTTPAuthorizationCredentials | None: authorization = request.headers.get("Authorization") scheme, credentials = get_authorization_scheme_param(authorization) if not (authorization and scheme and credentials): diff --git a/fastapi/security/oauth2.py b/fastapi/security/oauth2.py index 58ffc5c76..661043ce7 100644 --- a/fastapi/security/oauth2.py +++ b/fastapi/security/oauth2.py @@ -1,4 +1,4 @@ -from typing import Annotated, Any, Optional, Union, cast +from typing import Annotated, Any, cast from annotated_doc import Doc from fastapi.exceptions import HTTPException @@ -60,7 +60,7 @@ class OAuth2PasswordRequestForm: self, *, grant_type: Annotated[ - Union[str, None], + str | None, Form(pattern="^password$"), Doc( """ @@ -128,7 +128,7 @@ class OAuth2PasswordRequestForm: ), ] = "", client_id: Annotated[ - Union[str, None], + str | None, Form(), Doc( """ @@ -139,7 +139,7 @@ class OAuth2PasswordRequestForm: ), ] = None, client_secret: Annotated[ - Union[str, None], + str | None, Form(json_schema_extra={"format": "password"}), Doc( """ @@ -294,7 +294,7 @@ class OAuth2PasswordRequestFormStrict(OAuth2PasswordRequestForm): ), ] = "", client_id: Annotated[ - Union[str, None], + str | None, Form(), Doc( """ @@ -305,7 +305,7 @@ class OAuth2PasswordRequestFormStrict(OAuth2PasswordRequestForm): ), ] = None, client_secret: Annotated[ - Union[str, None], + str | None, Form(), Doc( """ @@ -344,7 +344,7 @@ class OAuth2(SecurityBase): self, *, flows: Annotated[ - Union[OAuthFlowsModel, dict[str, dict[str, Any]]], + OAuthFlowsModel | dict[str, dict[str, Any]], Doc( """ The dictionary of OAuth2 flows. @@ -352,7 +352,7 @@ class OAuth2(SecurityBase): ), ] = OAuthFlowsModel(), scheme_name: Annotated[ - Optional[str], + str | None, Doc( """ Security scheme name. @@ -362,7 +362,7 @@ class OAuth2(SecurityBase): ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ Security scheme description. @@ -420,7 +420,7 @@ class OAuth2(SecurityBase): headers={"WWW-Authenticate": "Bearer"}, ) - async def __call__(self, request: Request) -> Optional[str]: + async def __call__(self, request: Request) -> str | None: authorization = request.headers.get("Authorization") if not authorization: if self.auto_error: @@ -454,7 +454,7 @@ class OAuth2PasswordBearer(OAuth2): ), ], scheme_name: Annotated[ - Optional[str], + str | None, Doc( """ Security scheme name. @@ -464,7 +464,7 @@ class OAuth2PasswordBearer(OAuth2): ), ] = None, scopes: Annotated[ - Optional[dict[str, str]], + dict[str, str] | None, Doc( """ The OAuth2 scopes that would be required by the *path operations* that @@ -476,7 +476,7 @@ class OAuth2PasswordBearer(OAuth2): ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ Security scheme description. @@ -506,7 +506,7 @@ class OAuth2PasswordBearer(OAuth2): ), ] = True, refreshUrl: Annotated[ - Optional[str], + str | None, Doc( """ The URL to refresh the token and obtain a new one. @@ -533,7 +533,7 @@ class OAuth2PasswordBearer(OAuth2): auto_error=auto_error, ) - async def __call__(self, request: Request) -> Optional[str]: + async def __call__(self, request: Request) -> str | None: authorization = request.headers.get("Authorization") scheme, param = get_authorization_scheme_param(authorization) if not authorization or scheme.lower() != "bearer": @@ -562,7 +562,7 @@ class OAuth2AuthorizationCodeBearer(OAuth2): ), ], refreshUrl: Annotated[ - Optional[str], + str | None, Doc( """ The URL to refresh the token and obtain a new one. @@ -570,7 +570,7 @@ class OAuth2AuthorizationCodeBearer(OAuth2): ), ] = None, scheme_name: Annotated[ - Optional[str], + str | None, Doc( """ Security scheme name. @@ -580,7 +580,7 @@ class OAuth2AuthorizationCodeBearer(OAuth2): ), ] = None, scopes: Annotated[ - Optional[dict[str, str]], + dict[str, str] | None, Doc( """ The OAuth2 scopes that would be required by the *path operations* that @@ -589,7 +589,7 @@ class OAuth2AuthorizationCodeBearer(OAuth2): ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ Security scheme description. @@ -639,7 +639,7 @@ class OAuth2AuthorizationCodeBearer(OAuth2): auto_error=auto_error, ) - async def __call__(self, request: Request) -> Optional[str]: + async def __call__(self, request: Request) -> str | None: authorization = request.headers.get("Authorization") scheme, param = get_authorization_scheme_param(authorization) if not authorization or scheme.lower() != "bearer": @@ -666,7 +666,7 @@ class SecurityScopes: def __init__( self, scopes: Annotated[ - Optional[list[str]], + list[str] | None, Doc( """ This will be filled by FastAPI. diff --git a/fastapi/security/open_id_connect_url.py b/fastapi/security/open_id_connect_url.py index f4d953351..1c6fcc744 100644 --- a/fastapi/security/open_id_connect_url.py +++ b/fastapi/security/open_id_connect_url.py @@ -1,4 +1,4 @@ -from typing import Annotated, Optional +from typing import Annotated from annotated_doc import Doc from fastapi.openapi.models import OpenIdConnect as OpenIdConnectModel @@ -31,7 +31,7 @@ class OpenIdConnect(SecurityBase): ), ], scheme_name: Annotated[ - Optional[str], + str | None, Doc( """ Security scheme name. @@ -41,7 +41,7 @@ class OpenIdConnect(SecurityBase): ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ Security scheme description. @@ -84,7 +84,7 @@ class OpenIdConnect(SecurityBase): headers={"WWW-Authenticate": "Bearer"}, ) - async def __call__(self, request: Request) -> Optional[str]: + async def __call__(self, request: Request) -> str | None: authorization = request.headers.get("Authorization") if not authorization: if self.auto_error: diff --git a/fastapi/security/utils.py b/fastapi/security/utils.py index fd349aec7..8ee66fd38 100644 --- a/fastapi/security/utils.py +++ b/fastapi/security/utils.py @@ -1,8 +1,5 @@ -from typing import Optional - - def get_authorization_scheme_param( - authorization_header_value: Optional[str], + authorization_header_value: str | None, ) -> tuple[str, str]: if not authorization_header_value: return "", "" diff --git a/fastapi/types.py b/fastapi/types.py index 1c3a6de74..1fb86e13b 100644 --- a/fastapi/types.py +++ b/fastapi/types.py @@ -1,11 +1,12 @@ import types +from collections.abc import Callable from enum import Enum -from typing import Any, Callable, Optional, TypeVar, Union +from typing import Any, TypeVar, Union from pydantic import BaseModel from pydantic.main import IncEx as IncEx DecoratedCallable = TypeVar("DecoratedCallable", bound=Callable[..., Any]) UnionType = getattr(types, "UnionType", Union) -ModelNameMap = dict[Union[type[BaseModel], type[Enum]], str] -DependencyCacheKey = tuple[Optional[Callable[..., Any]], tuple[str, ...], str] +ModelNameMap = dict[type[BaseModel] | type[Enum], str] +DependencyCacheKey = tuple[Callable[..., Any] | None, tuple[str, ...], str] diff --git a/fastapi/utils.py b/fastapi/utils.py index 11082ffa0..a43099165 100644 --- a/fastapi/utils.py +++ b/fastapi/utils.py @@ -3,8 +3,7 @@ import warnings from typing import ( TYPE_CHECKING, Any, - Optional, - Union, + Literal, ) import fastapi @@ -17,7 +16,6 @@ from fastapi._compat import ( from fastapi.datastructures import DefaultPlaceholder, DefaultType from fastapi.exceptions import FastAPIDeprecationWarning, PydanticV1NotSupportedError from pydantic.fields import FieldInfo -from typing_extensions import Literal from ._compat import v2 @@ -25,7 +23,7 @@ if TYPE_CHECKING: # pragma: nocover from .routing import APIRoute -def is_body_allowed_for_status_code(status_code: Union[int, str, None]) -> bool: +def is_body_allowed_for_status_code(status_code: int | str | None) -> bool: if status_code is None: return True # Ref: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#patterned-fields-1 @@ -61,9 +59,9 @@ _invalid_args_message = ( def create_model_field( name: str, type_: Any, - default: Optional[Any] = Undefined, - field_info: Optional[FieldInfo] = None, - alias: Optional[str] = None, + default: Any | None = Undefined, + field_info: FieldInfo | None = None, + alias: str | None = None, mode: Literal["validation", "serialization"] = "validation", ) -> ModelField: if annotation_is_pydantic_v1(type_): @@ -122,9 +120,9 @@ def deep_dict_update(main_dict: dict[Any, Any], update_dict: dict[Any, Any]) -> def get_value_or_default( - first_item: Union[DefaultPlaceholder, DefaultType], - *extra_items: Union[DefaultPlaceholder, DefaultType], -) -> Union[DefaultPlaceholder, DefaultType]: + first_item: DefaultPlaceholder | DefaultType, + *extra_items: DefaultPlaceholder | DefaultType, +) -> DefaultPlaceholder | DefaultType: """ Pass items or `DefaultPlaceholder`s by descending priority. diff --git a/pdm_build.py b/pdm_build.py index a0eb88eeb..b1b662bd3 100644 --- a/pdm_build.py +++ b/pdm_build.py @@ -3,18 +3,38 @@ from typing import Any from pdm.backend.hooks import Context -TIANGOLO_BUILD_PACKAGE = os.getenv("TIANGOLO_BUILD_PACKAGE", "fastapi") +TIANGOLO_BUILD_PACKAGE = os.getenv("TIANGOLO_BUILD_PACKAGE") def pdm_build_initialize(context: Context) -> None: metadata = context.config.metadata + # Get main version + version = metadata["version"] # Get custom config for the current package, from the env var - config: dict[str, Any] = context.config.data["tool"]["tiangolo"][ + all_configs_config: dict[str, Any] = context.config.data["tool"]["tiangolo"][ "_internal-slim-build" - ]["packages"].get(TIANGOLO_BUILD_PACKAGE) - if not config: + ]["packages"] + + if TIANGOLO_BUILD_PACKAGE not in all_configs_config: return + + config = all_configs_config[TIANGOLO_BUILD_PACKAGE] project_config: dict[str, Any] = config["project"] # Override main [project] configs with custom configs for this package for key, value in project_config.items(): metadata[key] = value + # Get custom build config for the current package + build_config: dict[str, Any] = ( + config.get("tool", {}).get("pdm", {}).get("build", {}) + ) + # Override PDM build config with custom build config for this package + for key, value in build_config.items(): + context.config.build_config[key] = value + # Get main dependencies + dependencies: list[str] = metadata.get("dependencies", []) + # Sync versions in dependencies + new_dependencies = [] + for dep in dependencies: + new_dep = f"{dep}>={version}" + new_dependencies.append(new_dep) + metadata["dependencies"] = new_dependencies diff --git a/pyproject.toml b/pyproject.toml index 4895c2d34..1e6fda3b1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ description = "FastAPI framework, high performance, easy to learn, fast to code, readme = "README.md" license = "MIT" license-files = ["LICENSE"] -requires-python = ">=3.9" +requires-python = ">=3.10" authors = [ { name = "Sebastián Ramírez", email = "tiangolo@gmail.com" }, ] @@ -33,7 +33,6 @@ classifiers = [ "Framework :: Pydantic :: 2", "Intended Audience :: Developers", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", @@ -202,6 +201,29 @@ source-includes = [ [tool.tiangolo._internal-slim-build.packages.fastapi-slim.project] name = "fastapi-slim" +readme = "fastapi-slim/README.md" +dependencies = [ + "fastapi", +] +optional-dependencies = {} +scripts = {} + +[tool.tiangolo._internal-slim-build.packages.fastapi-slim.tool.pdm.build] +# excludes needs to explicitly exclude the top level python packages, +# otherwise PDM includes them by default +# A "*" glob pattern can't be used here because in PDM internals, the patterns are put +# in a set (unordered, order varies) and each excluded file is assigned one of the +# glob patterns that matches, as the set is unordered, the matched pattern could be "*" +# independent of the order here. And then the internal code would give it a lower score +# than the one for a default included file. +# By not using "*" and explicitly excluding the top level packages, they get a higher +# score than the default inclusion +excludes = ["fastapi", "tests", "pdm_build.py"] +# source-includes needs to explicitly define some value because PDM will check the +# truthy value of the list, and if empty, will include some defaults, including "tests", +# an empty string doesn't match anything, but makes the list truthy, so that PDM +# doesn't override it during the build. +source-includes = [""] [tool.mypy] plugins = ["pydantic.mypy"] @@ -263,20 +285,205 @@ omit = [ "docs_src/dependencies/tutorial014_an_py310.py", # temporary code example? # Pydantic v1 migration, no longer tested "docs_src/pydantic_v1_in_v2/tutorial001_an_py310.py", - "docs_src/pydantic_v1_in_v2/tutorial001_an_py39.py", "docs_src/pydantic_v1_in_v2/tutorial002_an_py310.py", - "docs_src/pydantic_v1_in_v2/tutorial002_an_py39.py", "docs_src/pydantic_v1_in_v2/tutorial003_an_py310.py", - "docs_src/pydantic_v1_in_v2/tutorial003_an_py39.py", "docs_src/pydantic_v1_in_v2/tutorial004_an_py310.py", - "docs_src/pydantic_v1_in_v2/tutorial004_an_py39.py", - # TODO: remove when removing this file, after updating translations, Pydantic v1 - "docs_src/schema_extra_example/tutorial001_pv1_py310.py", - "docs_src/schema_extra_example/tutorial001_pv1_py39.py", - "docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py", - "docs_src/settings/app03_py39/config_pv1.py", - "docs_src/settings/app03_an_py39/config_pv1.py", - "docs_src/settings/tutorial001_pv1_py39.py", + # TODO: remove all the ignores below when all translations use the new Python 3.10 files + "docs_src/additional_responses/tutorial001_py39.py", + "docs_src/additional_responses/tutorial003_py39.py", + "docs_src/advanced_middleware/tutorial001_py39.py", + "docs_src/advanced_middleware/tutorial002_py39.py", + "docs_src/advanced_middleware/tutorial003_py39.py", + "docs_src/app_testing/app_a_py39/main.py", + "docs_src/app_testing/app_a_py39/test_main.py", + "docs_src/app_testing/tutorial001_py39.py", + "docs_src/app_testing/tutorial002_py39.py", + "docs_src/app_testing/tutorial003_py39.py", + "docs_src/app_testing/tutorial004_py39.py", + "docs_src/async_tests/app_a_py39/main.py", + "docs_src/async_tests/app_a_py39/test_main.py", + "docs_src/authentication_error_status_code/tutorial001_an_py39.py", + "docs_src/background_tasks/tutorial001_py39.py", + "docs_src/behind_a_proxy/tutorial001_01_py39.py", + "docs_src/behind_a_proxy/tutorial001_py39.py", + "docs_src/behind_a_proxy/tutorial002_py39.py", + "docs_src/behind_a_proxy/tutorial003_py39.py", + "docs_src/behind_a_proxy/tutorial004_py39.py", + "docs_src/bigger_applications/app_an_py39/dependencies.py", + "docs_src/bigger_applications/app_an_py39/internal/admin.py", + "docs_src/bigger_applications/app_an_py39/main.py", + "docs_src/bigger_applications/app_an_py39/routers/items.py", + "docs_src/bigger_applications/app_an_py39/routers/users.py", + "docs_src/bigger_applications/app_py39/dependencies.py", + "docs_src/bigger_applications/app_py39/main.py", + "docs_src/body_nested_models/tutorial008_py39.py", + "docs_src/body_nested_models/tutorial009_py39.py", + "docs_src/conditional_openapi/tutorial001_py39.py", + "docs_src/configure_swagger_ui/tutorial001_py39.py", + "docs_src/configure_swagger_ui/tutorial002_py39.py", + "docs_src/configure_swagger_ui/tutorial003_py39.py", + "docs_src/cors/tutorial001_py39.py", + "docs_src/custom_docs_ui/tutorial001_py39.py", + "docs_src/custom_docs_ui/tutorial002_py39.py", + "docs_src/custom_response/tutorial001_py39.py", + "docs_src/custom_response/tutorial001b_py39.py", + "docs_src/custom_response/tutorial002_py39.py", + "docs_src/custom_response/tutorial003_py39.py", + "docs_src/custom_response/tutorial004_py39.py", + "docs_src/custom_response/tutorial005_py39.py", + "docs_src/custom_response/tutorial006_py39.py", + "docs_src/custom_response/tutorial006b_py39.py", + "docs_src/custom_response/tutorial006c_py39.py", + "docs_src/custom_response/tutorial007_py39.py", + "docs_src/custom_response/tutorial008_py39.py", + "docs_src/custom_response/tutorial009_py39.py", + "docs_src/custom_response/tutorial009b_py39.py", + "docs_src/custom_response/tutorial009c_py39.py", + "docs_src/custom_response/tutorial010_py39.py", + "docs_src/debugging/tutorial001_py39.py", + "docs_src/dependencies/tutorial006_an_py39.py", + "docs_src/dependencies/tutorial006_py39.py", + "docs_src/dependencies/tutorial007_py39.py", + "docs_src/dependencies/tutorial008_py39.py", + "docs_src/dependencies/tutorial008b_an_py39.py", + "docs_src/dependencies/tutorial008b_py39.py", + "docs_src/dependencies/tutorial008c_an_py39.py", + "docs_src/dependencies/tutorial008c_py39.py", + "docs_src/dependencies/tutorial008d_an_py39.py", + "docs_src/dependencies/tutorial008d_py39.py", + "docs_src/dependencies/tutorial008e_an_py39.py", + "docs_src/dependencies/tutorial008e_py39.py", + "docs_src/dependencies/tutorial010_py39.py", + "docs_src/dependencies/tutorial011_an_py39.py", + "docs_src/dependencies/tutorial011_py39.py", + "docs_src/dependencies/tutorial012_an_py39.py", + "docs_src/dependencies/tutorial012_py39.py", + "docs_src/events/tutorial001_py39.py", + "docs_src/events/tutorial002_py39.py", + "docs_src/events/tutorial003_py39.py", + "docs_src/extending_openapi/tutorial001_py39.py", + "docs_src/extra_models/tutorial004_py39.py", + "docs_src/extra_models/tutorial005_py39.py", + "docs_src/first_steps/tutorial001_py39.py", + "docs_src/first_steps/tutorial003_py39.py", + "docs_src/generate_clients/tutorial001_py39.py", + "docs_src/generate_clients/tutorial002_py39.py", + "docs_src/generate_clients/tutorial003_py39.py", + "docs_src/generate_clients/tutorial004_py39.py", + "docs_src/graphql_/tutorial001_py39.py", + "docs_src/handling_errors/tutorial001_py39.py", + "docs_src/handling_errors/tutorial002_py39.py", + "docs_src/handling_errors/tutorial003_py39.py", + "docs_src/handling_errors/tutorial004_py39.py", + "docs_src/handling_errors/tutorial005_py39.py", + "docs_src/handling_errors/tutorial006_py39.py", + "docs_src/metadata/tutorial001_1_py39.py", + "docs_src/metadata/tutorial001_py39.py", + "docs_src/metadata/tutorial002_py39.py", + "docs_src/metadata/tutorial003_py39.py", + "docs_src/metadata/tutorial004_py39.py", + "docs_src/middleware/tutorial001_py39.py", + "docs_src/openapi_webhooks/tutorial001_py39.py", + "docs_src/path_operation_advanced_configuration/tutorial001_py39.py", + "docs_src/path_operation_advanced_configuration/tutorial002_py39.py", + "docs_src/path_operation_advanced_configuration/tutorial003_py39.py", + "docs_src/path_operation_advanced_configuration/tutorial005_py39.py", + "docs_src/path_operation_advanced_configuration/tutorial006_py39.py", + "docs_src/path_operation_advanced_configuration/tutorial007_py39.py", + "docs_src/path_operation_configuration/tutorial002b_py39.py", + "docs_src/path_operation_configuration/tutorial006_py39.py", + "docs_src/path_params/tutorial001_py39.py", + "docs_src/path_params/tutorial002_py39.py", + "docs_src/path_params/tutorial003_py39.py", + "docs_src/path_params/tutorial003b_py39.py", + "docs_src/path_params/tutorial004_py39.py", + "docs_src/path_params/tutorial005_py39.py", + "docs_src/path_params_numeric_validations/tutorial002_an_py39.py", + "docs_src/path_params_numeric_validations/tutorial002_py39.py", + "docs_src/path_params_numeric_validations/tutorial003_an_py39.py", + "docs_src/path_params_numeric_validations/tutorial003_py39.py", + "docs_src/path_params_numeric_validations/tutorial004_an_py39.py", + "docs_src/path_params_numeric_validations/tutorial004_py39.py", + "docs_src/path_params_numeric_validations/tutorial005_an_py39.py", + "docs_src/path_params_numeric_validations/tutorial005_py39.py", + "docs_src/path_params_numeric_validations/tutorial006_an_py39.py", + "docs_src/path_params_numeric_validations/tutorial006_py39.py", + "docs_src/python_types/tutorial001_py39.py", + "docs_src/python_types/tutorial002_py39.py", + "docs_src/python_types/tutorial003_py39.py", + "docs_src/python_types/tutorial004_py39.py", + "docs_src/python_types/tutorial005_py39.py", + "docs_src/python_types/tutorial006_py39.py", + "docs_src/python_types/tutorial007_py39.py", + "docs_src/python_types/tutorial008_py39.py", + "docs_src/python_types/tutorial008b_py39.py", + "docs_src/python_types/tutorial009_py39.py", + "docs_src/python_types/tutorial009b_py39.py", + "docs_src/python_types/tutorial009c_py39.py", + "docs_src/python_types/tutorial010_py39.py", + "docs_src/python_types/tutorial013_py39.py", + "docs_src/query_params/tutorial001_py39.py", + "docs_src/query_params/tutorial005_py39.py", + "docs_src/query_params_str_validations/tutorial005_an_py39.py", + "docs_src/query_params_str_validations/tutorial005_py39.py", + "docs_src/query_params_str_validations/tutorial006_an_py39.py", + "docs_src/query_params_str_validations/tutorial006_py39.py", + "docs_src/query_params_str_validations/tutorial012_an_py39.py", + "docs_src/query_params_str_validations/tutorial012_py39.py", + "docs_src/query_params_str_validations/tutorial013_an_py39.py", + "docs_src/query_params_str_validations/tutorial013_py39.py", + "docs_src/request_files/tutorial001_03_an_py39.py", + "docs_src/request_files/tutorial001_03_py39.py", + "docs_src/request_files/tutorial001_an_py39.py", + "docs_src/request_files/tutorial001_py39.py", + "docs_src/request_files/tutorial002_an_py39.py", + "docs_src/request_files/tutorial002_py39.py", + "docs_src/request_files/tutorial003_an_py39.py", + "docs_src/request_files/tutorial003_py39.py", + "docs_src/request_form_models/tutorial001_an_py39.py", + "docs_src/request_form_models/tutorial001_py39.py", + "docs_src/request_form_models/tutorial002_an_py39.py", + "docs_src/request_form_models/tutorial002_py39.py", + "docs_src/request_forms/tutorial001_an_py39.py", + "docs_src/request_forms/tutorial001_py39.py", + "docs_src/request_forms_and_files/tutorial001_an_py39.py", + "docs_src/request_forms_and_files/tutorial001_py39.py", + "docs_src/response_change_status_code/tutorial001_py39.py", + "docs_src/response_cookies/tutorial001_py39.py", + "docs_src/response_cookies/tutorial002_py39.py", + "docs_src/response_directly/tutorial002_py39.py", + "docs_src/response_headers/tutorial001_py39.py", + "docs_src/response_headers/tutorial002_py39.py", + "docs_src/response_model/tutorial003_02_py39.py", + "docs_src/response_model/tutorial003_03_py39.py", + "docs_src/response_status_code/tutorial001_py39.py", + "docs_src/response_status_code/tutorial002_py39.py", + "docs_src/security/tutorial001_an_py39.py", + "docs_src/security/tutorial001_py39.py", + "docs_src/security/tutorial006_an_py39.py", + "docs_src/security/tutorial006_py39.py", + "docs_src/security/tutorial007_an_py39.py", + "docs_src/security/tutorial007_py39.py", + "docs_src/settings/app01_py39/config.py", + "docs_src/settings/app01_py39/main.py", + "docs_src/settings/app02_an_py39/config.py", + "docs_src/settings/app02_an_py39/main.py", + "docs_src/settings/app02_an_py39/test_main.py", + "docs_src/settings/app02_py39/config.py", + "docs_src/settings/app02_py39/main.py", + "docs_src/settings/app02_py39/test_main.py", + "docs_src/settings/app03_an_py39/config.py", + "docs_src/settings/app03_an_py39/main.py", + "docs_src/settings/app03_py39/config.py", + "docs_src/settings/app03_py39/main.py", + "docs_src/settings/tutorial001_py39.py", + "docs_src/static_files/tutorial001_py39.py", + "docs_src/sub_applications/tutorial001_py39.py", + "docs_src/templates/tutorial001_py39.py", + "docs_src/using_request_directly/tutorial001_py39.py", + "docs_src/websockets/tutorial001_py39.py", + "docs_src/websockets/tutorial003_py39.py", + "docs_src/wsgi/tutorial001_py39.py", ] [tool.coverage.report] @@ -305,32 +512,42 @@ ignore = [ [tool.ruff.lint.per-file-ignores] "__init__.py" = ["F401"] -"docs_src/dependencies/tutorial007_py39.py" = ["F821"] -"docs_src/dependencies/tutorial008_py39.py" = ["F821"] -"docs_src/dependencies/tutorial009_py39.py" = ["F821"] -"docs_src/dependencies/tutorial010_py39.py" = ["F821"] +"docs_src/custom_request_and_route/tutorial002_an_py310.py" = ["B904"] +"docs_src/custom_request_and_route/tutorial002_an_py39.py" = ["B904"] +"docs_src/custom_request_and_route/tutorial002_py310.py" = ["B904"] +"docs_src/custom_request_and_route/tutorial002_py39.py" = ["B904"] +"docs_src/custom_response/tutorial007_py310.py" = ["B007"] "docs_src/custom_response/tutorial007_py39.py" = ["B007"] "docs_src/dataclasses/tutorial003_py39.py" = ["I001"] -"docs_src/path_operation_advanced_configuration/tutorial007_py39.py" = ["B904"] -"docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py" = ["B904"] -"docs_src/custom_request_and_route/tutorial002_py39.py" = ["B904"] -"docs_src/custom_request_and_route/tutorial002_py310.py" = ["B904"] -"docs_src/custom_request_and_route/tutorial002_an_py39.py" = ["B904"] -"docs_src/custom_request_and_route/tutorial002_an_py310.py" = ["B904"] +"docs_src/dependencies/tutorial007_py310.py" = ["F821"] +"docs_src/dependencies/tutorial007_py39.py" = ["F821"] +"docs_src/dependencies/tutorial008_an_py310.py" = ["F821"] "docs_src/dependencies/tutorial008_an_py39.py" = ["F821"] +"docs_src/dependencies/tutorial008_py310.py" = ["F821"] +"docs_src/dependencies/tutorial008_py39.py" = ["F821"] +"docs_src/dependencies/tutorial008b_an_py310.py" = ["B904"] +"docs_src/dependencies/tutorial008b_an_py39.py" = ["B904"] +"docs_src/dependencies/tutorial008b_py310.py" = ["B904"] +"docs_src/dependencies/tutorial008b_py39.py" = ["B904"] +"docs_src/dependencies/tutorial009_py310.py" = ["F821"] +"docs_src/dependencies/tutorial009_py39.py" = ["F821"] +"docs_src/dependencies/tutorial010_py310.py" = ["F821"] +"docs_src/dependencies/tutorial010_py39.py" = ["F821"] +"docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py" = ["B904"] +"docs_src/path_operation_advanced_configuration/tutorial007_py310.py" = ["B904"] +"docs_src/path_operation_advanced_configuration/tutorial007_py39.py" = ["B904"] +"docs_src/query_params_str_validations/tutorial012_an_py310.py" = ["B006"] "docs_src/query_params_str_validations/tutorial012_an_py39.py" = ["B006"] +"docs_src/query_params_str_validations/tutorial013_an_py310.py" = ["B006"] "docs_src/query_params_str_validations/tutorial013_an_py39.py" = ["B006"] -"docs_src/security/tutorial004_py39.py" = ["B904"] -"docs_src/security/tutorial004_an_py39.py" = ["B904"] "docs_src/security/tutorial004_an_py310.py" = ["B904"] +"docs_src/security/tutorial004_an_py39.py" = ["B904"] "docs_src/security/tutorial004_py310.py" = ["B904"] +"docs_src/security/tutorial004_py39.py" = ["B904"] "docs_src/security/tutorial005_an_py310.py" = ["B904"] "docs_src/security/tutorial005_an_py39.py" = ["B904"] "docs_src/security/tutorial005_py310.py" = ["B904"] "docs_src/security/tutorial005_py39.py" = ["B904"] -"docs_src/dependencies/tutorial008b_py39.py" = ["B904"] -"docs_src/dependencies/tutorial008b_an_py39.py" = ["B904"] - [tool.ruff.lint.isort] known-third-party = ["fastapi", "pydantic", "starlette"] diff --git a/scripts/docs.py b/scripts/docs.py index 3cf62f084..23d74aaf4 100644 --- a/scripts/docs.py +++ b/scripts/docs.py @@ -8,7 +8,7 @@ from html.parser import HTMLParser from http.server import HTTPServer, SimpleHTTPRequestHandler from multiprocessing import Pool from pathlib import Path -from typing import Any, Optional, Union +from typing import Any import mkdocs.utils import typer @@ -103,7 +103,7 @@ def get_lang_paths() -> list[Path]: return sorted(docs_path.iterdir()) -def lang_callback(lang: Optional[str]) -> Union[str, None]: +def lang_callback(lang: str | None) -> str | None: if lang is None: return None lang = lang.lower() @@ -412,6 +412,13 @@ def langs_json(): @app.command() def generate_docs_src_versions_for_file(file_path: Path) -> None: target_versions = ["py39", "py310"] + full_path_str = str(file_path) + for target_version in target_versions: + if f"_{target_version}" in full_path_str: + logging.info( + f"Skipping {file_path}, already a version file for {target_version}" + ) + return base_content = file_path.read_text(encoding="utf-8") previous_content = {base_content} for target_version in target_versions: @@ -438,13 +445,207 @@ def generate_docs_src_versions_for_file(file_path: Path) -> None: if content_format in previous_content: continue previous_content.add(content_format) - version_file = file_path.with_name( - file_path.name.replace(".py", f"_{target_version}.py") - ) + # Determine where the version label should go: in the parent directory + # name or in the file name, matching the source structure. + label_in_parent = False + for v in target_versions: + if f"_{v}" in file_path.parent.name: + label_in_parent = True + break + if label_in_parent: + parent_name = file_path.parent.name + for v in target_versions: + parent_name = parent_name.replace(f"_{v}", "") + new_parent = file_path.parent.parent / f"{parent_name}_{target_version}" + new_parent.mkdir(parents=True, exist_ok=True) + version_file = new_parent / file_path.name + else: + base_name = file_path.stem + for v in target_versions: + if base_name.endswith(f"_{v}"): + base_name = base_name[: -len(f"_{v}")] + break + version_file = file_path.with_name(f"{base_name}_{target_version}.py") logging.info(f"Writing to {version_file}") version_file.write_text(content_format, encoding="utf-8") +@app.command() +def generate_docs_src_versions() -> None: + """ + Generate Python version-specific files for all .py files in docs_src. + """ + docs_src_path = Path("docs_src") + for py_file in sorted(docs_src_path.rglob("*.py")): + generate_docs_src_versions_for_file(py_file) + + +@app.command() +def copy_py39_to_py310() -> None: + """ + For each docs_src file/directory with a _py39 label that has no _py310 + counterpart, copy it with the _py310 label. + """ + docs_src_path = Path("docs_src") + # Handle directory-level labels (e.g. app_b_an_py39/) + for dir_path in sorted(docs_src_path.rglob("*_py39")): + if not dir_path.is_dir(): + continue + py310_dir = dir_path.parent / dir_path.name.replace("_py39", "_py310") + if py310_dir.exists(): + continue + logging.info(f"Copying directory {dir_path} -> {py310_dir}") + shutil.copytree(dir_path, py310_dir) + # Handle file-level labels (e.g. tutorial001_py39.py) + for file_path in sorted(docs_src_path.rglob("*_py39.py")): + if not file_path.is_file(): + continue + # Skip files inside _py39 directories (already handled above) + if "_py39" in file_path.parent.name: + continue + py310_file = file_path.with_name( + file_path.name.replace("_py39.py", "_py310.py") + ) + if py310_file.exists(): + continue + logging.info(f"Copying file {file_path} -> {py310_file}") + shutil.copy2(file_path, py310_file) + + +@app.command() +def update_docs_includes_py39_to_py310() -> None: + """ + Update .md files in docs/en/ to replace _py39 includes with _py310 versions. + + For each include line referencing a _py39 file or directory in docs_src, replace + the _py39 label with _py310. + """ + include_pattern = re.compile(r"\{[^}]*docs_src/[^}]*_py39[^}]*\.py[^}]*\}") + count = 0 + for md_file in sorted(en_docs_path.rglob("*.md")): + content = md_file.read_text(encoding="utf-8") + if "_py39" not in content: + continue + new_content = include_pattern.sub( + lambda m: m.group(0).replace("_py39", "_py310"), content + ) + if new_content != content: + md_file.write_text(new_content, encoding="utf-8") + count += 1 + logging.info(f"Updated includes in {md_file}") + print(f"Updated {count} file(s) ✅") + + +@app.command() +def remove_unused_docs_src() -> None: + """ + Delete .py files in docs_src that are not included in any .md file under docs/. + """ + docs_src_path = Path("docs_src") + # Collect all docs .md content referencing docs_src + all_docs_content = "" + for md_file in docs_path.rglob("*.md"): + all_docs_content += md_file.read_text(encoding="utf-8") + # Build a set of directory-based package roots (e.g. docs_src/bigger_applications/app_py39) + # where at least one file is referenced in docs. All files in these directories + # should be kept since they may be internally imported by the referenced files. + used_package_dirs: set[Path] = set() + for py_file in docs_src_path.rglob("*.py"): + if py_file.name == "__init__.py": + continue + rel_path = str(py_file) + if rel_path in all_docs_content: + # Walk up from the file's parent to find the package root + # (a subdirectory under docs_src/