匯入小工具 (3) - 設定環境變數,才不會被看光光


Posted by 微薄艇 on 2021-07-19

前言

上一篇文章我們使用 DBAPI wrapper package 與 ORM database engine 成功的與資料庫連接,並且使用 SQL/ORM 新增一筆聯絡人到資料庫中。但是別開心得太早,這邊我們會遇上兩個嚴重的問題,一個問題是我們將與資料庫的連線、寫入資料庫的邏輯以及資料庫模型寫在一起,就像個大泥球一樣遲早會過度耦合,未來需要創建更多資料表、欄位,處理更多資料庫的操作時,增加了額外的維護成本也徒增改壞系統的可能性,所以需要幫它瘦身一下按照功能拆分出來。

第二個問題則是,程式碼中關於資料庫的 Connection Strings/Connection URIs,裡頭都使用明碼的呈現無碼,像是資料庫密碼這種敏感資料被人拿到就不好了!又或者專案本身有版本控制,將服務中機敏資料的密鑰或密碼,也一起上到 Git、雲端,增加資料外洩的風險。將它寫入環境變數裡,可以大大的預防、降低風險。引入 .env 檔案做環境變數存放,這邊先不討論各個環境下(development, staging, production)該如何部署的情況。

分流...治理...分而治之

或許可以從歷史借鏡,過去的列強在治理殖民地時,都會根據種族、文化等分化治理,讓本來就關係不好的兩派(或多派)不會起來造反。扯遠了,db.py 一份檔案也能按照邏輯拆成數份檔案,讓我們來分析一下。首先,來盤點一下它做了幾件事情:

  1. 連結資料庫
  2. 資料表模型
  3. 操作資料庫

那是不是能先朝著這三個方面優化。緊接著將 db.py 在額外拆出 crud.pymodels.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 檔案,將資料整理過後寫入資料庫,也要判斷資料是否已經存在資料庫裡,資料表格式不符合預期該怎麼處理,那我們就下一章節見。

參考


#Python #environment variable #.env #python-dotenv #dotenv







Related Posts

Redux, useSelector, useDispatch

Redux, useSelector, useDispatch

後端系統架構圖

後端系統架構圖

W12_API 自己做 [ BE101 ] 實作之二

W12_API 自己做 [ BE101 ] 實作之二


Comments