mirror of https://github.com/tiangolo/fastapi.git
Merge 31fe59da9c into 272204c0c7
This commit is contained in:
commit
6b3b0ab875
|
|
@ -1,12 +1,13 @@
|
|||
from fastapi import FastAPI
|
||||
from fastapi.openapi.docs import (
|
||||
get_redoc_html,
|
||||
get_stoplight_elements_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 = FastAPI(docs_url=None, redoc_url=None, stoplight_elements_url=None)
|
||||
|
||||
app.mount("/static", StaticFiles(directory="static"), name="static")
|
||||
|
||||
|
|
@ -36,6 +37,16 @@ async def redoc_html():
|
|||
)
|
||||
|
||||
|
||||
@app.get("/elements", include_in_schema=False)
|
||||
async def elements_html():
|
||||
return get_stoplight_elements_html(
|
||||
openapi_url=app.openapi_url,
|
||||
title=app.title + " - Elements",
|
||||
stoplight_elements_js_url="/static/web-components.min.js",
|
||||
stoplight_elements_css_url="/static/styles.min.css",
|
||||
)
|
||||
|
||||
|
||||
@app.get("/users/{username}")
|
||||
async def read_user(username: str):
|
||||
return {"message": f"Hello {username}"}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
from fastapi import FastAPI
|
||||
from fastapi.openapi.docs import (
|
||||
LayoutOptions,
|
||||
RouterOptions,
|
||||
TryItCredentialPolicyOptions,
|
||||
get_stoplight_elements_html,
|
||||
)
|
||||
|
||||
app = FastAPI(stoplight_elements_url=None)
|
||||
|
||||
|
||||
@app.get("/elements", include_in_schema=False)
|
||||
async def elements_html():
|
||||
return get_stoplight_elements_html(
|
||||
openapi_url=app.openapi_url,
|
||||
title=app.title + " - Elements",
|
||||
stoplight_elements_js_url="https://unpkg.com/@stoplight/elements/web-components.min.js",
|
||||
stoplight_elements_css_url="https://unpkg.com/@stoplight/elements/styles.min.css",
|
||||
stoplight_elements_favicon_url="https://fastapi.tiangolo.com/img/favicon.png",
|
||||
api_description_document="",
|
||||
base_path="",
|
||||
hide_internal=False,
|
||||
hide_try_it=False,
|
||||
try_it_cors_proxy="",
|
||||
try_it_credential_policy=TryItCredentialPolicyOptions.OMIT,
|
||||
layout=LayoutOptions.SIDEBAR,
|
||||
logo="",
|
||||
router=RouterOptions.HISTORY,
|
||||
)
|
||||
|
||||
|
||||
@app.get("/users/{username}")
|
||||
async def read_user(username: str):
|
||||
return {"message": f"Hello {username}"}
|
||||
|
|
@ -26,6 +26,7 @@ from fastapi.logger import logger
|
|||
from fastapi.middleware.asyncexitstack import AsyncExitStackMiddleware
|
||||
from fastapi.openapi.docs import (
|
||||
get_redoc_html,
|
||||
get_stoplight_elements_html,
|
||||
get_swagger_ui_html,
|
||||
get_swagger_ui_oauth2_redirect_html,
|
||||
)
|
||||
|
|
@ -466,6 +467,30 @@ class FastAPI(Starlette):
|
|||
"""
|
||||
),
|
||||
] = "/docs/oauth2-redirect",
|
||||
stoplight_elements_url: Annotated[
|
||||
Optional[str],
|
||||
Doc(
|
||||
"""
|
||||
The path to the alternative automatic interactive API documentation
|
||||
provided by Stoplight Elements.
|
||||
|
||||
The default URL is `/elements`. You can disable it by setting it to `None`.
|
||||
|
||||
If `openapi_url` is set to `None`, this will be automatically disabled.
|
||||
|
||||
Read more in the
|
||||
[FastAPI docs for Metadata and Docs URLs](https://fastapi.tiangolo.com/tutorial/metadata/#docs-urls).
|
||||
|
||||
**Example**
|
||||
|
||||
```python
|
||||
from fastapi import FastAPI
|
||||
|
||||
app = FastAPI(docs_url="/documentation", stoplight_elements_url="elementsdocs")
|
||||
```
|
||||
"""
|
||||
),
|
||||
] = "/elements",
|
||||
swagger_ui_init_oauth: Annotated[
|
||||
Optional[Dict[str, Any]],
|
||||
Doc(
|
||||
|
|
@ -869,6 +894,7 @@ class FastAPI(Starlette):
|
|||
self.docs_url = docs_url
|
||||
self.redoc_url = redoc_url
|
||||
self.swagger_ui_oauth2_redirect_url = swagger_ui_oauth2_redirect_url
|
||||
self.stoplight_elements_url = stoplight_elements_url
|
||||
self.swagger_ui_init_oauth = swagger_ui_init_oauth
|
||||
self.swagger_ui_parameters = swagger_ui_parameters
|
||||
self.servers = servers or []
|
||||
|
|
@ -1133,6 +1159,21 @@ class FastAPI(Starlette):
|
|||
|
||||
self.add_route(self.redoc_url, redoc_html, include_in_schema=False)
|
||||
|
||||
if self.openapi_url and self.stoplight_elements_url:
|
||||
|
||||
async def stoplight_elements_html(req: Request) -> HTMLResponse:
|
||||
root_path = req.scope.get("root_path", "").rstrip("/")
|
||||
openapi_url = root_path + self.openapi_url
|
||||
return get_stoplight_elements_html(
|
||||
openapi_url=openapi_url, title=self.title + " - Stoplight Elements"
|
||||
)
|
||||
|
||||
self.add_route(
|
||||
self.stoplight_elements_url,
|
||||
stoplight_elements_html,
|
||||
include_in_schema=False,
|
||||
)
|
||||
|
||||
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
|
||||
if self.root_path:
|
||||
scope["root_path"] = self.root_path
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import json
|
||||
from enum import Enum
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from annotated_doc import Doc
|
||||
|
|
@ -343,3 +344,70 @@ def get_swagger_ui_oauth2_redirect_html() -> HTMLResponse:
|
|||
</html>
|
||||
"""
|
||||
return HTMLResponse(content=html)
|
||||
|
||||
|
||||
class TryItCredentialPolicyOptions(Enum):
|
||||
OMIT = "omit"
|
||||
include = "include"
|
||||
SAME_ORIGIN = "same-origin"
|
||||
|
||||
|
||||
class LayoutOptions(Enum):
|
||||
SIDEBAR = "sidebar"
|
||||
STACKED = "stacked"
|
||||
|
||||
|
||||
class RouterOptions(Enum):
|
||||
HISTORY = "history"
|
||||
HASH = "hash"
|
||||
MEMORY = "memory"
|
||||
STATIC = "static"
|
||||
|
||||
|
||||
def get_stoplight_elements_html(
|
||||
*,
|
||||
openapi_url: str,
|
||||
title: str,
|
||||
stoplight_elements_js_url: str = "https://unpkg.com/@stoplight/elements/web-components.min.js",
|
||||
stoplight_elements_css_url: str = "https://unpkg.com/@stoplight/elements/styles.min.css",
|
||||
stoplight_elements_favicon_url: str = "https://fastapi.tiangolo.com/img/favicon.png",
|
||||
api_description_document: str = "",
|
||||
base_path: str = "",
|
||||
hide_internal: bool = False,
|
||||
hide_try_it: bool = False,
|
||||
try_it_cors_proxy: str = "",
|
||||
try_it_credential_policy: TryItCredentialPolicyOptions = TryItCredentialPolicyOptions.OMIT,
|
||||
layout: LayoutOptions = LayoutOptions.SIDEBAR,
|
||||
logo: str = "",
|
||||
router: RouterOptions = RouterOptions.HISTORY,
|
||||
) -> HTMLResponse:
|
||||
html = f"""
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<title>{title}</title>
|
||||
<link rel="shortcut icon" href="{stoplight_elements_favicon_url}">
|
||||
<script src="{stoplight_elements_js_url}"></script>
|
||||
<link rel="stylesheet" href="{stoplight_elements_css_url}">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<elements-api
|
||||
{f'apiDescriptionUrl="{openapi_url}"' if openapi_url != "" else ""}
|
||||
{f'apiDescriptionDocument="{api_description_document}"' if api_description_document != "" else ""}
|
||||
{f'basePath="{base_path}"' if base_path != "" else ""}
|
||||
{'hideInternal="true"' if hide_internal is True else ""}
|
||||
{'hideTryIt="true"' if hide_try_it is True else ""}
|
||||
{f'tryItCorsProxy="{try_it_cors_proxy}"' if try_it_cors_proxy != "" else ""}
|
||||
tryItCredentialPolicy="{try_it_credential_policy.value}"
|
||||
layout="{layout.value}"
|
||||
{f'logo="{logo}"' if logo != "" else ""}
|
||||
router="{router.value}"
|
||||
/>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
return HTMLResponse(html)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
app = FastAPI(title="Example App")
|
||||
|
||||
|
||||
@app.get("/a/b")
|
||||
async def get_a_and_b():
|
||||
return {"a": "b"}
|
||||
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
def test_elements_uit():
|
||||
response = client.get("/elements")
|
||||
assert response.status_code == 200, response.text
|
||||
print(response.text)
|
||||
assert app.title in response.text
|
||||
assert "Stoplight" in response.text
|
||||
|
||||
|
||||
def test_response():
|
||||
response = client.get("/a/b")
|
||||
assert response.json() == {"a": "b"}
|
||||
|
|
@ -36,6 +36,13 @@ def test_redoc_html(client: TestClient):
|
|||
assert "/static/redoc.standalone.js" in response.text
|
||||
|
||||
|
||||
def test_elements_html(client: TestClient):
|
||||
response = client.get("/elements")
|
||||
assert response.status_code == 200, response.text
|
||||
assert "/static/web-components.min.js" in response.text
|
||||
assert "/static/styles.min.css" in response.text
|
||||
|
||||
|
||||
def test_api(client: TestClient):
|
||||
response = client.get("/users/john")
|
||||
assert response.status_code == 200, response.text
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
from fastapi.testclient import TestClient
|
||||
|
||||
from docs_src.extending_openapi.tutorial006 import app
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
def test_swagger_ui():
|
||||
response = client.get("/elements")
|
||||
assert response.status_code == 200, response.text
|
||||
assert 'router="history"' in response.text
|
||||
assert 'layout="sidebar"' in response.text
|
||||
assert 'tryItCredentialPolicy="omit"' in response.text
|
||||
|
||||
|
||||
def test_get_users():
|
||||
response = client.get("/users/foo")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {"message": "Hello foo"}
|
||||
Loading…
Reference in New Issue