@Mikkkch

Как подключить свой UI к FastAPI?

Здравствуйте, недавно посоветовали FastAPI для создания своего API. В целях было подключить фронтенд и поэтому вместо DRF я выбрал именно этот фреймворк. Однако fapi предоставляет свой собственный пользовательский интерфейс(Swagger UI).
Все, что я нашел в исходниках:
import json
from typing import Optional

from fastapi.encoders import jsonable_encoder
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@3.30.0/swagger-ui-bundle.js",
    swagger_css_url: str = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@3.30.0/swagger-ui.css",
    swagger_favicon_url: str = "https://fastapi.tiangolo.com/img/favicon.png",
    oauth2_redirect_url: Optional[str] = None,
    init_oauth: Optional[dict] = None,
) -> HTMLResponse:

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

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

    html += """
        dom_id: '#swagger-ui',
        presets: [
        SwaggerUIBundle.presets.apis,
        SwaggerUIBundle.SwaggerUIStandalonePreset
        ],
        layout: "BaseLayout",
        deepLinking: true,
        showExtensions: true,
        showCommonExtensions: true
    })"""

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

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


def get_redoc_html(
    *,
    openapi_url: str,
    title: str,
    redoc_js_url: str = "https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js",
    redoc_favicon_url: str = "https://fastapi.tiangolo.com/img/favicon.png",
    with_google_fonts: bool = True,
) -> HTMLResponse:
    html = f"""
    <!DOCTYPE html>
    <html>
    <head>
    <title>{title}</title>
    <!-- needed for adaptive design -->
    <meta charset="utf-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    """
    if with_google_fonts:
        html += """
    <link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
    """
    html += f"""
    <link rel="shortcut icon" href="{redoc_favicon_url}">
    <!--
    ReDoc doesn't change outer page styles
    -->
    <style>
      body {{
        margin: 0;
        padding: 0;
      }}
    </style>
    </head>
    <body>
    <redoc spec-url="{openapi_url}"></redoc>
    <script src="{redoc_js_url}"> </script>
    </body>
    </html>
    """
    return HTMLResponse(html)


def get_swagger_ui_oauth2_redirect_html() -> HTMLResponse:
    html = """
    <!DOCTYPE html>
    <html lang="en-US">
    <body onload="run()">
    </body>
    </html>
    <script>
        'use strict';
        function run () {
            var oauth2 = window.opener.swaggerUIRedirectOauth2;
            var sentState = oauth2.state;
            var redirectUrl = oauth2.redirectUrl;
            var isValid, qp, arr;

            if (/code|token|error/.test(window.location.hash)) {
                qp = window.location.hash.substring(1);
            } else {
                qp = location.search.substring(1);
            }

            arr = qp.split("&")
            arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';})
            qp = qp ? JSON.parse('{' + arr.join() + '}',
                    function (key, value) {
                        return key === "" ? value : decodeURIComponent(value)
                    }
            ) : {}

            isValid = qp.state === sentState

            if ((
            oauth2.auth.schema.get("flow") === "accessCode"||
            oauth2.auth.schema.get("flow") === "authorizationCode"
            ) && !oauth2.auth.code) {
                if (!isValid) {
                    oauth2.errCb({
                        authId: oauth2.auth.name,
                        source: "auth",
                        level: "warning",
                        message: "Authorization may be unsafe, passed state was changed in server Passed state wasn't returned from auth server"
                    });
                }

                if (qp.code) {
                    delete oauth2.state;
                    oauth2.auth.code = qp.code;
                    oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
                } else {
                    let oauthErrorMsg
                    if (qp.error) {
                        oauthErrorMsg = "["+qp.error+"]: " +
                            (qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
                            (qp.error_uri ? "More info: "+qp.error_uri : "");
                    }

                    oauth2.errCb({
                        authId: oauth2.auth.name,
                        source: "auth",
                        level: "error",
                        message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server"
                    });
                }
            } else {
                oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl});
            }
            window.close();
        }
    </script>
        """
    return HTMLResponse(content=html)

Мне бы хотелось узнать следующее:
  1. Каким образом работает механизм генерации документации исходя из кода.
  2. Смогу ли я произвести аналогичные действия для того, чтобы подключить собственные шаблоны?

Я думал можно реализовать это путем рендеринга шаблона и передавать туда аргументы. Что то вроде такого(псевдокод):
return HTMLResponse(template, {"routes:" app.routes})

А в шаблоне что-то вроде
{% for route in routes %}:
<разметка, отображающая информацию о методе апликейшена и т.д>
{% endfor %}
  • Вопрос задан
  • 1441 просмотр
Решения вопроса 1
@Zanak
Давайте, для начала, поймем ваши цели. Вы хотите создать свой API, пообщаться с существующим, или приделать свой UI для известного API?

Для создания своего API указанный инструмент, FastAPI, вполне подходит. Не все фреймворки умеют формировать/отдавать swagger спеку, но некоторые это позволяют. Собственно, для создания собственного сервиса и API, по которому он будет отдавать данные, swagger непременным условием не является. Спека для swagger пишется первой, когда у вас есть генератор, который может построить заготовку проекта на нужном вам языке. Во всех остальных случаях, она, или пишется вручную, или формируется по написанному коду, с использованием соответствующих инструментов.

Пообщаться с существующим API можно, помимо, например, Postman-а и подобных инструментов, с помощью SwaggerUI, который написан на JS и может быть размещен, например, на вашем сервере. Достаточно подсунуть ему спеку интересующего вас сервера и он, в более или менее удобной форме, позволит увидеть доступные вызовы, отправить запрос, получить ответ, ну и прочитать описание каждого вызова, если автор спеки об этом позаботился. Задача swagger - это представить машинно понятное описание api, со всеми используемыми структурами запросов и ответов, чтобы клиентский код сумел его разобрать, отправить запрос, обработать ответ, и возможно, по крайней мере теоретически, сформировать форму, чтобы пользователь мог ввести свои данные, например, для отправки их в БД. Ни для чего больше, как машинно понятное описание API swagger не предназначен.

Ну и уже должно быть понятно, что SwaggerUI не предназначен для создания собственных клиентов к API. Это скорее интерактивная документация к сервису, который описан в спеке.

В выборе средств реализации пользовательского UI вы не ограничены ни чем. Мобильное приложение, десктоп, веб с шаблонами a-la django, spa приложение, все к вашим услугам. На реализацию серверной части это ни как не завязано.
Ответ написан
Комментировать
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы