Dernière activité 9 months ago

提供一個整合資料庫連線、API 金鑰驗證與基本路由的 Flask 應用程式範本,適合作為 Web API 開發起點。

Révision f9b5423271a4142d4a390968d08ee9006bf53b98

template.py Brut
1#!/usr/bin/env python
2
3import os
4import sys
5from urllib.parse import quote_plus
6from datetime import datetime
7
8from dotenv import load_dotenv
9from flask import Flask, request, jsonify, g
10import records
11import pandas as pd
12from loguru import logger
13
14# 載入 .env 檔案中的環境變數
15load_dotenv()
16
17# 應用程式與 API 設定參數
18SECRET_KEY = os.getenv("SECRET_KEY", "default_secret")
19API_KEY = os.getenv("API_KEY", "your_generic_api_key_here")
20LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO")
21
22# 設定 loguru 日誌工具,將日誌輸出到標準輸出
23logger.remove()
24logger.add(
25 sys.stdout,
26 level=LOG_LEVEL,
27 format="<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | <level>{level}</level> | <cyan>{message}</cyan>",
28)
29
30# ------------------ 資料庫連線模組 ------------------
31
32class BaseDatabase:
33 def __init__(self, url):
34 self.url = url
35 logger.debug(f"資料庫 URL: {self.url}")
36 try:
37 self.db = records.Database(self.url)
38 logger.info("資料庫連線初始化成功。")
39 except Exception as e:
40 logger.error(f"資料庫連線初始化失敗: {e}")
41 raise
42
43 def query(self, sql):
44 try:
45 logger.debug(f"執行 SQL 查詢: {sql}")
46 self.result = self.db.query(sql, fetchall=True)
47 logger.info("查詢執行成功。")
48 except Exception as e:
49 logger.error(f"查詢執行發生錯誤: {e}")
50 self.result = None
51
52 def to_dataframe(self):
53 if self.result:
54 try:
55 data = self.result.dataset.dict
56 logger.debug(f"查詢結果轉換為 DataFrame: {data}")
57 return pd.DataFrame.from_dict(data)
58 except Exception as e:
59 logger.error(f"轉換為 DataFrame 失敗: {e}")
60 return pd.DataFrame()
61 else:
62 logger.warning("查詢結果為空。")
63 return pd.DataFrame()
64
65 def close(self):
66 try:
67 self.db.close()
68 logger.info("資料庫連線已成功關閉。")
69 except Exception as e:
70 logger.error(f"關閉資料庫連線失敗: {e}")
71
72 def test_connection(self):
73 try:
74 self.query("SELECT 1+1 AS result")
75 if self.result:
76 for row in self.result:
77 row_dict = row.as_dict()
78 return row_dict.get("result")
79 else:
80 logger.warning("測試連線查詢無回傳結果。")
81 return None
82 except Exception as e:
83 logger.error(f"測試連線時發生錯誤: {e}")
84 return None
85
86class SqlSrv(BaseDatabase):
87 def __init__(self, server, username, password, db_name):
88 if None in [server, username, password, db_name]:
89 raise ValueError("資料庫連線參數不足。")
90 password_encoded = quote_plus(password)
91 url = f"mssql+pymssql://{username}:{password_encoded}@{server}:1433/{db_name}?tds_version=7.0"
92 super().__init__(url)
93
94class SQLite(BaseDatabase):
95 def __init__(self, db_path):
96 url = f"sqlite:///{db_path}"
97 super().__init__(url)
98
99class MySQL(BaseDatabase):
100 def __init__(self, server, username, password, db_name):
101 url = f"mysql+pymysql://{username}:{quote_plus(password)}@{server}/{db_name}"
102 super().__init__(url)
103
104def get_db():
105 """
106 從環境變數中讀取資料庫連線參數,
107 建立 SQL Server 連線(可根據需求改用 SQLite 或 MySQL)。
108 """
109 DB_SERVER = os.getenv("DB_SERVER")
110 DB_USERNAME = os.getenv("DB_USERNAME")
111 DB_PASSWORD = os.getenv("DB_PASSWORD")
112 DB_NAME = os.getenv("DB_NAME")
113 return SqlSrv(DB_SERVER, DB_USERNAME, DB_PASSWORD, DB_NAME)
114
115# ------------------ API 工具與路由設定 ------------------
116
117def validate_api_key():
118 """
119 檢查請求中的 API 金鑰是否正確。
120 """
121 key = request.headers.get("Authorization") or request.headers.get("X-API-KEY")
122 if not key or (key != f"Bearer {API_KEY}" and key != API_KEY):
123 return False
124 return True
125
126def create_app():
127 """
128 建立並配置 Flask 應用程式,
129 包含資料庫連線初始化、路由註冊等設定。
130 """
131 app = Flask(__name__)
132 app.config['SECRET_KEY'] = SECRET_KEY
133
134 @app.before_request
135 def before_request():
136 """為每個請求初始化資料庫連線。"""
137 g.db = get_db()
138
139 @app.teardown_request
140 def teardown_request(exception=None):
141 """在請求結束時關閉資料庫連線。"""
142 db = getattr(g, 'db', None)
143 if db is not None:
144 db.close()
145
146 @app.route('/data', methods=['GET'])
147 def get_data():
148 """
149 資料查詢 API:
150 - 驗證 API 金鑰
151 - 解析請求參數(start 與 end)
152 - 執行 SQL 查詢並回傳 JSON 格式的查詢結果
153 """
154 if not validate_api_key():
155 return jsonify({"error": "Unauthorized"}), 401
156
157 start_time = request.args.get('start')
158 end_time = request.args.get('end')
159
160 if not start_time or not end_time:
161 return jsonify({"error": "Please provide 'start' and 'end' parameters"}), 400
162
163 try:
164 start_dt = datetime.strptime(start_time, "%Y-%m-%d %H:%M:%S")
165 end_dt = datetime.strptime(end_time, "%Y-%m-%d %H:%M:%S")
166 except ValueError:
167 return jsonify({"error": "Invalid date format. Use YYYY-MM-DD HH:MM:SS"}), 400
168
169 # 示範查詢語法,請根據實際需求修改資料表名稱與欄位
170 query = f"""
171 SELECT *
172 FROM my_table
173 WHERE created_at >= '{start_dt}'
174 AND created_at <= '{end_dt}'
175 ORDER BY created_at ASC
176 """
177 g.db.query(query)
178 df = g.db.to_dataframe()
179
180 if df.empty:
181 return jsonify([])
182
183 return jsonify(df.to_dict(orient="records"))
184
185 @app.route('/health', methods=['GET'])
186 def health_check():
187 """健康檢查 API,回傳伺服器狀態。"""
188 return jsonify({"status": "ok"})
189
190 return app
191
192# ------------------ 主程式執行進入點 ------------------
193
194if __name__ == '__main__':
195 from waitress import serve
196 app = create_app()
197 print("Starting generic server with Waitress...")
198 serve(app, host='0.0.0.0', port=5000)
199
test_api.sh Brut
1#!/bin/bash
2
3# 測試 API /data 端點的 curl 測試 script
4# Usage: ./test_api.sh '2025-01-01 00:00:00' '2025-01-02 00:00:00'
5
6API_URL="http://127.0.0.1:5000/data"
7API_KEY="your_generic_api_key_here" # 請替換成正確的 API 金鑰
8
9if [ "$#" -lt 2 ]; then
10 echo "Usage: $0 <start_time> <end_time>"
11 echo "Example: $0 '2025-01-01 00:00:00' '2025-01-02 00:00:00'"
12 exit 1
13fi
14
15START_TIME="$1"
16END_TIME="$2"
17
18# 將時間中的空格轉換成 URL 可讀格式
19ENCODED_START=$(echo "$START_TIME" | sed 's/ /%20/g')
20ENCODED_END=$(echo "$END_TIME" | sed 's/ /%20/g')
21
22curl -H "Authorization: Bearer $API_KEY" \
23 "$API_URL?start=$ENCODED_START&end=$ENCODED_END"
24