Initial commit: Momentry Core v0.1
- Rust-based digital asset management system - Video analysis: ASR, OCR, YOLO, Face, Pose - RAG capabilities with Qdrant vector database - Multi-database support: PostgreSQL, Redis, MongoDB - Monitoring system with launchd plists - n8n workflow automation integration
This commit is contained in:
483
docs/PYTHON.md
Normal file
483
docs/PYTHON.md
Normal file
@@ -0,0 +1,483 @@
|
||||
# Python 開發規範
|
||||
|
||||
## 概述
|
||||
|
||||
本文檔定義 Momentry 專案中 Python 程式碼的開發標準與最佳實踐。
|
||||
|
||||
---
|
||||
|
||||
## 版本管理
|
||||
|
||||
### 鎖定版本
|
||||
|
||||
| 版本 | 用途 | 路徑 |
|
||||
|------|------|------|
|
||||
| Python 3.11.14 | Momentry venv | /Users/accusys/momentry_core_0.1/venv/bin/python |
|
||||
| Python 3.11.14 | 系統安裝 | /opt/homebrew/bin/python3.11 |
|
||||
| Python 3.14.3 | 系統預設 | /opt/homebrew/bin/python3 |
|
||||
| Python 3.9.6 | 系統預設 (備用) | /usr/bin/python3 |
|
||||
|
||||
### 版本選擇原則
|
||||
|
||||
- **Momentry 專案**:使用 venv 中的 Python 3.11.14
|
||||
- **新專案**:建議使用 venv 管理
|
||||
- **系統工具**:可使用系統預設版本
|
||||
|
||||
---
|
||||
|
||||
## 腳本規範
|
||||
|
||||
### Shebang 宣告
|
||||
|
||||
所有 Momentry Python 腳本必須在第一行宣告明確的 Python 路徑:
|
||||
|
||||
```python
|
||||
#!/opt/homebrew/bin/python3.11
|
||||
```
|
||||
|
||||
**錯誤範例**:
|
||||
```python
|
||||
#!/usr/bin/env python3 # 會解析到系統預設 (3.14.3)
|
||||
#!/usr/bin/python3 # 會使用系統 Python (3.9.6)
|
||||
```
|
||||
|
||||
**正確範例**:
|
||||
```python
|
||||
#!/opt/homebrew/bin/python3.11
|
||||
import sys
|
||||
...
|
||||
```
|
||||
|
||||
### 檔案結構
|
||||
|
||||
```
|
||||
scripts/
|
||||
├── asr_processor.py # ASR 處理腳本
|
||||
├── thumbnail_extractor.py # 縮圖提取腳本
|
||||
└── new_script.py # 新腳本模板
|
||||
```
|
||||
|
||||
### 腳本模板
|
||||
|
||||
```python
|
||||
#!/opt/homebrew/bin/python3.11
|
||||
"""
|
||||
腳本名稱
|
||||
簡短描述腳本功能
|
||||
|
||||
用法:
|
||||
python3.11 script.py <args>
|
||||
|
||||
作者: Momentry Team
|
||||
版本: 1.0.0
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import logging
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s - %(levelname)s - %(message)s",
|
||||
stream=sys.stderr,
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="腳本功能描述")
|
||||
parser.add_argument("input", help="輸入檔案或參數")
|
||||
parser.add_argument("-o", "--output", default="output.json", help="輸出檔案")
|
||||
parser.add_argument("-v", "--verbose", action="store_true", help="詳細輸出")
|
||||
parser.add_argument("-c", "--count", type=int, default=10, help="數量")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.verbose:
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
# 業務邏輯
|
||||
result = process_data(args.input, args.count)
|
||||
|
||||
# 輸出 JSON結果
|
||||
print(json.dumps(result))
|
||||
|
||||
|
||||
def process_data(input_path: str, count: int) -> dict:
|
||||
"""處理資料並返回結果"""
|
||||
logger.info(f"Processing: {input_path}")
|
||||
|
||||
# TODO: 實作業務邏輯
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"input": input_path,
|
||||
"count": count,
|
||||
}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 與 Rust 整合
|
||||
|
||||
### 使用 venv (目前採用)
|
||||
|
||||
Momentry 使用 venv 管理 Python 環境,避免與系統其他程式衝突。
|
||||
|
||||
#### 建立 venv
|
||||
|
||||
```bash
|
||||
# 建立虛擬環境
|
||||
cd /Users/accusys/momentry_core_0.1
|
||||
/opt/homebrew/bin/python3.11 -m venv venv
|
||||
|
||||
# 啟用虛擬環境
|
||||
source venv/bin/activate
|
||||
|
||||
# 安裝依賴
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
#### 從 Rust 呼叫 venv Python
|
||||
|
||||
```rust
|
||||
use std::path::Path;
|
||||
|
||||
let script_path = Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("scripts")
|
||||
.join("asr_processor.py");
|
||||
|
||||
// 使用 venv 中的 Python
|
||||
let venv_python = Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("venv")
|
||||
.join("bin")
|
||||
.join("python");
|
||||
|
||||
let output = Command::new(venv_python)
|
||||
.arg(script_path)
|
||||
.arg(video_path)
|
||||
.output()
|
||||
.context("Failed to run processor")?;
|
||||
```
|
||||
|
||||
**優點**:
|
||||
- 專案依賴隔離
|
||||
- 不同專案可使用不同 Python 版本
|
||||
- 易於重現環境
|
||||
- 不影響系統其他程式
|
||||
|
||||
---
|
||||
|
||||
## 依賴管理
|
||||
|
||||
### venv 目錄結構
|
||||
|
||||
```
|
||||
momentry_core_0.1/
|
||||
├── venv/ # 虛擬環境
|
||||
│ ├── bin/
|
||||
│ │ ├── python # Python 3.11.14
|
||||
│ │ ├── pip
|
||||
│ │ └── ...
|
||||
│ └── lib/python3.11/ # 安裝的套件
|
||||
├── requirements.txt # 依賴列表
|
||||
├── scripts/ # Python 腳本
|
||||
│ ├── asr_processor.py
|
||||
│ └── thumbnail_extractor.py
|
||||
└── src/ # Rust 程式碼
|
||||
```
|
||||
|
||||
### 使用虛擬環境
|
||||
|
||||
```bash
|
||||
# 啟用虛擬環境
|
||||
source venv/bin/activate
|
||||
|
||||
# 安裝依賴
|
||||
pip install faster-whisper
|
||||
|
||||
# 退出虛擬環境
|
||||
deactivate
|
||||
```
|
||||
|
||||
# 退出虛擬環境
|
||||
deactivate
|
||||
```
|
||||
|
||||
### 依賴列表格式
|
||||
|
||||
建立 `requirements.txt`:
|
||||
|
||||
```
|
||||
faster-whisper>=1.0.0
|
||||
ffmpeg-python>=0.2.0
|
||||
Pillow>=10.0.0
|
||||
```
|
||||
|
||||
### 安裝專案依賴
|
||||
|
||||
```bash
|
||||
# 使用 python3.11 安裝
|
||||
/opt/homebrew/bin/python3.11 -m pip install -r requirements.txt
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 程式碼規範
|
||||
|
||||
### Import 排序
|
||||
|
||||
```python
|
||||
# 1. 標準庫
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
# 2. 第三方庫
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from faster_whisper import WhisperModel
|
||||
|
||||
# 3. 本地模組
|
||||
from . import local_module
|
||||
from ..package import module
|
||||
```
|
||||
|
||||
### 命名規範
|
||||
|
||||
| 類型 | 規範 | 範例 |
|
||||
|------|------|------|
|
||||
| 模組/檔案 | snake_case | `asr_processor.py` |
|
||||
| 類別 | PascalCase | `class VideoProcessor` |
|
||||
| 函數/變數 | snake_case | `def process_video()` |
|
||||
| 常量 | UPPER_SNAKE_CASE | `MAX_WORKERS = 4` |
|
||||
| 私有成員 | _leading_underscore | `_private_method()` |
|
||||
|
||||
### 類型提示
|
||||
|
||||
```python
|
||||
from typing import Optional, List, Dict
|
||||
|
||||
def process_video(
|
||||
video_path: str,
|
||||
options: Optional[Dict[str, int]] = None,
|
||||
) -> List[Dict[str, float]]:
|
||||
"""處理影片並返回結果"""
|
||||
...
|
||||
```
|
||||
|
||||
### 錯誤處理
|
||||
|
||||
```python
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def process_video(video_path: str) -> dict:
|
||||
path = Path(video_path)
|
||||
|
||||
if not path.exists():
|
||||
logger.error(f"Video file not found: {video_path}")
|
||||
raise FileNotFoundError(f"Video not found: {video_path}")
|
||||
|
||||
try:
|
||||
result = _do_process(path)
|
||||
logger.info(f"Processed successfully: {path}")
|
||||
return result
|
||||
except Exception as e:
|
||||
logger.exception(f"Processing failed: {e}")
|
||||
raise
|
||||
```
|
||||
|
||||
### 日誌規範
|
||||
|
||||
```python
|
||||
import logging
|
||||
import sys
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s - %(levelname)s - %(message)s",
|
||||
stream=sys.stderr,
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# 使用說明
|
||||
logger.info("Starting process...")
|
||||
logger.debug(f"Input: {input_path}")
|
||||
logger.warning(f"Using fallback: {reason}")
|
||||
logger.error(f"Failed: {error}")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 測試規範
|
||||
|
||||
### 測試結構
|
||||
|
||||
```
|
||||
tests/
|
||||
├── __init__.py
|
||||
├── test_asr_processor.py
|
||||
└── test_thumbnail_extractor.py
|
||||
```
|
||||
|
||||
### 測試範例
|
||||
|
||||
```python
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent / "scripts"))
|
||||
|
||||
from asr_processor import run_asr
|
||||
|
||||
|
||||
def test_asr_processor_with_valid_video(tmp_path):
|
||||
video_path = tmp_path / "test.mp4"
|
||||
output_path = tmp_path / "output.json"
|
||||
|
||||
# 建立測試影片
|
||||
video_path.write_text("dummy")
|
||||
|
||||
# 執行
|
||||
result = run_asr(str(video_path), str(output_path))
|
||||
|
||||
# 斷言
|
||||
assert output_path.exists()
|
||||
assert result["segments"]
|
||||
|
||||
|
||||
def test_asr_processor_with_invalid_video():
|
||||
with pytest.raises(FileNotFoundError):
|
||||
run_asr("/nonexistent/video.mp4", "/tmp/output.json")
|
||||
```
|
||||
|
||||
### 執行測試
|
||||
|
||||
```bash
|
||||
# 使用 python3.11 執行測試
|
||||
/opt/homebrew/bin/python3.11 -m pytest tests/ -v
|
||||
|
||||
# 包含覆蓋率
|
||||
/opt/homebrew/bin/python3.11 -m pytest tests/ --cov=scripts
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 監控配置
|
||||
|
||||
### 監控腳本
|
||||
|
||||
在 `monitor/config/monitor_config.yaml` 中配置:
|
||||
|
||||
```yaml
|
||||
service:
|
||||
- name: "python"
|
||||
type: "process"
|
||||
process_name: "python3"
|
||||
enabled: true
|
||||
check_interval: 60
|
||||
version_lock: "3.11.14"
|
||||
scripts:
|
||||
- "/Users/accusys/momentry_core_0.1/scripts/asr_processor.py"
|
||||
- "/Users/accusys/momentry_core_0.1/scripts/thumbnail_extractor.py"
|
||||
```
|
||||
|
||||
### 檢查版本
|
||||
|
||||
```bash
|
||||
# 執行 Python 監控
|
||||
bash /Users/accusys/momentry_core_0.1/monitor/control/monitor_control.sh check python
|
||||
|
||||
# 查看資料庫記錄
|
||||
psql -U accusys -h localhost -d momentry -c "SELECT * FROM python_version_baseline;"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CI/CD 考量
|
||||
|
||||
### GitHub Actions 範例
|
||||
|
||||
```yaml
|
||||
name: Python Tests
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python 3.11
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.11"
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
python -m pytest tests/ -v
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 常見問題
|
||||
|
||||
### Q: 為什麼腳本要用 `#!/opt/homebrew/bin/python3.11` 而不是 `#!/usr/bin/env python3`?
|
||||
|
||||
A: `#!/usr/bin/env python3` 會解析 PATH 中的第一個 `python3`,在 macOS 上可能是:
|
||||
- `/opt/homebrew/bin/python3` (3.14.3)
|
||||
- `/usr/bin/python3` (3.9.6)
|
||||
|
||||
明確指定路徑可確保使用正確版本。
|
||||
|
||||
### Q: Rust 呼叫 Python 腳本時如何確保版本正確?
|
||||
|
||||
A: 有三種方式:
|
||||
1. Rust 程式碼中使用明確路徑:`Command::new("/opt/homebrew/bin/python3.11")`
|
||||
2. 設定環境變數 PATH
|
||||
3. 建立系統別名(不推薦,影響其他程式)
|
||||
|
||||
### Q: 如何管理多個 Python 版本?
|
||||
|
||||
A: 建議使用:
|
||||
- **pyenv**:管理多個 Python 版本
|
||||
- **venv**:隔離專案依賴
|
||||
- **Docker**:容器化環境
|
||||
|
||||
---
|
||||
|
||||
## 檢查清單
|
||||
|
||||
新增 Python 腳本時確認:
|
||||
|
||||
- [ ] 使用 `#!/opt/homebrew/bin/python3.11` shebang
|
||||
- [ ] 包含 docstring 說明功能
|
||||
- [ ] 使用 argparse 處理命令行參數
|
||||
- [ ] 使用 logging 進行日誌輸出
|
||||
- [ ] 錯誤處理適當
|
||||
- [ ] 類型提示完整
|
||||
- [ ] 更新監控配置
|
||||
- [ ] 建立測試案例
|
||||
|
||||
---
|
||||
|
||||
## 相關文件
|
||||
|
||||
- [NODEJS.md](./NODEJS.md) - Node.js 開發指南
|
||||
- [monitor_config.yaml](../monitor/config/monitor_config.yaml) - 監控配置
|
||||
- [python_monitor.sh](../monitor/service/python_monitor.sh) - Python 監控腳本
|
||||
Reference in New Issue
Block a user