如何将自定义的HTML内容添加到FastAPI Swagger UI文档中?

huangapple go评论57阅读模式
英文:

How to add custom HTML content to FastAPI Swagger UI docs?

问题

我需要在我的FastAPI应用程序的Swagger UI中添加自定义按钮。我找到了这个答案,它建议了一种很好的方法来添加自定义JavaScript到Swagger UI,还有来自FastAPI的这个文档。但是,这个解决方案只适用于添加自定义JavaScript代码。我尝试添加一些HTML代码来添加一个新的按钮,使用Swagger UI的授权按钮样式:

custom_html = '<div class="scheme-containerr"><section class="schemes wrapper block col-12"><div class="auth-wrapper"><button class="btn authorize"><span>Authorize Google</span><svg width="20" height="20"><use href="#unlocked" xlink:href="#unlocked"></use></svg></button></div></section></div>'
    
@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",
        custom_js_url=google_custom_button,
        custom_html=custom_html,
    )

如何在Swagger UI中添加自定义HTML(在这种情况下,像Swagger的Authorize按钮一样的按钮)以满足特定需求,使用FastAPI?

更新

如果我将自定义HTML添加到<div id="swagger-ui"></div>之外,我可以在Swagger UI中看到我的自定义按钮,如下所示:

如何将自定义的HTML内容添加到FastAPI Swagger UI文档中?

但我想要将我的按钮添加到原始的Authorize按钮所在的位置。

英文:

I need to add a custom button in Swagger UI of my FastAPI application. I found this answer which suggest a good solution to add custom javascript to Swagger UI along with this documentations from FastAPI. But this solution only works for adding custom javascript code. I tried to add some HTML code for adding a new button to it using the swagger UI Authorise button style:

custom_html = &#39;&lt;div class=&quot;scheme-containerr&quot;&gt;&lt;section class=&quot;schemes wrapper block col-12&quot;&gt;&lt;div class=&quot;auth-wrapper&quot;&gt;&lt;button class=&quot;btn authorize&quot;&gt;&lt;span&gt;Authorize Google&lt;/span&gt;&lt;svg width=&quot;20&quot; height=&quot;20&quot;&gt;&lt;use href=&quot;#unlocked&quot; xlink:href=&quot;#unlocked&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/button&gt;&lt;/div&gt;&lt;/section&gt;&lt;/div&gt;&#39;

@app.get(&quot;/docs&quot;, include_in_schema=False)
async def custom_swagger_ui_html():
    return get_swagger_ui_html(
        openapi_url=app.openapi_url,
        title=app.title + &quot; - Swagger UI&quot;,
        oauth2_redirect_url=app.swagger_ui_oauth2_redirect_url,
        swagger_js_url=&quot;/static/swagger-ui-bundle.js&quot;,
        swagger_css_url=&quot;/static/swagger-ui.css&quot;,
        custom_js_url=google_custom_button,
        custom_html=custom_html,
    )

def get_swagger_ui_html(
        *,
        ...
        custom_html: Optional[str] = None,
) -&gt; HTMLResponse:

    ...

    html = f&quot;&quot;&quot;
    &lt;!DOCTYPE html&gt;
    &lt;html&gt;
    &lt;head&gt;
    &lt;link type=&quot;text/css&quot; rel=&quot;stylesheet&quot; href=&quot;{swagger_css_url}&quot;&gt;
    &lt;link rel=&quot;shortcut icon&quot; href=&quot;{swagger_favicon_url}&quot;&gt;
    &lt;title&gt;{title}&lt;/title&gt;
    &lt;/head&gt;
    &lt;body&gt;
    &lt;div id=&quot;swagger-ui&quot;&gt;
    {custom_html if custom_html else &quot;&quot;}  # &lt;-- I added the HTML code here
    &lt;/div&gt;
    &quot;&quot;&quot;
    ....

But looks like whatever I put between &lt;div id=&quot;swagger-ui&quot;&gt;&lt;/div&gt; gets overwritten somehow and won't make it in the Swagger UI.

