https://github.com/monkenwu/pythonflaskdevarchitecture
基於 Flask 的 API 伺服器開發架構示範
https://github.com/monkenwu/pythonflaskdevarchitecture
flask flask-api flask-application flask-restful flask-sqlalchemy
Last synced: 2 months ago
JSON representation
基於 Flask 的 API 伺服器開發架構示範
- Host: GitHub
- URL: https://github.com/monkenwu/pythonflaskdevarchitecture
- Owner: monkenWu
- License: mit
- Created: 2024-02-16T12:33:50.000Z (over 2 years ago)
- Default Branch: main
- Last Pushed: 2024-02-18T05:16:23.000Z (over 2 years ago)
- Last Synced: 2025-03-27T14:47:05.316Z (over 1 year ago)
- Topics: flask, flask-api, flask-application, flask-restful, flask-sqlalchemy
- Language: Python
- Homepage:
- Size: 47.9 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Python Flask Dev Architecture
這是一個基於 Flask 的 API 伺服器開發架構示範。
在這個示範中整合了一些常用的開發工具,並且提供了一個基本的開發環境。包含以下特性:
* 使用 `docker-compose` 部署開發環境
* 使用 `sqllib` 進行資料庫操作
* 使用 `alembic` 進行資料庫 Migration
* 使用 `SQLAlchemy` 作為 ORM,並且提供基本的 Model 設計
* 使用 `unittest` 進行單元測試,提供基本的測試行為與獨立的測試環境組態設定
* 使用 `flask` 進行 API 開發,並且進行 API 版本控制
* 使用 `logging` 進行日誌記錄,提供統一的日誌管理介面
* 使用 `flask` 內建的錯誤處理機制,並且提供統一的錯誤處理介面
* 建立 Controller-Service-DAO 的開發架構
## Feature
在專案內已包含了一些 APIs 與相關的測試,你可以透過在啟動開發環境後進行基本的測試。你可以在專案根目錄中找到 `APIs.postman_collection.json` 檔案,這是一個 Postman Collection 檔案,你可以透過 Postman 進行 API 測試,這些 API 在設計上依循 RESTful API 的設計原則。
在專案內提供以下的功能模組:
* 使用者
* 註冊
* 登入 - 取得 Access Token
* 取得使用者資訊
* 任務
* 新增任務
* 取得任務列表
* 取得任務資訊
* 更新任務 - 完整更新
* 更新任務 - 部分更新
* 刪除任務
## Environment
本專案將透過 docker-compose.yml 部署開發環境
### 初次部署
* 建構環境
```bash
docker-compose build
```
> 如果需要重頭建構 image 可以同時在指令後面使用 `--no-cache` 選項
* 執行容器
```bash
docker-compose up
```
> 如果需要背景執行可以使用 `-d` 選項
* 進入容器
```bash
docker-compose exec app bash
```
> 你也可以透過 VSCode 的 Remote - Containers 擴充套件進入容器開發
* 將 `env.example` 複製成 `.env` 並調整組態設定使其符合需求
* 初始化資料庫
```bash
alembic upgrade head
```
* 執行開發伺服器
```bash
python main.py
```
### 更新 pip 依賴
當你在專案中新增了新的依賴,你需要更新 `requirements.txt` 檔案。
```bash
pip freeze > requirements.txt
```
再次建構環境
```bash
docker-compose build
```
## Database Migration
資料庫 Migration 使用 `alembic`,並且使用 `SQLAlchemy` 作為 ORM。
`alembic` 會自動掃描 `models.orm` Package 下的所有 Model 並且生成對應的 Migration。
### 更新 Migration Versiom
若對於資料庫的 Schema 進行了修改,則需要更新 Migration Version。
```bash
alembic revision --autogenerate -m "message"
```
### 執行 Migration
```bash
alembic upgrade head
```
## Controller and API Version Control
所有 `controllers` 底下的資料夾都會被自動地掃描並且註冊成 API 路由。
API 將透過 `controllers` 底下的資料夾進行版本控制,每個資料夾代表一個版本的 API。
### 新增 API 版本
* 新增一個資料夾,並且命名為 `v{version}`,例如 `v1`
* 在 main.py 中調整以下程式
```python
## auto require all controller
versions = ['v1', 'v2', 'v{version}']
```
* 在新的 `v{version}` 資料夾中開始新增新的 Controller
### 新增 Controller
在 `v{version}` 資料夾中新增一個新的 Controller,例如 `IndexController.py`
```python
from flask import Blueprint, jsonify
from system.LogManager import LogManager
defi = Blueprint('v1_index_api', __name__)
@defi.route('/', methods=['GET'])
def index_page():
access_log = LogManager.get_logger('access_log', LogManager.LOG_INFO)
access_log.write('Access index page', LogManager.LOG_INFO)
return jsonify(status=200, msg={
"message": "Server is running. This is version 1 API."
})
```
defi 是一個 Flask Blueprint 實體,他將會被註冊成一個 API 路由。你一定得透過 defi 作為 Blueprint('v1_index_api', __name__) 的 Blueprint 名稱,這樣才能被 `main.py` 正確地自動註冊。
### 路由規則
所有 Controller `v{version}` 底下的路由都會以 `/api/{version}` 開頭。舉個例子:
置於 `{project_root}/controllers/v2/IndexController.py` 的以下路由
```python
@defi.route('/', methods=['GET'])
def index_page():
return jsonify(status=200, msg={
"message": "Server is running, This is version 2 API."
})
```
將會被註冊成 `/api/v2/` 。
## 執行單元測試
專案使用 `unittest` 作為單元測試框架,所有的單元測試都會置於 `tests` 資料夾底下。在執行單元測試前,請確保根目錄下的 `.testing.env` 與 `alembic.testing.ini` 檔案已經設定好。
### 執行測試
* 執行單個測試
```bash
python -m unittest tests/v1/TestUserCase.py
```
* 執行全部測試
```bash
python -m unittest discover -s ./tests/v1 -p 'Test*.py'
```
### 建立新的測試
一個測試檔案應該繼承 `unittest.TestCase` 並且在 `setUp` 方法中初始化測試環境,並且在 `tearDown` 方法中清理測試環境。
請於合適的地方建立一個以 `Test` 開頭的測試檔案,並讓他看起來像是以下的程式碼:
```python
from tests.BaseTest import initialize_database, clear_database
import unittest
from main import app
class TestUserCase(unittest.TestCase):
def setUp(self):
self.client = app.test_client()
self.ctx = app.app_context()
self.ctx.push()
initialize_database()
def tearDown(self):
self.ctx.pop()
clear_database()
```
上述程式碼中的 `initialize_database` 與 `clear_database` 是用來初始化與清理測試資料庫的方法,這個測試用資料庫將會依賴 `.testing.env` 與 `alembic.testing.ini` 檔案中的相關設定。
## 錯誤處理
專案使用 `flask` 內建的錯誤處理機制,並且透過 `logging` 模組進行日誌的記錄。
### 錯誤處理
錯誤處理的程式碼位於 `main.py` 中,並且透過 `@app.errorhandler` 裝飾器進行註冊。若在程式中出現無法處理的錯誤,那麼將會統一拋出 HTTP 500 錯誤。
當 `.env` 中的 `APP_ENV` 設定為 `development` 時,錯誤處理將會回傳詳細的錯誤訊息,否則將會回傳一個簡單的錯誤訊息。
舉個例子:
* development 環境響應的錯誤資訊
```json
{
"details": [
"Traceback (most recent call last):",
" File \"/usr/local/lib/python3.9/site-packages/flask/app.py\", line 870, in full_dispatch_request",
" rv = self.dispatch_request()",
" File \"/usr/local/lib/python3.9/site-packages/flask/app.py\", line 855, in dispatch_request",
" return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args) # type: ignore[no-any-return]",
" File \"/app/controllers/v1/UserController.py\", line 10, in register",
" sdfds",
"NameError: name 'sdfds' is not defined"
],
"message": "name 'sdfds' is not defined"
}
```
* production 環境響應的錯誤資訊
```json
{
"message": "Internal Server Error"
}
```
### 拋出錯誤
你可以透過 `system.Exceptions` 底下的類別在你的 Controller 或是 Service 中拋出錯誤。框架會自動地將這些錯誤訊息轉換成 HTTP 響應。
舉了一個例子:
```python
from system.Exceptions import ValidationError
def create_task(self, user_id, name):
task = self.task_dao.find_by_name(user_id, name)
if task:
raise ValidationError(f'Task {name} already exists')
```
此時,當 `create_task` 方法被呼叫時,若 `task` 已經存在,則會拋出一個 HTTP 400 錯誤,並且響應以下的訊息:
```json
{
"message": "Task {name} already exists"
}
```
## 日誌
專案內的日誌統一由 `system.LogManager` 進行管理,他是一個 Singleton 類別,並且透過 `logging` 模組進行日誌的記錄。所有的 Log 將統一被寫入到 `writable/logs` 資料夾底下,相同的日誌最多留存 3 天。
### 系統預設錯誤日誌
系統錯誤將會被記錄在 `writable/logs/app_error.log`。當無法預期的錯誤發生時,將會被記錄在這個日誌中。就像這樣:
```log
2024-02-18 01:00:38,773 - ERROR - name 'sdfds' is not defined
Traceback (most recent call last):
File "/usr/local/lib/python3.9/site-packages/flask/app.py", line 870, in full_dispatch_request
rv = self.dispatch_request()
File "/usr/local/lib/python3.9/site-packages/flask/app.py", line 855, in dispatch_request
return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args) # type: ignore[no-any-return]
File "/app/controllers/v1/UserController.py", line 10, in register
sdfds
NameError: name 'sdfds' is not defined
```
### 建立你自己的日誌
你可以透過 `system.LogManager` 類別建立你自己的日誌,使用 `get_logger` 方法取得 Logger 實體。舉個例子:
```python
from flask import Blueprint, jsonify
from system.LogManager import LogManager
defi = Blueprint('v1_index_api', __name__)
@defi.route('/', methods=['GET'])
def index_page():
access_log = LogManager.get_logger('access_log', LogManager.LOG_INFO)
access_log.write('Access index page', LogManager.LOG_INFO)
return jsonify(status=200, msg={
"message": "Server is running. This is version 1 API."
})
```
上述程式碼中的 `access_log` 將會記錄在 `writable/logs/access_log.log` 中。