開發生涯,不斷堆加著程式碼,總有那麼一天,只是不經意地修改,卻...突然壞掉了!然後,修很久都修不好。這說明了一件重要的事:專案結構脆弱;隨著認知程度和專案規模的變大,標準也跟著一直提升。而這就是系統要「轉骨」的時候了,為了長治久安,必須進行重構,才能迎接下一階段的成長。以下紀錄我目前的最佳實行。
source control 相關
根目錄的第一個東西,隱藏目錄 .git 和隱藏檔 .gitignore。這是為了 source control。
python 虛擬環境相關
第二個東西是隱藏目錄 .venv (個人習慣的命名),專為此專案的 python 執行環境,相關的操作指令如下:# 以下指令在 zsh 終端機畫面,在專案根目錄執行,% 為提示符號
# 本專案指定使用 python3.11
% python3.11 -m venv .venv
% source .vnev/bin/activate
# 之後進入 python 虛擬環境,提示符號變為 (.venv)%
(.venv)% pip install --upgrade pip setuptools wheel
(.venv)% pip install -r requirements.txt
# requirements.txt 是另一個必須在根目錄的檔案,產生的指令如下:
(.venv)% pip freeze > requirements.txt
以上是專案最底層的資訊,非常重要,若有差錯,日後都是傷筋動骨的事,所有開發和執行的動作,都是基於此虛擬環境。
os 層級的檢查
比虛擬環境更底層的,是 os,在繼續發展之前,需要確認根基的穩固,相關指令如下:
# 看看目前的 python 版本,是什麼平台的
(.vnev)% python -c "import platform; print(platform.machine())"
# 以我的環境為例,應該會輸出 arm64,如果是 x86,代表用到模擬的 x86 環境,
# 效率較差,必須重來。以下紀錄整個重來的過程。
# 很有可能,連用來安裝 python 的 brew 就已經是 x86 版本,那就會一路錯下去
# 重裝 arm 版本的 Homebrew,開一個乾淨的 terminal 執行以下指令
% /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
# 檢查 brew 是哪一個 brew
% which brew
# 應該是 /opt/homebrew/bin/brew
# 也可以看看系統有多少 brew
% which -a brew
# 有可能多版本並存如下
# /opt/homebrew/bin/brew
# /usr/local/bin/brew
# 這時就要看誰排在前面,若要確認先用 arm 版本的 brew,需改 ~/.zshrc
% nano ~/.zshrc
# 把這行加在最上面
export PATH="/opt/homebrew/bin:/opt/homebrew/sbin:$PATH"
# 執行一次 .zshrc,再檢查一次 which brew
% source ~/.zshrc
# 用乾淨的 brew 安裝 pyenv
% brew install pyenv
# 用 pyenv 安裝 python
% pyenv install 3.11.14
% which python3.11
% python3.11 --version
需要以上嚴格的程序,確認正確的 python 版本。
cache 目錄
程式運行中,需要高頻率反覆存取的東西,而原始資料卻不會一直變動者,適合存成本地檔案。如「日 K 線」就是典型,昨天以前的資料,在當日之間是不會變動的,但計算波動與報酬,卻會經常用到,適合存起來快取。
wheel 目錄
有些套件,並沒有公開到外面的資料庫,如本系統使用的券商 api,是直接提供 .whl 檔,當佈署雲端時也是必須包含的。此外,也有可能再開 resource 目錄,反正只要 deploy 時要一併送上雲端的檔案,都可以開在專案根目錄,方便參用。
流程自動化所需 shell script file
如「佈署到雲端」的一系列動作,可以寫成 deploy.sh,放在專案根目錄。
#!/bin/bash
set -e # stop on error
echo "👉 Cleaning old package..."
rm -f myapp.zip
echo "👉 Packing all source files into myapp.zip..."
zip -r myapp.zip . \
-x ".venv/*" \
-x ".vscode/*" \
-x ".gitiqnore" \
-x "**/__pycache__/*" \
-x ".git/*" \
-x "candles_cache/*" \
-x "log/*" \
-x "**/log/*"
echo "👉 Adding cached price snapshots to the package..."
zip -r myapp.zip candles_cache/price_snapshot*
zip -r myapp.zip candles_cache/symbol_names*
echo "👉 Deploying to Azure App Service..."
az webapp deploy \
--resource-group aaabbb \
--name xxxyyy \
--src-path myapp.zip \
--type zip \
--restart true \
--clean true
echo "🎉 Deployment complete!"
原始碼
重點來了,因為專案根目錄已經太多資料了,我所有的原始碼都放在 src 目錄下面,且為了專案檔案間可以互相 import,最好是遵循正規的 package 規範。一個 package 就是一個目錄,為了有基準點,我把 src 也變成一個 package,所需動作就是在 src 目錄中新增一個檔案 __init __.py,檔案內容就先空著。
src 目錄中直接放置的程式碼很少,主要是作為入口的 main.py,和作為路由器的 router.py,其餘大量程式主要分為兩類,分別放置兩個目錄,controller and service,他們也都是 package,這樣 import 的規則就很簡單,全部採用「相對路徑」:
# main.py 引用同目錄的 newrouter.py:
from .newrouter import newrouter
# 在某個 service 目錄中,引用同一層的其他 service,也是同樣的方式:
from .firestoreService import FirestoreService
# controller 引用 service 的方式,從上兩層為參考點:
from ..newservice.fubonService import FubonService
package 的設置,大幅提升了專案的可攜性,在 deploy to cloud 時,降低踩雷機率。以前胡亂 import,可以跑就先撐著,苟延殘喘,某一天一定會爆,那時就被迫要做這件事了。
啟動 fastapi server 的方式
注意必須在虛擬環境提示符之下 (.venv)%,在專案根目錄,輸入:
uvicorn src.main:app --reload
重點就是,以「專案根目錄」為參考點,最清晰明瞭,不易出錯。以上 src 就是 package name,main 就是模組,app 就是 server instance,所以在 main.py 必須有一行:
app = FastAPI()
# main.py,這是程式進入點
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from .newrouter import newrouter
app = FastAPI()
origins = [
"http://localhost:5173", # Vite local frontend
"http://127.0.0.1:5173",
"https://newman-portfolio.azurewebsites.net", # Azure frontend
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins, # domains allowed, ["x"] for allow all
allow_credentials=True,
allow_methods=["*"], # GET, POST, etc.
allow_headers=["*"], # Authorization, Content-Type, ...
)
app.include_router(newrouter, prefix="/api")
部署到 azure app service
azure app service 中,重要且必要的設定如下:
- Stack setting:選擇正確的 python 版本
- Startup command:uvicorn src.main:app --host 0.0.0.0 --port 8000
在 deploy script 中會用到的重要識別資料:resource group name and name。
部署時最常用到的工具,就是 Log stream and SSH。
總結
以上紀錄的重點,基本上等同於「踩過的雷」,特此紀錄,以免再犯。
project/
├─ .venv/
├─ .git
├─ .gitignore
├─ cache/
├─ wheels/
├─ src/
│ ├─ __init__.py
│ ├─ main.py
│ ├─ newrouter.py
│ ├─ controller/
│ │ ├─ __init__.py
│ │ ├─ stockController.py
│ ├─ service/
│ │ ├─ __init__.py
│ │ ├─ fubonservice.py
├─ deploy.sh
├─ requirements.txt
Newman 2026/2/5
導覽頁:紐曼的技術筆記-索引
導覽頁:精明管家












