iT邦幫忙

2025 iThome 鐵人賽

DAY 25
0
AI & Data

AI 營養師 + Web3 數位健康護照系列 第 25

Day25. Flask 與資料庫整合 Ep4:用 TDD(測試驅動開發)+ SQLite 完成AI營養顧問的資料庫功能 Part-2

  • 分享至 

  • xImage
  •  

今天要進入 TDD 流程的第二階段:「綠燈與重構」。

第一階段請參考:
Day24. Flask 與資料庫整合 Ep3:用 TDD(測試驅動開發)完成AI營養顧問的資料庫功能 Part-1


一、最小實作(models.py

1. 匯入必要模組

# 為了支援 timezone-aware 時間與 JSON 序列化,需加入 `datetime.timezone` 與 `json` 模組
from datetime import datetime, timezone
import json

2. 改為 timezone-aware created_at

# 為了避免時間差的問題,用 datetime.now(timezone.utc) 明確指定要求獲取的是 UTC 時區的當前時間
created_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc))

3. 新增 nutrients 屬性(Getter / Setter)

此屬性是 nutrients_json 欄位的包裝層,用以自動處理 JSON & Python 結構的轉換。
設計重點:

  • 若給定字串為合法 JSON,則直接儲存;
  • 若給定為 Python 結構(list/dict),自動轉換為 JSON;
  • 若轉換失敗,則設為 None 以避免破壞資料庫結構。
@property
def nutrients(self):
    if not self.nutrients_json:
        return []
    try:
        return json.loads(self.nutrients_json)
    except Exception:
        return []

@nutrients.setter
def nutrients(self, value):
    if isinstance(value, str):
        try:
            json.loads(value)
            self.nutrients_json = value
            return
        except Exception:
            self.nutrients_json = None
            return

    try:
        self.nutrients_json = json.dumps(value, ensure_ascii=False)
    except Exception:
        self.nutrients_json = None

二、修正相容性與警告

在測試與實際專案程式中,修正了 SQLAlchemy 的舊 API,這是 SQLAlchemy 2.0 的用法,可避免 LegacyAPIWarning,目的是確保在未來升級時維持兼容性與穩定性。

# 舊:
# rec = AnalysisRecord.query.get(rid)

# 新:
rec = db.session.get(AnalysisRecord, rid)

三、測試驗證結果

pytest -q

測試結果:

測試項目 結果
建立 / 查詢 / 更新 / 刪除 全部通過
JSON 欄位轉換 通過
timezone-aware 檢查 通過
SQLAlchemy 警告 無警告
pytest 執行結果 8 passed in 0.42s

四、過程中遇到的幾個問題,以及如何處理:

問題 原因與解法
ImportError: 找不到 models 或 db 確認於專案根目錄執行 pytest;pytest 會自動將根目錄加入 PYTHONPATH
IntegrityError: UNIQUE constraint failed 測試中重複使用相同 image_path。可於 fixture 中重新建立 in-memory DB,或在每個測試後清空資料表。
JSON parsing error setter 預設在無法序列化時設為 None。若要嚴格處理,可改為 raise ValueError 並於測試中檢查例外。

五、實作這次的專案資料庫

1. 技術面

  • ORM (Object-Relational Mapping):
    採用 Flask-SQLAlchemy
  • 資料庫引擎:
    開發與正式環境均使用 SQLite
    預設資料庫檔案位於 instance/ainutri.db

2. 資料庫設定與初始化

資料庫設定集中於 app.pycreate_app

  • 設定檔:

    • 正式環境:'sqlite:///ainutri.db'
    • 測試環境:'sqlite:///:memory:'(記憶體資料庫,確保測試獨立)
  • 初始化:

    1. models.py 中建立全域資料庫:
      db = SQLAlchemy()
      
    2. create_app() 中呼叫 db.init_app(app)
    3. 應用啟動時執行 db.create_all() 自動建立資料表。

3. 資料模型說明 (AnalysisRecord)

欄位 型別 說明
id Integer 主鍵,唯一識別每筆紀錄。
image_path String(255) 圖片檔案路徑,unique 以避免重複處理。
created_at DateTime 建立時間(使用 UTC)。
raw_analysis Text 儲存 AI 回傳的完整文字或 JSON。
summary Text AI 分析的簡短摘要。
nutrients_json Text 儲存營養素資訊的 JSON 字串。

nutrients 屬性:封裝

  • Getter: 自動將 nutrients_json 反序列化成 Python 結構。
  • Setter: 自動將 list/dict 轉換成 JSON 並寫回資料庫。

4. 使用者驅動的儲存機制:可參照 Day26 的專案展示說明

專案的資料儲存由使用者自行決定是否要寫入資料庫。

  1. 分析與暫存:
    上傳圖片後,AI 分析結果會被序列化並暫存在伺服器 session 中。

  2. 使用者確認:
    使用者在結果頁面檢視分析內容後,再按下「儲存這筆資料」按鈕進行確認。

  3. 非同步儲存:
    前端以 AJAX 發送 POST 請求至 /save_record API,
    後端從 session 讀取資料,建立 AnalysisRecord 物件:

    db.session.add(record)
    
    # 將紀錄正式寫入 SQLite。
    db.session.commit()
    

六、未來的進階目標

主題 建議
資料儲存優化 改用 PostgreSQL 的 JSONB 型別以支援索引查詢。
索引分離架構 將向量資料(如 Chroma、FAISS)獨立儲存,僅保留關聯鍵。
CI/CD 整合 於 GitHub Actions 自動執行 pytestflake8,確保品質。
測試覆蓋率追蹤 使用 pytest-cov 監控測試覆蓋率。

Ref.


上一篇
Day24. Flask 與資料庫整合 Ep3:用 TDD(測試驅動開發)+ SQLite 完成AI營養顧問的資料庫功能 Part-1
下一篇
Day26. 手動測試已經測到厭世了嗎?這次就請 AI 幫忙測:Chrome DevTools MCP 自動測試
系列文
AI 營養師 + Web3 數位健康護照27
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言