iT邦幫忙

2024 iThome 鐵人賽

DAY 25
0
Python

眾裏尋它:Python表格利器Great Tables系列 第 25

[Day25] - 如何與FastAPI整合 - 靜態表格

  • 分享至 

  • xImage
  •  

今天我們來說明如何將gt表格呈現於FastAPI app中。

本日程式碼將存在main.py檔中,內容參考自官方Solar Zenith Angles範例

步驟1:建立FastAPI instance

建立一個名為appFastAPI instance。

from functools import cache

import polars as pl
from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
from great_tables import GT, html
from great_tables.data import sza

app = FastAPI()

步驟2:建立回傳Polars DataFrame的get_sza()函數

get_sza()會返回一個Polars DataFrame,是由gt提供的sza Pandas DataFrame轉換而來。由於DataFrame於此app中不會變動,所以可以將@cache裝飾在get_sza()上。

@cache
def get_sza():
    return pl.from_pandas(sza)

步驟3:建立回傳表格的index()函數

@app.get("/", response_class=HTMLResponse)
async def index(request: Request):
    sza_pivot = (
        get_sza()
        .filter((pl.col("latitude") == "20") & (pl.col("tst") <= "1200"))
        .select(pl.col("*").exclude("latitude"))
        .drop_nulls()
        .pivot(values="sza", index="month", on="tst", sort_columns=True)
    )

    sza_gt = (
        GT(sza_pivot, rowname_col="month")
        .data_color(
            domain=[90, 0],
            palette=["rebeccapurple", "white", "orange"],
            na_color="white",
        )
        .tab_header(
            title="Solar Zenith Angles from 05:30 to 12:00",
            subtitle=html("Average monthly values at latitude of 20&deg;N."),
        )
        .sub_missing(missing_text="")
    )

    context = {"sza_gt": sza_gt.as_raw_html()}

    return templates.TemplateResponse(
        request=request, name="index.html", context=context
    )

程式分段說明如下:

  • 使用app.get("/")定義index()為使用者以HTTP GET訪問「"/"」時使用的函數。其response_class指定為HTMLResponse,將有助於其docs了解回傳型態為text/html
  • sza_pivot為Polars DataFrame的處理過程;sza_gt變數則為表格的製作過程。
  • context = {"sza_gt": sza_gt.as_raw_html()}為整合的關鍵。我們使用GT.as_raw_html()來生成HTML,並置於context中。
  • 使用templates.TemplateResponse來回傳index.htmlcontext可以視為製作index.html所需要的額外資訊。

步驟4:建立index.html

在與main.py的同一階層建立一個templates資料夾,並於其中建立index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>FastAPI-GT Website</title>
  </head>
  <body>
    <main>
        <h1 style="text-align:center">Great Tables shown in FastAPI</h1>  
    </main>
    <div>
        {{ sza_gt | safe }}
    </div>
  </body>
</html>

index.html中,我們可以使用Jinja語法{{ sza_gt }}來取得於context所傳入的值。但請注意這邊必須使用| safe這個filter來確認此HTML是安全的,如此才可於瀏覽器中順利呈現gt表格。

步驟5:啟動Uvicorn server

於命令列中執行下列指令:

uvicorn main:app --reload

接著打開瀏覽器前往預設網址,如http://127.0.0.1:8000/,就可以見到下面這個漂亮的表格:

table

其它參考作法

如果覺得每次都需要記得呼叫GT.as_raw_html()很麻煩的話,可以考慮使用裝飾器來幫忙呼叫。請留意,此時裝飾器需考慮其裝飾的函數是否為async。

def gt2fastapi(func=None):
    """
    https://pybit.es/articles/decorator-optional-argument/
    """

    def _get_template_response(resp):
        context = resp.context
        request = context.pop("request")
        name = resp.template.name
        new_context = {}
        for key, value in context.items():
            if isinstance(value, GT):
                value = value.as_raw_html()
            new_context[key] = value
        return templates.TemplateResponse(
            request=request, name=name, context=new_context
        )

    if func is None:
        return partial(gt2fastapi)

    @wraps(func)
    async def async_wrapper(*args, **kwargs):
        resp = await func(*args, **kwargs)
        return _get_template_response(resp)

    @wraps(func)
    def wrapper(*args, **kwargs):
        resp = func(*args, **kwargs)
        return _get_template_response(resp)

    return async_wrapper if inspect.iscoroutinefunction(func) else wrapper

gt-fastapimain2.py為使用@gt2fastapi的一個範例,雖然技術上可行,但我不確定這是否是個更好的方法。

如果對裝飾器這個概念仍然不是那麼清楚的朋友,可以參考我於2023年鐵人賽的系列文 ── Python十翼:與未來的自己對話,其中有詳細的說明。

Code

本日所有程式碼可參考gt-fastapi repo。


上一篇
[Day24] - 如何與Streamlit整合 - 靜態表格
下一篇
[Day26] - 如何與Django整合 - 靜態表格
系列文
眾裏尋它:Python表格利器Great Tables30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言