diff --git a/docs/Tutorial/UI_Templates.md b/docs/Tutorial/UI_Templates.md
new file mode 100644
index 00000000..4b30de3a
--- /dev/null
+++ b/docs/Tutorial/UI_Templates.md
@@ -0,0 +1,91 @@
+Flask OpenAPI3 supports [Swagger](https://github.com/swagger-api/swagger-ui), [Redoc](https://github.com/Redocly/redoc)
+and [RapiDoc](https://github.com/rapi-doc/RapiDoc) templates by default.
+
+You can customize templates use `ui_templates` in initializing OpenAPI.
+
+```python
+ui_templates = {
+ "swagger": swagger_html_string,
+ "rapipdf": rapipdf_html_string
+}
+
+app = OpenAPI(__name__, info=info, ui_templates=ui_templates)
+```
+
+In the above example, `swagger` will overwrite the original template and `rapipdf` is a new template.
+
+You can do anything with `swagger_html_string`, `rapipdf_html_string` or `any_html_string`.
+
+**swagger_html_string:**
+
+```html hl_lines="5 32"
+
+
+
+
+ Custom Title
+
+
+
+
+
+
+
+
+
+
+```
+
+**rapipdf_html_string:**
+
+```html hl_lines="9"
+
+
+
+
+
+
+
+
+
+```
+
+!!! info
+
+ `api_doc_url` is a necessary parameter for rendering template, so you must define it in your template.
\ No newline at end of file
diff --git a/examples/custom_ui_templates_demo.py b/examples/custom_ui_templates_demo.py
new file mode 100644
index 00000000..7e3530c6
--- /dev/null
+++ b/examples/custom_ui_templates_demo.py
@@ -0,0 +1,105 @@
+# -*- coding: utf-8 -*-
+# @Author : llc
+# @Time : 2023/2/3 15:14
+from pydantic import BaseModel
+
+from flask_openapi3 import Info, Tag
+from flask_openapi3 import OpenAPI
+
+info = Info(title="book API", version="1.0.0")
+swagger_html_string = """
+
+
+
+
+ Custom Title
+
+
+
+
+
+
+
+
+
+
+"""
+
+rapipdf_html_string = """
+
+
+
+
+
+
+
+
+
+"""
+ui_templates = {
+ "swagger": swagger_html_string,
+ "rapipdf": rapipdf_html_string
+}
+app = OpenAPI(__name__, info=info, ui_templates=ui_templates)
+
+book_tag = Tag(name='book', description='Some Book')
+
+
+class BookQuery(BaseModel):
+ age: int
+ author: str
+
+
+@app.get('/book', tags=[book_tag])
+def get_book(query: BookQuery):
+ """get books
+ get all books
+ """
+ return {
+ "code": 0,
+ "message": "ok",
+ "data": [
+ {"bid": 1, "age": query.age, "author": query.author},
+ {"bid": 2, "age": query.age, "author": query.author}
+ ]
+ }
+
+
+if __name__ == '__main__':
+ app.run(debug=True)
diff --git a/flask_openapi3/openapi.py b/flask_openapi3/openapi.py
index 60b4acec..43854e2e 100644
--- a/flask_openapi3/openapi.py
+++ b/flask_openapi3/openapi.py
@@ -7,7 +7,7 @@
from copy import deepcopy
from typing import Optional, List, Dict, Union, Any, Type, Callable, Tuple
-from flask import Flask, Blueprint, render_template
+from flask import Flask, Blueprint, render_template, render_template_string
from pydantic import BaseModel
from .blueprint import APIBlueprint
@@ -39,6 +39,7 @@ def __init__(
swagger_url: str = "/swagger",
redoc_url: str = "/redoc",
rapidoc_url: str = "/rapidoc",
+ ui_templates: Optional[Dict[str, str]] = None,
servers: Optional[List[Server]] = None,
external_docs: Optional[ExternalDocumentation] = None,
**kwargs: Any
@@ -64,6 +65,7 @@ def __init__(
swagger_url: The Swagger UI documentation. Defaults to `/swagger`.
redoc_url: The Redoc UI documentation. Defaults to `/redoc`.
rapidoc_url: The RapiDoc UI documentation. Defaults to `/rapidoc`.
+ ui_templates: Custom UI templates, which used to overwrite or add UI documents.
servers: An array of Server Objects, which provide connectivity information to a target server.
external_docs: Allows referencing an external resource for extended documentation.
See: https://spec.openapis.org/oas/v3.0.3#external-documentation-object
@@ -92,11 +94,14 @@ def __init__(
if not isinstance(oauth_config, OAuthConfig):
raise TypeError("`initOAuth` must be `OAuthConfig`")
self.oauth_config = oauth_config
- if doc_ui:
- self._init_doc()
self.doc_expansion = doc_expansion
+ if ui_templates is None:
+ ui_templates = dict()
+ self.ui_templates = ui_templates
self.severs = servers
self.external_docs = external_docs
+ if doc_ui:
+ self._init_doc()
# add openapi command
self.cli.add_command(openapi_command)
@@ -120,32 +125,46 @@ def _init_doc(self) -> None:
endpoint="api_doc",
view_func=lambda: self.api_doc
)
- blueprint.add_url_rule(
- rule=self.swagger_url,
- endpoint="swagger",
- view_func=lambda: render_template(
- "swagger.html",
- api_doc_url=self.api_doc_url.lstrip("/"),
- doc_expansion=self.doc_expansion,
- oauth_config=self.oauth_config.dict() if self.oauth_config else None
+ # iter ui_templates
+ for key, value in self.ui_templates.items():
+ blueprint.add_url_rule(
+ rule=f"/{key}",
+ endpoint=key,
+ # pass default value to source
+ view_func=lambda source=value: render_template_string(
+ source,
+ api_doc_url=self.api_doc_url.lstrip("/")
+ )
)
- )
- blueprint.add_url_rule(
- rule=self.redoc_url,
- endpoint="redoc",
- view_func=lambda: render_template(
- "redoc.html",
- api_doc_url=self.api_doc_url.lstrip("/")
+ if self.swagger_url.strip("/") not in self.ui_templates.keys():
+ blueprint.add_url_rule(
+ rule=self.swagger_url,
+ endpoint="swagger",
+ view_func=lambda: render_template(
+ "swagger.html",
+ api_doc_url=self.api_doc_url.lstrip("/"),
+ doc_expansion=self.doc_expansion,
+ oauth_config=self.oauth_config.dict() if self.oauth_config else None
+ )
)
- )
- blueprint.add_url_rule(
- rule=self.rapidoc_url,
- endpoint="rapidoc",
- view_func=lambda: render_template(
- "rapidoc.html",
- api_doc_url=self.api_doc_url.lstrip("/")
+ if self.redoc_url.strip("/") not in self.ui_templates.keys():
+ blueprint.add_url_rule(
+ rule=self.redoc_url,
+ endpoint="redoc",
+ view_func=lambda: render_template(
+ "redoc.html",
+ api_doc_url=self.api_doc_url.lstrip("/")
+ )
+ )
+ if self.rapidoc_url.strip("/") not in self.ui_templates.keys():
+ blueprint.add_url_rule(
+ rule=self.rapidoc_url,
+ endpoint="rapidoc",
+ view_func=lambda: render_template(
+ "rapidoc.html",
+ api_doc_url=self.api_doc_url.lstrip("/")
+ )
)
- )
blueprint.add_url_rule(
rule="/",
endpoint="index",
diff --git a/mkdocs.yml b/mkdocs.yml
index 18c61ec4..b3d6f4fd 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -64,6 +64,7 @@ plugins:
Operation: 路由操作
Request: 请求
Response: 响应
+ UI Templates: 自定义模板
Example: 示例
API Reference: API 参考
LICENSE: 许可
@@ -85,6 +86,7 @@ nav:
- Operation: Tutorial/Operation.md
- Request: Tutorial/Request.md
- Response: Tutorial/Response.md
+ - UI Templates: Tutorial/UI_Templates.md
- JSON: Tutorial/JSON.md
- Example: Example.md
- API Reference:
diff --git a/tests/test_custom_ui_templates_demo.py b/tests/test_custom_ui_templates_demo.py
new file mode 100644
index 00000000..d482976a
--- /dev/null
+++ b/tests/test_custom_ui_templates_demo.py
@@ -0,0 +1,126 @@
+# -*- coding: utf-8 -*-
+# @Author : llc
+# @Time : 2023/2/3 15:14
+import pytest
+from pydantic import BaseModel
+
+from flask_openapi3 import Info, Tag
+from flask_openapi3 import OpenAPI
+
+info = Info(title="book API", version="1.0.0")
+swagger_html_string = """
+
+
+
+
+ Custom Title
+
+
+
+
+
+
+
+
+
+
+"""
+
+rapipdf_html_string = """
+
+
+
+
+
+
+
+
+
+"""
+ui_templates = {
+ "swagger": swagger_html_string,
+ "rapipdf": rapipdf_html_string
+}
+app = OpenAPI(__name__, info=info, ui_templates=ui_templates)
+app.config["TESTING"] = True
+book_tag = Tag(name='book', description='Some Book')
+
+
+class BookQuery(BaseModel):
+ age: int
+ author: str
+
+
+@app.get('/book', tags=[book_tag])
+def get_book(query: BookQuery):
+ """get books
+ get all books
+ """
+ return {
+ "code": 0,
+ "message": "ok",
+ "data": [
+ {"bid": 1, "age": query.age, "author": query.author},
+ {"bid": 2, "age": query.age, "author": query.author}
+ ]
+ }
+
+
+@pytest.fixture
+def client():
+ client = app.test_client()
+
+ return client
+
+
+def test_openapi(client):
+ resp = client.get("/openapi/openapi.json")
+ assert resp.status_code == 200
+ assert resp.json == app.api_doc
+
+
+def test_swagger(client):
+ resp = client.get("/openapi/swagger")
+ assert resp.status_code == 200
+ assert "Custom Title" in resp.text
+
+
+def test_rapipdf(client):
+ resp = client.get("/openapi/rapipdf")
+ assert resp.status_code == 200