前言
上一篇文章我們使用 DBAPI wrapper package 與 ORM database engine 成功的與資料庫連接,並且使用 SQL/ORM 新增一筆聯絡人到資料庫中。但是別開心得太早,這邊我們會遇上兩個嚴重的問題,一個問題是我們將與資料庫的連線、寫入資料庫的邏輯以及資料庫模型寫在一起,就像個大泥球一樣遲早會過度耦合,未來需要創建更多資料表、欄位,處理更多資料庫的操作時,增加了額外的維護成本也徒增改壞系統的可能性,所以需要幫它瘦身一下按照功能拆分出來。
第二個問題則是,程式碼中關於資料庫的 Connection Strings/Connection URIs,裡頭都使用明碼的呈現無碼,像是資料庫密碼這種敏感資料被壞人拿到就不好了!又或者專案本身有版本控制,將服務中機敏資料的密鑰或密碼,也一起上到 Git、雲端,增加資料外洩的風險。將它寫入環境變數裡,可以大大的預防、降低風險。引入 .env 檔案做環境變數存放,這邊先不討論各個環境下(development, staging, production)該如何部署的情況。
分流...治療理...分而治之
或許可以從歷史借鏡,過去的列強在治理殖民地時,都會根據種族、文化等分化治理,讓本來就關係不好的兩派(或多派)不會起來造反。扯遠了,db.py
一份檔案也能按照邏輯拆成數份檔案,讓我們來分析一下。首先,來盤點一下它做了幾件事情:
- 連結資料庫
- 資料表模型
- 操作資料庫
那是不是能先朝著這三個方面優化。緊接著將 db.py 在額外拆出 crud.py
、models.py
兩份檔案後,三份檔案就會各施其職、更好維護了,如下:
# db.py before
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
engine = create_engine('postgresql://postgres:secret@localhost:5432/test', echo=True, future=True)
Session = sessionmaker(bind=engine)
Base = declarative_base()
# crud.py
from db import Session
from models import User
session = Session()
session.add(User(name='ed', email='ormtest@gooogle', company='Gooogle'))
person = session.query(User).filter_by(name='ed').first()
session.commit()
# models.py
from sqlalchemy import Column, Integer, String
from db import Base
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String(50), nullable=False)
email = Column(String(50), nullable=False)
company = Column(String(50), nullable=False)
孟母三遷引入環境變數
還記得剛剛我們做了什麼嗎?分而治之嘛!這邊也要將 db.py 裡頭的 database URL
移出來,新建兩個檔案 config.py
與 .env
,但開始動手前想先談談 python-dotenv。關於 Python 專案的環境變數設定有很多種方法,可以用內建的 libary Configuration,或是待會隆重介紹的 python-dotenv
;.env 的形式也有很多種,像是 .ini
、.json
、.toml
、.yaml
等檔案形式,但我想用更簡單的方式呈現,所以選用 python-dotenv 引入專案裡。
先用 pip 套件管理工具,下載 package
pip install python-dotenv
python-dotenv 的運作方式也很簡單,預設就是引入 key-value
結構的 .env 檔案,將設定寫入環境變數中,透過 os.getenv
取得環境變數中的值。
所以我們的 .env 檔案格式如下,會相似於 Bash file
:
export POSTGRES_USER="postgres"
export POSTGRES_PASSWORD="secret"
export POSTGRES_SERVER="localhost"
export POSTGRES_PORT="5432"
export POSTGRES_DB="test"
export DATABASE_URL="postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_SERVER}:${POSTGRES_PORT}/${POSTGRES_DB}"
python-dotenv 的設定中可以省略不加 export
,key 值可以選擇加上或不加單引號、雙引號,我更喜歡簡潔的表示如下:
POSTGRES_USER=postgres
POSTGRES_PASSWORD=secret
POSTGRES_SERVER=localhost
POSTGRES_PORT=5432
POSTGRES_DB=test
DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_SERVER}:${POSTGRES_PORT}/${POSTGRES_DB}
準備好 .env 檔就可以呼叫 load_dotenv()
載入 .env,config.py 如下:
# config.py
import os
from dotenv import load_dotenv
load_dotenv()
SQLALCHEMY_DATABASE_URI = os.getenv('DATABASE_URL')
Database URL 透過環境變數的引入,db.py 只要透過 config.py 取得 Database URL,就能正常與資料庫連線。
# db.py after
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
from config import SQLALCHEMY_DATABASE_URI
engine = create_engine(SQLALCHEMY_DATABASE_URI, echo=True, future=True)
Session = sessionmaker(bind=engine)
Base = declarative_base()
是不是很簡單又方便使用,還有很多進階的 python-dotenv 使用方法,建議到文件裡查閱。
結論
回顧一下本次所學到的內容。為了好維護專案程式碼,將功能切分一小塊、拆分成各自的服務,分而治之並讓它們各施其職,做好自己職責。將機敏資料寫在 .env file 裡,透過 config.py 用環境變數的方式引入,db.py 就能從 config 裡拿到 Database URL,也保護專案的裡頭重要的密碼、密鑰,不會這麼容易被取得。
下一章節會再把主軸拉回到匯入小工具身上,會談的是如何讀取 csv 檔案,將資料整理過後寫入資料庫,也要判斷資料是否已經存在資料庫裡,資料表格式不符合預期該怎麼處理,那我們就下一章節見。