How to add custom HTML (in this case, buttons like Swagger's Authorise button) for specific needs in Swagger UI using FastAPI?

Update

If I add the custom HTML outside of the &lt;div id=&quot;swagger-ui&quot;&gt;&lt;/div&gt; I can see my custom button in Swagger UI like this:

如何将自定义的HTML内容添加到FastAPI Swagger UI文档中?

But I would like to add my button where the original Authorise button is.

答案1

得分: 0

你可以修改FastAPI的get_swagger_ui_html()函数,以注入一些自定义JavaScript代码,如@lunaa在这里所述,并通过custom_script.js以编程方式创建自定义HTML按钮。然而,由于Authorize按钮元素是在DOM/窗口加载之后创建的,似乎没有一种本地方式可以在其定义后运行您的JS代码,即使您使用Window.load事件来运行JavaScript代码。您需要等待该元素被创建,使用这里描述的方法,然后创建自定义按钮并将其添加到DOM中。

完整的工作示例

app.py

from fastapi import FastAPI
from fastapi import Depends
from fastapi.security import OpenIdConnect
from fastapi.staticfiles import StaticFiles
from fastapi.openapi.docs import (
    get_redoc_html,
    get_swagger_ui_oauth2_redirect_html,
)
from custom_swagger import get_swagger_ui_html

app = FastAPI(docs_url=None)
app.mount("/static", StaticFiles(directory="static"), name="static")
oidc_google = OpenIdConnect(openIdConnectUrl='https://accounts.google.com/.well-known/openid-configuration')

@app.get("/docs", include_in_schema=False)
async def custom_swagger_ui_html():
    return get_swagger_ui_html(
        openapi_url=app.openapi_url,
        title="My API",
        oauth2_redirect_url=app.swagger_ui_oauth2_redirect_url,
        custom_js_url="/static/custom_script.js",
    )

@app.get('/')
def main(token: str = Depends(oidc_google)):
    return "You are Authenticated"

custom_swagger.py

import json
from typing import Any, Dict, Optional
from fastapi.encoders import jsonable_encoder
from fastapi.openapi.docs import swagger_ui_default_parameters
from starlette.responses import HTMLResponse

def get_swagger_ui_html(
    *,
    openapi_url: str,
    title: str,
    swagger_js_url: str = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@4/swagger-ui-bundle.js",
    swagger_css_url: str = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@4/swagger-ui.css",
    swagger_favicon_url: str = "https://fastapi.tiangolo.com/img/favicon.png",
    oauth2_redirect_url: Optional[str] = None,
    init_oauth: Optional[Dict[str, Any]] = None,
    swagger_ui_parameters: Optional[Dict[str, Any]] = None,
    custom_js_url: Optional[str] = None,
) -> HTMLResponse:
    current_swagger_ui_parameters = swagger_ui_default_parameters.copy()
    if swagger_ui_parameters:
        current_swagger_ui_parameters.update(swagger_ui_parameters)

    html = """
    <!DOCTYPE html>
    <html>
    <head>
    <link type="text/css" rel="stylesheet" href="{swagger_css_url}">
    <link rel="shortcut icon" href="{swagger_favicon_url}">
    <title>{title}</title>
    </head>
    <body>
    <div id="swagger-ui">
    </div>
    """
    
    if custom_js_url:
        html += """
        <script src="{custom_js_url}"></script>
        """

    html += """
    <script src="{swagger_js_url}"></script>
    <!-- `SwaggerUIBundle` is now available on the page -->
    <script>
    const ui = SwaggerUIBundle({
        url: '{openapi_url}',
    """

    for key, value in current_swagger_ui_parameters.items():
        html += f"{json.dumps(key)}: {json.dumps(jsonable_encoder(value))},\n"

    if oauth2_redirect_url:
        html += f"oauth2RedirectUrl: window.location.origin + '{oauth2_redirect_url}',"

    html += """
    presets: [
        SwaggerUIBundle.presets.apis,
        SwaggerUIBundle.SwaggerUIStandalonePreset
        ],
    })
    """

    if init_oauth:
        html += """
        ui.initOAuth({json.dumps(jsonable_encoder(init_oauth))})
        """

    html += """
    </script>
    </body>
    </html>
    """
    return HTMLResponse(html)

static/custom_script.js

function waitForElm(selector) {
    return new Promise(resolve => {
        if (document.querySelector(selector)) {
            return resolve(document.querySelector(selector));
        }

        const observer = new MutationObserver(mutations => {
            if (document.querySelector(selector)) {
                resolve(document.querySelector(selector));
                observer.disconnect();
            }
        });

        observer.observe(document.body, {
            childList: true,
            subtree: true
        });
    });
}

waitForElm('.auth-wrapper').then((elm) => {
    var authWrapper = document.getElementsByClassName("auth-wrapper")[0];
    var btn = document.createElement("BUTTON");
    btn.innerHTML = "Click me";
    btn.id = "btn-id";
    btn.onclick = function() {
        alert("button is clicked");
    };
    authWrapper.append(btn);
});

替代通过JavaScript编程方式创建按钮,您可以使用JavaScript加载外部HTML文件,该文件将包含按钮的HTML代码以及您可能希望插入的任何其他元素。以下是示例:

static/custom_script.js

function waitForElm(selector) {
   // 与上述代码段相同
}

waitForElm('.auth-wrapper').then((elm) => {
   var authWrapper = document.getElementsByClassName("auth-wrapper")[0];
   fetch('/static/button.html')
      .then(response => response.text())
      .then(text => {
         const newDiv = document.createElement("div");
         newDiv.innerHTML = text;
         authWrapper.append(newDiv);
      });
});

static/button.html

<button onclick="alert('button is clicked');" class="btn authorize unlocked Google">
   <span>Authorize Google</span>
   <svg width="20" height="20">
      <use href="#unlocked" xlink:href="#unlocked"></use>
   </svg>
</button>

添加动态自定义内容

如果您想要添加一些动态内容,而不是静态JS/HTML文件内容,您可以将内容直接作为字符串传递给get_swagger_ui_html()函数,或者使用静态内容与动态变量的组合,这些变量可以使用Jinja2模板添加。以下是示例,演示了对先前提供的代码所需的更改——其他代码应保持与上述相同。下面示例中的动态变量是msg

示例

app.py

# ...
from jinja2 import Environment, FileSystemLoader

def get_template():
    env = Environment(loader=FileSystemLoader('./static'))
    template = env.get_template('custom_script.js')
    context = {'msg': 'button is clicked!

<details>
<summary>英文:</summary>

You could modify FastAPI&#39;s [`get_swagger_ui_html()`][1] function, in order to inject some custom JavaScript code, as described by @lunaa [here][2], and create the custom HTML button programmatically through the `custom_script.js`. However, since the `Authorize` button element is created after the DOM/Window is loaded&amp;mdash;and there doesn&#39;t seem to be a native way to run your JS code after is defined, even if you use [`Window.load`][3] event to run the JavaScript code&amp;mdash;and you need to add your custom button next to it, you could simply wait for that element to be created, using the approach described [here][4], and then create the custom button and add it to the DOM.

## Complete Working Example

**app.py**
```python
from fastapi import FastAPI
from fastapi import Depends
from fastapi.security import OpenIdConnect
from fastapi.staticfiles import StaticFiles
from fastapi.openapi.docs import (
    get_redoc_html,
    get_swagger_ui_oauth2_redirect_html,
)
from custom_swagger import get_swagger_ui_html


app = FastAPI(docs_url=None) 
app.mount(&quot;/static&quot;, StaticFiles(directory=&quot;static&quot;), name=&quot;static&quot;)
oidc_google = OpenIdConnect(openIdConnectUrl=&#39;https://accounts.google.com/.well-known/openid-configuration&#39;)


@app.get(&quot;/docs&quot;, include_in_schema=False)
async def custom_swagger_ui_html():
    return get_swagger_ui_html(
        openapi_url=app.openapi_url,
        title=&quot;My API&quot;,
        oauth2_redirect_url=app.swagger_ui_oauth2_redirect_url,
        #swagger_js_url=&quot;/static/swagger-ui-bundle.js&quot;,  # Optional
        #swagger_css_url=&quot;/static/swagger-ui.css&quot;,  # Optional
        #swagger_favicon_url=&quot;/static/favicon-32x32.png&quot;,  # Optional
        custom_js_url=&quot;/static/custom_script.js&quot;,
    )


@app.get(&#39;/&#39;)
def main(token: str = Depends(oidc_google)):
    return &quot;You are Authenticated&quot;

custom_swagger.py

import json
from typing import Any, Dict, Optional
from fastapi.encoders import jsonable_encoder
from fastapi.openapi.docs import swagger_ui_default_parameters
from starlette.responses import HTMLResponse

def get_swagger_ui_html(
    *,
    openapi_url: str,
    title: str,
    swagger_js_url: str = &quot;https://cdn.jsdelivr.net/npm/swagger-ui-dist@4/swagger-ui-bundle.js&quot;,
    swagger_css_url: str = &quot;https://cdn.jsdelivr.net/npm/swagger-ui-dist@4/swagger-ui.css&quot;,
    swagger_favicon_url: str = &quot;https://fastapi.tiangolo.com/img/favicon.png&quot;,
    oauth2_redirect_url: Optional[str] = None,
    init_oauth: Optional[Dict[str, Any]] = None,
    swagger_ui_parameters: Optional[Dict[str, Any]] = None,
    custom_js_url: Optional[str] = None,
) -&gt; HTMLResponse:
    current_swagger_ui_parameters = swagger_ui_default_parameters.copy()
    if swagger_ui_parameters:
        current_swagger_ui_parameters.update(swagger_ui_parameters)

    html = f&quot;&quot;&quot;
    &lt;!DOCTYPE html&gt;
    &lt;html&gt;
    &lt;head&gt;
    &lt;link type=&quot;text/css&quot; rel=&quot;stylesheet&quot; href=&quot;{swagger_css_url}&quot;&gt;
    &lt;link rel=&quot;shortcut icon&quot; href=&quot;{swagger_favicon_url}&quot;&gt;
    &lt;title&gt;{title}&lt;/title&gt;
    &lt;/head&gt;
    &lt;body&gt;
    &lt;div id=&quot;swagger-ui&quot;&gt;
    &lt;/div&gt;
    &quot;&quot;&quot;
    
    if custom_js_url:
        html += f&quot;&quot;&quot;
        &lt;script src=&quot;{custom_js_url}&quot;&gt;&lt;/script&gt;
        &quot;&quot;&quot;

    html += f&quot;&quot;&quot;
    &lt;script src=&quot;{swagger_js_url}&quot;&gt;&lt;/script&gt;
    &lt;!-- `SwaggerUIBundle` is now available on the page --&gt;
    &lt;script&gt;
    const ui = SwaggerUIBundle({{
        url: &#39;{openapi_url}&#39;,
    &quot;&quot;&quot;

    for key, value in current_swagger_ui_parameters.items():
        html += f&quot;{json.dumps(key)}: {json.dumps(jsonable_encoder(value))},\n&quot;

    if oauth2_redirect_url:
        html += f&quot;oauth2RedirectUrl: window.location.origin + &#39;{oauth2_redirect_url}&#39;,&quot;

    html += &quot;&quot;&quot;
    presets: [
        SwaggerUIBundle.presets.apis,
        SwaggerUIBundle.SwaggerUIStandalonePreset
        ],
    })&quot;&quot;&quot;

    if init_oauth:
        html += f&quot;&quot;&quot;
        ui.initOAuth({json.dumps(jsonable_encoder(init_oauth))})
        &quot;&quot;&quot;

    html += &quot;&quot;&quot;
    &lt;/script&gt;
    &lt;/body&gt;
    &lt;/html&gt;
    &quot;&quot;&quot;
    return HTMLResponse(html)

static/custom_script.js

function waitForElm(selector) {
	return new Promise(resolve =&gt; {
		if (document.querySelector(selector)) {
			return resolve(document.querySelector(selector));
		}

		const observer = new MutationObserver(mutations =&gt; {
			if (document.querySelector(selector)) {
				resolve(document.querySelector(selector));
				observer.disconnect();
			}
		});

		observer.observe(document.body, {
			childList: true,
			subtree: true
		});
	});
}

waitForElm(&#39;.auth-wrapper&#39;).then((elm) =&gt; {
	var authWrapper = document.getElementsByClassName(&quot;auth-wrapper&quot;)[0];
	var btn = document.createElement(&quot;BUTTON&quot;);
	btn.innerHTML = &quot;Click me&quot;;
	btn.id = &quot;btn-id&quot;;
	btn.onclick = function() {
		alert(&quot;button is clicked&quot;);
	};
	authWrapper.append(btn);
});

Instead of programmatically creating the button through JavaScript, you could load an external HTML file (using JavaScript), which would contain the HTML code for the button and any other elements you would possibly like to insert. Example below:

static/custom_script.js

function waitForElm(selector) {
   // same as in the previous code snippet
}

waitForElm(&#39;.auth-wrapper&#39;).then((elm) =&gt; {
   var authWrapper = document.getElementsByClassName(&quot;auth-wrapper&quot;)[0];
   fetch(&#39;/static/button.html&#39;)
      .then(response =&gt; response.text())
      .then(text =&gt; {
         const newDiv = document.createElement(&quot;div&quot;);
         newDiv.innerHTML = text;
         authWrapper.append(newDiv);
      });
});

static/button.html

&lt;button onclick=&quot;alert(&#39;button is clicked&#39;);&quot; class=&quot;btn authorize unlocked Google&quot;&gt;
   &lt;span&gt;Authorize Google&lt;/span&gt;
   &lt;svg width=&quot;20&quot; height=&quot;20&quot;&gt;
      &lt;use href=&quot;#unlocked&quot; xlink:href=&quot;#unlocked&quot;&gt;&lt;/use&gt;
   &lt;/svg&gt;
&lt;/button&gt;

Adding Dynamic Custom Content

In case you would like to add some dynamic content, instead of static JS/HTML file content, you could either pass the content directly as a string to the get_swagger_ui_html() function, or use a combination of static content with dynamic variables, which could be added using Jinja2 templates. Example is given below, demonstrating the changes to be made to the code provided earlier&mdash;rest of the code should remain the same as above. The dynamic variable in the exmaple below is msg.

Example

app.py

# ...
from jinja2 import Environment, FileSystemLoader

def get_template():
    env = Environment(loader=FileSystemLoader(&#39;./static&#39;))
    template = env.get_template(&#39;custom_script.js&#39;)
    context = {&#39;msg&#39;: &#39;button is clicked!&#39;}
    html = template.render(context)
    return html

@app.get(&quot;/docs&quot;, include_in_schema=False)
async def custom_swagger_ui_html():
    return get_swagger_ui_html(
        openapi_url=app.openapi_url,
        title=&quot;My API&quot;,
        oauth2_redirect_url=app.swagger_ui_oauth2_redirect_url,
        custom_js_content=get_template()
    )

custom_swagger.py

def get_swagger_ui_html(
    *,
    # ...
    custom_js_content: Optional[str] = None,
) -&gt; HTMLResponse:
    # ...
    
    if custom_js_content:
        html += f&quot;&quot;&quot;
        &lt;script&gt;{custom_js_content}&lt;/script&gt;
        &quot;&quot;&quot;
    # ...

static/custom_script.js

function waitForElm(selector) {
   // ...
}

waitForElm(&#39;.auth-wrapper&#39;).then((elm) =&gt; {
    var authWrapper = document.getElementsByClassName(&quot;auth-wrapper&quot;)[0];
    var btn = document.createElement(&quot;BUTTON&quot;);
    btn.innerHTML = `
	   &lt;span&gt;Authorize Google&lt;/span&gt;
	   &lt;svg width=&quot;20&quot; height=&quot;20&quot;&gt;
		  &lt;use href=&quot;#unlocked&quot; xlink:href=&quot;#unlocked&quot;&gt;&lt;/use&gt;
	   &lt;/svg&gt;
   `;
    btn.className = &quot;btn authorize unlocked Google&quot;;
    btn.onclick = function() {
        alert(&quot;{{msg}}&quot;);
    };
    authWrapper.append(btn);
});

or

static/custom_script.js

function waitForElm(selector) {
   // ...
}

waitForElm(&#39;.auth-wrapper&#39;).then((elm) =&gt; {
    var authWrapper = document.getElementsByClassName(&quot;auth-wrapper&quot;)[0];
    var html = `
	&lt;button onclick=&quot;alert(&#39;{{msg}}&#39;);&quot; class=&quot;btn authorize unlocked Google&quot;&gt;
	   &lt;span&gt;Authorize Google&lt;/span&gt;
	   &lt;svg width=&quot;20&quot; height=&quot;20&quot;&gt;
		  &lt;use href=&quot;#unlocked&quot; xlink:href=&quot;#unlocked&quot;&gt;&lt;/use&gt;
	   &lt;/svg&gt;
	&lt;/button&gt;
	`;
    var newDiv = document.createElement(&quot;div&quot;);
    newDiv.innerHTML = html;
    authWrapper.append(newDiv);
});

huangapple
  • 本文由 发表于 2023年6月5日 20:56:00
  • 转载请务必保留本文链接:https://go.coder-hub.com/76406637.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定