今天我們來說明如何將gt
表格呈現於FastAPI app中。
本日程式碼將存在main.py檔中,內容參考自官方Solar Zenith Angles範例。
FastAPI
instance建立一個名為app
的FastAPI
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()
get_sza()
函數get_sza()
會返回一個Polars DataFrame,是由gt
提供的sza
Pandas DataFrame轉換而來。由於DataFrame於此app中不會變動,所以可以將@cache裝飾在get_sza()
上。
@cache
def get_sza():
return pl.from_pandas(sza)
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°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.html
。context
可以視為製作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
表格。
於命令列中執行下列指令:
uvicorn main:app --reload
接著打開瀏覽器前往預設網址,如http://127.0.0.1:8000/,就可以見到下面這個漂亮的表格:
如果覺得每次都需要記得呼叫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-fastapi的main2.py為使用@gt2fastapi
的一個範例,雖然技術上可行,但我不確定這是否是個更好的方法。
如果對裝飾器這個概念仍然不是那麼清楚的朋友,可以參考我於2023年鐵人賽的系列文 ── Python十翼:與未來的自己對話,其中有詳細的說明。
本日所有程式碼可參考gt-fastapi repo。