概述
項目中需要快速搭建一個前后端系統,涉及到dash-fastapi架構的時候,對該架構的時候進行總結。本文主要總結的是對該架構的基本使用,后續再對該架構的項目源碼進行總結分析
此處實現一個小的demo,迷你任務管理器,后端使用FastAPI,前端則使用Dash,數據存儲暫時使用列表進行存儲,主要功能如下
- 任務列表展示: 前端頁面顯示一個簡單的任務列表,包含任務標題和狀態。
- 添加任務: 用戶可以在前端輸入任務標題,點擊按鈕添加新任務。
- 刷新任務列表: 點擊按鈕可以刷新任務列表,從后端獲取最新數據。
整體架構理解
代碼主體架構
- 后端
- main.py (Fast API主文件)
- requirements.txt(后端依賴)
- 前端
- app.py(Dash主文件)
- api_client.py(前端API客戶端)
- layoputs.py(前端布局)
- callbacks.py(前端回調函數)
- requirements.txt(后端依賴)
主要邏輯理解
代碼中具體體現
- 后端
- main.py:后端,也就類似于廚房。專門負責接收顧客的訂單,然后準備食物(構建響應)并告知服務器食物準備后
- tasks_db = []:通過列表內存列表,類似于廚師的菜單列表。其記錄了餐廳可以提供的菜品,也就是后端可以完成的任務
- @app.get :廚師提供給服務員今日菜單,服務員發送get請求的時候,就可以知道后端提供什么服務(從tasks_db中獲取)
- @app.post:創創建任務,類似于服務員將菜單傳給廚房;后面的邏輯就是請求響應的基本邏輯
- Task(使用Pydantic模型):菜單上的菜品敘述,規定了每個任務包含哪些信息,提供任務名以及狀態是否完成
- 前端
- layouts.py:餐廳的菜單,定義了顧客可以看到什么,也就是前端顯示的頁面
- dcc.Input:點餐單的填寫區域,顧客要吃什么
- dbc.Button:提交按鈕,這里可以對應設計供,例如提交點餐單或者是刷新菜單信息
- html.Div:上菜的盤子,廚房準備后的食物會放進這個盤子里展示給顧客
- ?callbacks.py:服務員接收到顧客的指令應該如何行動
- api_client.py:點餐系統,幫助前端與后端溝通,服務員與廚師之間的溝通? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
- layouts.py:餐廳的菜單,定義了顧客可以看到什么,也就是前端顯示的頁面
具體實現
該實例主要用于理解該結構的運行
代碼
后端:主要提供兩個方法,獲取所有任務列表和創建新任務
# backend/main.pyfrom fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import Listapp = FastAPI()# 模擬內存數據庫 (使用 Python 列表)
tasks_db = []
# 用于生成唯一的用戶ID
task_id_counter = 1class Task(BaseModel):id: inttitle: strstatus: str = "待完成" # 默認狀態class TaskCreate(BaseModel):title: strclass TaskResponse(BaseModel):tasks: List[Task]@app.get("/api/tasks", response_model=TaskResponse)
async def get_tasks():"""獲取所有任務列表"""return TaskResponse(tasks=tasks_db)@app.post("/api/tasks", response_model=Task)
async def create_task(task_create: TaskCreate):"""創建新任務"""global task_id_counternew_task = Task(id=task_id_counter, title=task_create.title)tasks_db.append(new_task)task_id_counter += 1return new_taskif __name__ == "__main__":import uvicornuvicorn.run(app, host="0.0.0.0", port=8000, reload=True)
后端依賴:requirements.txt
fastapi
uvicorn
pydantic
前端代碼:api_client.py(向服務端發起請求)
import requestsAPI_BASE_URL = "http://localhost:8000/api" # 后端 API 基礎 URLdef get_task_list():"""獲取任務列表"""url = f"{API_BASE_URL}/tasks"response = requests.get(url)response.raise_for_status() # 檢查請求是否成功 (狀態碼 2xx)return response.json()def create_new_task(title):"""創建新任務"""url = f"{API_BASE_URL}/tasks"headers = {'Content-Type': 'application/json'}data = {'title': title}response = requests.post(url, headers=headers, json=data)response.raise_for_status()return response.json()
前端回調:callbacks.py,當顧客觸碰哪些按鈕后與后端交互然后返回的邏輯實現
from dash import Output, Input, State
from .app import app # 導入 Dash app 實例
from frontend import api_client # 導入 API 客戶端
import dash_html_components as html
import dash@app.callback(Output("task-list-output", "children"),[Input("refresh-tasks-button", "n_clicks"),Input("add-task-button", "n_clicks")],[State("new-task-title", "value")]
)
def update_task_list(refresh_clicks, add_clicks, new_task_title):"""更新任務列表顯示"""triggered_id = [p['prop_id'] for p in dash.callback_context.triggered][0]if "add-task-button" in triggered_id:if new_task_title:api_client.create_new_task(new_task_title) # 調用 API 創建新任務tasks_data = api_client.get_task_list() # 調用 API 獲取任務列表task_items = []if tasks_data and tasks_data.get('tasks'): # 檢查 tasks_data 和 tasks 鍵是否存在for task in tasks_data['tasks']:task_items.append(html.Li(f"{task['title']} - 狀態: {task['status']} (ID: {task['id']})"))else:task_items.append(html.Li("暫無任務"))return html.Ul(task_items)
?前端頁面布局layouts.py
import dash_html_components as html
import dash_core_components as dcc
import dash_bootstrap_components as dbclayout = dbc.Container([html.H1("迷你任務管理器"),dbc.Row([dbc.Col([html.Div("任務標題:"),dcc.Input(id="new-task-title", type="text", placeholder="請輸入任務標題"),dbc.Button("添加任務", id="add-task-button", n_clicks=0, className="mt-2"),]),]),html.Hr(className="mt-3"),html.H4("任務列表"),dbc.Button("刷新任務列表", id="refresh-tasks-button", n_clicks=0, className="mb-2"),html.Div(id="task-list-output"), # 用于顯示任務列表
])
前端依賴
dash
dash-bootstrap-components
requests
pydantic補充
"""
簡單事例
"""# from pydantic import BaseModel
#
# class User(BaseModel):
# id: int
# name: str
# email: str
# is_active: bool = True # 默認值
#
# # 示例數據
# user_data = {
# 'id': 1,
# 'name': 'Alice',
# 'email': 'alice@example.com',
# }
#
# # 使用 Pydantic 模型進行數據驗證和解析
# user = User(**user_data)
# print(user)"""
復雜事例的封裝
"""
from pydantic import BaseModel
from typing import Listclass Address(BaseModel):street: strcity: strzip_code: strclass User(BaseModel):id: intname: straddress: Address # 嵌套模型# 創建嵌套數據
user_data = {'id': 1,'name': 'John','address': {'street': '123 Main St','city': 'New York','zip_code': '10001',}
}user = User(**user_data)
print(user)
前端回調邏輯
@app.callback(Output("task-list-output", "children"),[Input("refresh-tasks-button", "n_clicks"),Input("add-task-button", "n_clicks")],[State("new-task-title", "value")]
)
def update_task_list(refresh_clicks, add_clicks, new_task_title):"""更新任務列表顯示"""triggered_id = [p['prop_id'] for p in dash.callback_context.triggered][0]if "add-task-button" in triggered_id:if new_task_title:api_client.create_new_task(new_task_title) # 調用 API 創建新任務tasks_data = api_client.get_task_list() # 調用 API 獲取任務列表task_items = []if tasks_data and tasks_data.get('tasks'): # 檢查 tasks_data 和 tasks 鍵是否存在for task in tasks_data['tasks']:task_items.append(html.Li(f"{task['title']} - 狀態: {task['status']} (ID: {task['id']})"))else:task_items.append(html.Li("暫無任務"))return html.Ul(task_items)
回調函數callbacks理解
Dash框架中回調函數是實現交互的關鍵,其一可以連接前端的UI組件交互和侯丹數據的處理邏輯(調用API或者更新圖表操作),從而實現動態更新前端UI,而不需要更新整個頁面
Output("task-list-output", "children")
(輸出)
該處定義了回調函數的輸出,其指定了當回調函數執行完畢后,哪個前端組件的哪個屬性會被更新
html.Div(id="task-list-output") # <--- 這里定義了 id="task-list-output" 的 Div 組件
這個回調函數執行完成后,將會更新 id
為 "task-list-output"
的 Div
組件的 children
屬性。 換句話說,回調函數的返回值將會被設置為這個 Div
組件的內容,從而更新任務列表的顯示
換句話說,output就是上菜的盤子,盤子里面的內容就是children屬性
[Input("refresh-tasks-button", "n_clicks"), Input("add-task-button", "n_clicks")]
(輸入 - 觸發器)
指定了當前前端組件的哪些屬性發生變化的時候,會觸發這個回調函數執行
dbc.Button("刷新任務列表", id="refresh-tasks-button", ...) # <--- 這里定義了 id="refresh-tasks-button" 的按鈕
類似于顧客點擊菜價查詢,服務員就會去問一下菜價,當顧客點擊提交餐單的時候,服務員就會立馬去廚房下單
[State("new-task-title", "value")]
(狀態)
指定哪些前端組件的哪些屬性的當前值需要傳遞給回調函數,但是State組件屬性的變化不會觸發回調函數執行
可以理解State就是顧客在菜單上書寫的菜名
當 update_task_list
回調函數被觸發執行時 (因為 "刷新任務列表" 按鈕或 "添加任務" 按鈕被點擊了),Dash 框架會將 id
為 "new-task-title"
的輸入框組件的 value
屬性的 "當前值" 作為參數傳遞給 update_task_list
函數。 注意,輸入框內容的變化 不會 直接觸發回調函數,只有當 Input
指定的組件屬性變化時才會觸發