Compare commits

..

6 Commits

Author SHA1 Message Date
github-actions[bot]
841c5c3189 🎨 Auto format 2026-01-22 20:37:56 +00:00
Yurii Motov
fa7a5d38a7 Add swagger_extra_presets parameter to get_swagger_ui_html 2026-01-22 18:40:48 +01:00
Yurii Motov
cf7d4fe7b7 Add swagger_extra_js_urls parameter to get_swagger_ui_html 2026-01-22 18:28:20 +01:00
Yurii Motov
02d8c6e258 Remove outdated SwaggerUIBundle.SwaggerUIStandalonePreset from presets 2026-01-22 18:13:36 +01:00
github-actions[bot]
597b435ae7 📝 Update release notes
[skip ci]
2026-01-22 16:33:00 +00:00
Sofie Van Landeghem
74cc27fd5a 🔧 Ensure that an edit to uv.lock gets the internal label (#14759)
add uv.lock to files for labeling the PR with 'internal'
2026-01-22 17:32:34 +01:00
11 changed files with 69 additions and 35 deletions

1
.github/labeler.yml vendored
View File

@@ -31,6 +31,7 @@ internal:
- .pre-commit-config.yaml - .pre-commit-config.yaml
- pdm_build.py - pdm_build.py
- requirements*.txt - requirements*.txt
- uv.lock
- docs/en/data/sponsors.yml - docs/en/data/sponsors.yml
- docs/en/overrides/main.html - docs/en/overrides/main.html
- all-globs-to-all-files: - all-globs-to-all-files:

View File

@@ -164,6 +164,8 @@ $ pip install "fastapi[standard]"
Create a file `main.py` with: Create a file `main.py` with:
```Python ```Python
from typing import Union
from fastapi import FastAPI from fastapi import FastAPI
app = FastAPI() app = FastAPI()
@@ -175,7 +177,7 @@ def read_root():
@app.get("/items/{item_id}") @app.get("/items/{item_id}")
def read_item(item_id: int, q: str | None = None): def read_item(item_id: int, q: Union[str, None] = None):
return {"item_id": item_id, "q": q} return {"item_id": item_id, "q": q}
``` ```
@@ -184,7 +186,9 @@ def read_item(item_id: int, q: str | None = None):
If your code uses `async` / `await`, use `async def`: If your code uses `async` / `await`, use `async def`:
```Python hl_lines="7 12" ```Python hl_lines="9 14"
from typing import Union
from fastapi import FastAPI from fastapi import FastAPI
app = FastAPI() app = FastAPI()
@@ -196,7 +200,7 @@ async def read_root():
@app.get("/items/{item_id}") @app.get("/items/{item_id}")
async def read_item(item_id: int, q: str | None = None): async def read_item(item_id: int, q: Union[str, None] = None):
return {"item_id": item_id, "q": q} return {"item_id": item_id, "q": q}
``` ```
@@ -287,7 +291,9 @@ Now modify the file `main.py` to receive a body from a `PUT` request.
Declare the body using standard Python types, thanks to Pydantic. Declare the body using standard Python types, thanks to Pydantic.
```Python hl_lines="2 7-10 23-25" ```Python hl_lines="4 9-12 25-27"
from typing import Union
from fastapi import FastAPI from fastapi import FastAPI
from pydantic import BaseModel from pydantic import BaseModel
@@ -297,7 +303,7 @@ app = FastAPI()
class Item(BaseModel): class Item(BaseModel):
name: str name: str
price: float price: float
is_offer: bool | None = None is_offer: Union[bool, None] = None
@app.get("/") @app.get("/")
@@ -306,7 +312,7 @@ def read_root():
@app.get("/items/{item_id}") @app.get("/items/{item_id}")
def read_item(item_id: int, q: str | None = None): def read_item(item_id: int, q: Union[str, None] = None):
return {"item_id": item_id, "q": q} return {"item_id": item_id, "q": q}

View File

@@ -145,6 +145,8 @@ There are other formats and tools to define and install package dependencies.
* Create a `main.py` file with: * Create a `main.py` file with:
```Python ```Python
from typing import Union
from fastapi import FastAPI from fastapi import FastAPI
app = FastAPI() app = FastAPI()
@@ -156,7 +158,7 @@ def read_root():
@app.get("/items/{item_id}") @app.get("/items/{item_id}")
def read_item(item_id: int, q: str | None = None): def read_item(item_id: int, q: Union[str, None] = None):
return {"item_id": item_id, "q": q} return {"item_id": item_id, "q": q}
``` ```

View File

@@ -60,8 +60,7 @@ FastAPI also includes these JavaScript-only `presets` settings:
```JavaScript ```JavaScript
presets: [ presets: [
SwaggerUIBundle.presets.apis, SwaggerUIBundle.presets.apis
SwaggerUIBundle.SwaggerUIStandalonePreset
] ]
``` ```

View File

@@ -161,6 +161,8 @@ $ pip install "fastapi[standard]"
Create a file `main.py` with: Create a file `main.py` with:
```Python ```Python
from typing import Union
from fastapi import FastAPI from fastapi import FastAPI
app = FastAPI() app = FastAPI()
@@ -172,7 +174,7 @@ def read_root():
@app.get("/items/{item_id}") @app.get("/items/{item_id}")
def read_item(item_id: int, q: str | None = None): def read_item(item_id: int, q: Union[str, None] = None):
return {"item_id": item_id, "q": q} return {"item_id": item_id, "q": q}
``` ```
@@ -181,7 +183,9 @@ def read_item(item_id: int, q: str | None = None):
If your code uses `async` / `await`, use `async def`: If your code uses `async` / `await`, use `async def`:
```Python hl_lines="7 12" ```Python hl_lines="9 14"
from typing import Union
from fastapi import FastAPI from fastapi import FastAPI
app = FastAPI() app = FastAPI()
@@ -193,7 +197,7 @@ async def read_root():
@app.get("/items/{item_id}") @app.get("/items/{item_id}")
async def read_item(item_id: int, q: str | None = None): async def read_item(item_id: int, q: Union[str, None] = None):
return {"item_id": item_id, "q": q} return {"item_id": item_id, "q": q}
``` ```
@@ -284,7 +288,9 @@ Now modify the file `main.py` to receive a body from a `PUT` request.
Declare the body using standard Python types, thanks to Pydantic. Declare the body using standard Python types, thanks to Pydantic.
```Python hl_lines="2 7-10 23-25" ```Python hl_lines="4 9-12 25-27"
from typing import Union
from fastapi import FastAPI from fastapi import FastAPI
from pydantic import BaseModel from pydantic import BaseModel
@@ -294,7 +300,7 @@ app = FastAPI()
class Item(BaseModel): class Item(BaseModel):
name: str name: str
price: float price: float
is_offer: bool | None = None is_offer: Union[bool, None] = None
@app.get("/") @app.get("/")
@@ -303,7 +309,7 @@ def read_root():
@app.get("/items/{item_id}") @app.get("/items/{item_id}")
def read_item(item_id: int, q: str | None = None): def read_item(item_id: int, q: Union[str, None] = None):
return {"item_id": item_id, "q": q} return {"item_id": item_id, "q": q}

View File

@@ -36,6 +36,7 @@ hide:
### Internal ### Internal
* 🔧 Ensure that an edit to `uv.lock` gets the `internal` label. PR [#14759](https://github.com/fastapi/fastapi/pull/14759) by [@svlandeg](https://github.com/svlandeg).
* 🔧 Update sponsors: remove Requestly. PR [#14735](https://github.com/fastapi/fastapi/pull/14735) by [@tiangolo](https://github.com/tiangolo). * 🔧 Update sponsors: remove Requestly. PR [#14735](https://github.com/fastapi/fastapi/pull/14735) by [@tiangolo](https://github.com/tiangolo).
* 🔧 Update sponsors, LambdaTest changes to TestMu AI. PR [#14734](https://github.com/fastapi/fastapi/pull/14734) by [@tiangolo](https://github.com/tiangolo). * 🔧 Update sponsors, LambdaTest changes to TestMu AI. PR [#14734](https://github.com/fastapi/fastapi/pull/14734) by [@tiangolo](https://github.com/tiangolo).
* ⬆ Bump actions/cache from 4 to 5. PR [#14511](https://github.com/fastapi/fastapi/pull/14511) by [@dependabot[bot]](https://github.com/apps/dependabot). * ⬆ Bump actions/cache from 4 to 5. PR [#14511](https://github.com/fastapi/fastapi/pull/14511) by [@dependabot[bot]](https://github.com/apps/dependabot).

View File

@@ -102,16 +102,15 @@ Of course, you can also declare additional query parameters whenever you need, a
As, by default, singular values are interpreted as query parameters, you don't have to explicitly add a `Query`, you can just do: As, by default, singular values are interpreted as query parameters, you don't have to explicitly add a `Query`, you can just do:
```Python
q: str | None = None
```
Or in Python 3.9:
```Python ```Python
q: Union[str, None] = None q: Union[str, None] = None
``` ```
Or in Python 3.10 and above:
```Python
q: str | None = None
```
For example: For example:

View File

@@ -54,6 +54,14 @@ def get_swagger_ui_html(
""" """
), ),
] = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui-bundle.js", ] = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui-bundle.js",
swagger_extra_js_urls: Annotated[
Optional[list[str]],
Doc(
"""
The URLs of additional JavaScript files to include.
"""
),
] = None,
swagger_css_url: Annotated[ swagger_css_url: Annotated[
str, str,
Doc( Doc(
@@ -98,6 +106,14 @@ def get_swagger_ui_html(
""" """
), ),
] = None, ] = None,
swagger_extra_presets: Annotated[
Optional[list[str]],
Doc(
"""
Extra presets to add to Swagger UI.
"""
),
] = None,
) -> HTMLResponse: ) -> HTMLResponse:
""" """
Generate and return the HTML that loads Swagger UI for the interactive Generate and return the HTML that loads Swagger UI for the interactive
@@ -114,6 +130,13 @@ def get_swagger_ui_html(
if swagger_ui_parameters: if swagger_ui_parameters:
current_swagger_ui_parameters.update(swagger_ui_parameters) current_swagger_ui_parameters.update(swagger_ui_parameters)
js_urls = [swagger_js_url]
if swagger_extra_js_urls:
js_urls.extend(swagger_extra_js_urls)
scripts_str = "\n ".join(
f'<script src="{js_url}"></script>' for js_url in js_urls
)
html = f""" html = f"""
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
@@ -125,7 +148,7 @@ def get_swagger_ui_html(
<body> <body>
<div id="swagger-ui"> <div id="swagger-ui">
</div> </div>
<script src="{swagger_js_url}"></script> {scripts_str}
<!-- `SwaggerUIBundle` is now available on the page --> <!-- `SwaggerUIBundle` is now available on the page -->
<script> <script>
const ui = SwaggerUIBundle({{ const ui = SwaggerUIBundle({{
@@ -138,12 +161,18 @@ def get_swagger_ui_html(
if oauth2_redirect_url: if oauth2_redirect_url:
html += f"oauth2RedirectUrl: window.location.origin + '{oauth2_redirect_url}'," html += f"oauth2RedirectUrl: window.location.origin + '{oauth2_redirect_url}',"
html += """ presets = ["SwaggerUIBundle.presets.apis"]
if swagger_extra_presets:
presets.extend(swagger_extra_presets)
presets_str = ",\n ".join(presets)
html += f"""
presets: [ presets: [
SwaggerUIBundle.presets.apis, {presets_str},
SwaggerUIBundle.SwaggerUIStandalonePreset
], ],
})""" """
html += " })"
if init_oauth: if init_oauth:
html += f""" html += f"""

View File

@@ -18,9 +18,6 @@ def test_swagger_ui():
assert "SwaggerUIBundle.presets.apis," in response.text, ( assert "SwaggerUIBundle.presets.apis," in response.text, (
"default configs should be preserved" "default configs should be preserved"
) )
assert "SwaggerUIBundle.SwaggerUIStandalonePreset" in response.text, (
"default configs should be preserved"
)
assert '"layout": "BaseLayout",' in response.text, ( assert '"layout": "BaseLayout",' in response.text, (
"default configs should be preserved" "default configs should be preserved"
) )

View File

@@ -21,9 +21,6 @@ def test_swagger_ui():
assert "SwaggerUIBundle.presets.apis," in response.text, ( assert "SwaggerUIBundle.presets.apis," in response.text, (
"default configs should be preserved" "default configs should be preserved"
) )
assert "SwaggerUIBundle.SwaggerUIStandalonePreset" in response.text, (
"default configs should be preserved"
)
assert '"layout": "BaseLayout",' in response.text, ( assert '"layout": "BaseLayout",' in response.text, (
"default configs should be preserved" "default configs should be preserved"
) )

View File

@@ -24,9 +24,6 @@ def test_swagger_ui():
assert "SwaggerUIBundle.presets.apis," in response.text, ( assert "SwaggerUIBundle.presets.apis," in response.text, (
"default configs should be preserved" "default configs should be preserved"
) )
assert "SwaggerUIBundle.SwaggerUIStandalonePreset" in response.text, (
"default configs should be preserved"
)
assert '"layout": "BaseLayout",' in response.text, ( assert '"layout": "BaseLayout",' in response.text, (
"default configs should be preserved" "default configs should be preserved"
) )