機翻,如有差異,請查看原文地址 https://hackersandslackers.com/simplify-your-python-projects-configuration/
有一天,我們每個人都會死。也許我們會光榮地走出去,過上幸福的生活后蓋章。當我們吸取了無法再繼續的無用職業的最后一根稻草時,我們中的一些人可能會內心死亡。無論您的死亡是肉體死亡還是精神死亡,都可以肯定有一件事:您的雇主和同事會認為您永遠對他們死。
辦公文化使奇怪的成語永存,我最喜歡的是永恆的“被公共汽車撞”的陳詞濫調。多年來,每家公司都有相當一部分經驗豐富的員工,他們積累了寶貴的知識。隨着公司發現自己越來越依賴這些貢獻者,組織上的謝意開始轉向一種偏執狂。沒有人會懷疑:“ 如果我們最好的員工被公交車撞到怎么辦?
我感謝一個組織的詩意正義,在剝削員工后無奈。也就是說,還有其他原因可確保您編寫的代碼易於他人閱讀和使用。如果計划構建可繼續運行的軟件,則需要從邏輯上構建應用程序開始。讓我們從第一個方框開始:項目配置。
我們可以使用許多文件類型來存儲和訪問整個項目中的重要變量。諸如ini,YAML或what-have 等文件類型都具有在結構化(或非結構化)層次結構中存儲信息的獨特方式。根據項目的性質,這些文件結構中的每一個都可以很好地為您服務或妨礙您的工作。我們將研究所有這些選項的優勢,以及如何使用其相應的Python庫解析這些配置。
認識競爭者
格式化的方法不止一種,但是在現代軟件中格式化配置文件的方法甚至更多。我們將介紹一些用於處理項目配置的最常見文件格式(ini,toml,yaml,conf,json,env)和解析它們的Python庫。
INI文件
ini文件可能是我們可以使用的最直接的配置文件。ini文件非常適合較小的項目,主要是因為這些文件僅支持1級深的層次結構。ini文件本質上是平面文件,但變量可以屬於組。下面的示例演示了具有相同主題的變量如何可以歸入一個通用標題,例如_[DATABASE]或[LOGS]_:
config.ini
[APP]
ENVIRONMENT = development
DEBUG = False
[DATABASE]
USERNAME: root
PASSWORD: p@ssw0rd
HOST: 127.0.0.1
PORT: 5432
DB: my_database
[LOGS]
ERRORS: logs/errors.log
INFO: data/info.log
[FILES]
STATIC_FOLDER: static
TEMPLATES_FOLDER: templates
這種結構無疑使人們更容易理解事物,但是這種結構的實用性超出了美學。讓我們使用Python的configparser庫解析此文件,以了解實際情況。我們首先將test.ini的內容保存到一個名為config的變量中:
config.py
import configparser
config = configparser.ConfigParser()
config.read('~/Desktop/config.ini')
調用read()
上的ini文件確實比普通商店的數據更為; 實際上,我們的config變量現在是其自己的唯一數據結構,從而允許我們使用各種方法來讀取和寫入配置值。嘗試跑步print(config)
看看自己:
<configparser.ConfigParser object at 0x10e58c390>
存在配置文件只是為了提取值。configparser允許我們以多種方式執行此操作。下面的每一行都返回127.0.0.1
:
config.get('DATABASE', 'HOST')
config['DATABASE']['HOST']
對於期望接收特定數據類型的值,configparser有許多類型檢查方法來檢索我們正在尋找的數據結構中的值。該命令config.getboolean('APP', 'DEBUG')
將正確返回布爾值False,而不是一個字符串“ False”,這顯然對我們的應用程序有問題。如果將我們的值DEBUG
設置為布爾值以外的值,config.getboolean()
則會拋出錯誤。configparser還有許多其他類型檢查方法,例如getint()
,getfloat()
等等。
configparser的功能 並不止於此。我們可以詳細介紹該庫編寫新配置值,檢查鍵是否存在等的能力,但我們不可以。
TOML文件
乍看起來,TOML文件似乎與ini文件共享_某些_語法相似之處,但支持更廣泛的數據類型以及值本身之間的關系。TOML文件還迫使我們提前更清楚地了解數據結構,而不是像configparser那樣_在_解析_后_確定它們。
在Python中解析TOML文件由一個適當地稱為toml的庫處理,在我們去那里之前,讓我們看看TOML的炒作是什么。
TOML變量類型
TOML文件通過鍵/值對定義變量,方式與ini文件類似。Ť HESE對被稱為_密鑰_。但是,與ini文件不同,TOML希望將鍵的值存儲為打算用作鍵的數據類型。打算解析為字符串的變量_必須_作為值存儲在引號中,而布爾值必須存儲為原始的true或false值。這消除了我們配置的許多歧義:我們不需要諸如getboolean()
TOML文件之類的方法。
TOML文件可以支持令人印象深刻的變量類型目錄。TOML支持的一些更令人印象深刻的變量類型包括DateTime,本地時間,數組,float甚至十六進制值:
config.toml
[project]
name: "Faceback"
description: "Powerful AI which renders the back of somebody's head, based on their face."
version: "1.0.0"
updated: 1979-05-27T07:32:00Z
author = "Todd Birchard"
...
TOML文件結構
TOML文件中帶括號的部分稱為表。密鑰可以存在於表的內部或外部,如下面的示例所示。您會注意到,這些並不是TOML文件中僅有的兩個元素:
config.toml
# Keys
title = "My TOML Config"
# Tables
[project]
name = "Faceback"
description = "Powerful AI which renders the back of somebody's head, based on their face."
version = "1.0.0"
updated = 1979-05-27T07:32:00Z
author = "Todd Birchard"
[database]
host = "127.0.0.1"
password = "p@ssw0rd"
port = 5432
name = "my_database"
connection_max = 5000
enabled = true
# Nested `tables`
[environments]
[environments.dev]
ip = "10.0.0.1"
dc = "eqdc10"
[environments.staging]
ip = "10.0.0.2"
dc = "eqdc10"
[environments.production]
ip = "10.0.0.3"
dc = "eqdc10"
# Array of Tables
[[testers]]
id = 1
username = "JohnCena"
password = "YouCantSeeMe69"
[[testers]]
id = 3
username = "TheRock"
password = "CantCook123"
如表中所示,TOML支持“嵌套表”的概念,該[environments]
表后面帶有多個子表。通過使用點符號,我們能夠創建表的關聯,這意味着它們是同一元素的不同實例。
同樣有趣的是概念“表列”,它做什么用發生[[testers]]
。雙括號中的表會自動添加到數組中,其中數組中的每個項目都是具有相同名稱的表。可視化此處發生情況的最佳方法是使用JSON等價物:
{
"testers": [
{ "id": 1, "username": "JohnCena", "password": "YouCantSeeMe69" },
{ "id": 2, "username": "TheRock", "password": "CantCook123" }
]
}
解析TOML
足夠使用TOML作為標准,讓我們獲取數據:
import toml
config = toml.load('/Users/toddbirchard/Desktop/config.toml')
print(config)
加載TOML文件立即返回字典:
{'title': 'My TOML Config',
'project': {'name': 'Faceback',
'description': "Powerful AI which renders the back of somebody's head, based on their face.",
'version': '1.0.0',
'updated': datetime.datetime(1979, 5, 27, 7, 32, tzinfo=<toml.tz.TomlTz object at 0x107b82390>),
'author': 'Todd Birchard'},
'database': {'host': '127.0.0.1',
'password': 'p@ssw0rd',
'port': 5432,
'name': 'my_database',
'connection_max': 5000,
'enabled': True},
'environments': {'dev': {'ip': '10.0.0.1', 'dc': 'eqdc10'},
'staging': {'ip': '10.0.0.2', 'dc': 'eqdc10'},
'production': {'ip': '10.0.0.3', 'dc': 'eqdc10'}},
'testers': [{'id': 1, 'username': 'JohnCena', 'password': 'YouCantSeeMe69'},
{'id': 1, 'username': 'TheRock', 'password': 'CantCook123'}]}
從config抓取值就像使用任何字典一樣容易:
# Retrieving a dictionary
config['project']
config.get('project')
# Retrieving a value
config['project']['author']
config.get('project').get('author')
YAML配置
YAML文件格式已經成為配置的人群首選,大概是因為它們易於閱讀。那些熟悉YAML規范的人會告訴您,YAML _遠_不是一種優雅的文件格式,但這似乎並沒有阻止任何人。
YAML文件利用空格來定義變量層次結構,這似乎引起了許多開發人員的共鳴。查看示例YAML配置可能是什么樣的:
config.yaml
appName: appName
logLevel: WARN
AWS:
Region: us-east-1
Resources:
EC2:
Type: "AWS::EC2::Instance"
Properties:
ImageId: "ami-0ff8a91507f77f867"
InstanceType: t2.micro
KeyName: testkey
BlockDeviceMappings:
-
DeviceName: /dev/sdm
Ebs:
VolumeType: io1
Iops: 200
DeleteOnTermination: false
VolumeSize: 20
Lambda:
Type: "AWS::Lambda::Function"
Properties:
Handler: "index.handler"
Role:
Fn::GetAtt:
- "LambdaExecutionRole"
- "Arn"
Runtime: "python3.7"
Timeout: 25
TracingConfig:
Mode: "Active"
routes:
admin:
url: /admin
template: admin.html
assets:
templates: /templates
static: /static
dashboard:
url: /dashboard
template: dashboard.html
assets:
templates: /templates
static: /static
account:
url: /account
template: account.html
assets:
templates: /templates
static: /static
databases:
cassandra:
host: example.cassandra.db
username: user
password: password
redshift:
jdbcURL: jdbc:redshift://<IP>:<PORT>/file?user=username&password=pass
tempS3Dir: s3://path/to/redshift/temp/dir/
redis:
host: hostname
port: port-number
auth: authentication
db: databaseconfig.yaml
顯而易見,YAML配置_易於編寫和理解_。上面的YAML文件能夠完成我們在TOML文件中看到的相同類型的復雜層次結構。但是,我們不需要顯式設置變量數據類型,也不需要花時間來理解諸如表或表****數組之類的概念。可以輕易地辯稱,YAML的易用性並不能證明其缺點。不要花太多時間考慮這個問題:我們在這里談論配置文件。
我認為我們都可以同意的一點是,YAML肯定比JSON配置更勝一籌。這是與JSON文件相同的配置:
config.json
{
"appName": "appName",
"logLevel": "WARN",
"AWS": {
"Region": "us-east-1",
"Resources": {
"EC2": {
"Type": "AWS::EC2::Instance",
"Properties": {
"ImageId": "ami-0ff8a91507f77f867",
"InstanceType": "t2.micro",
"KeyName": "testkey",
"BlockDeviceMappings": [
{
"DeviceName": "/dev/sdm",
"Ebs": {
"VolumeType": "io1",
"Iops": 200,
"DeleteOnTermination": false,
"VolumeSize": 20
}
}
]
}
},
"Lambda": {
"Type": "AWS::Lambda::Function",
"Properties": {
"Handler": "index.handler",
"Role": {
"Fn::GetAtt": [
"LambdaExecutionRole",
"Arn"
]
},
"Runtime": "python3.7",
"Timeout": 25,
"TracingConfig": {
"Mode": "Active"
}
}
}
}
},
"routes": {
"admin": {
"url": "/admin",
"template": "admin.html",
"assets": {
"templates": "/templates",
"static": "/static"
}
},
"dashboard": {
"url": "/dashboard",
"template": "dashboard.html",
"assets": {
"templates": "/templates",
"static": "/static"
}
},
"account": {
"url": "/account",
"template": "account.html",
"assets": {
"templates": "/templates",
"static": "/static"
}
}
},
"databases": {
"cassandra": {
"host": "example.cassandra.db",
"username": "user",
"password": "password"
},
"redshift": {
"jdbcURL": "jdbc:redshift://<IP>:<PORT>/file?user=username&password=pass",
"tempS3Dir": "s3://path/to/redshift/temp/dir/"
},
"redis": {
"host": "hostname",
"port": "port-number",
"auth": "authentication",
"db": "database"
}
}
}
告訴我一個比YAML更喜歡JSON的人,我將向您展示一個受虐狂,否認他們對AWS的供應商鎖定。
在Python中解析YAML
我建議使用Python _Confuse_庫(一個軟件包名稱,一定會引起公司信息安全團隊的注意)。
Confuse允許我們與YAML文件進行交互,幾乎與JSON進行交互,除了.get()
在遍歷樹層次結構結束時指定的例外外,如下所示:
config = confuse.Configuration('MyApp', __name__)
config['AWS']['Lambda']['Runtime'].get()
.get()可以接受數據類型值,例如_int。_這樣做可以確保我們獲得的值實際上是我們所期望的模式,這是一個很好的功能。
驗證者
Confuse的文檔詳細介紹了從YAML文件中提取的值的其他驗證方法。方法,如as_filename()
,as_number()
和as_str_seq()
基本上做你希望他們到什么。
CLI配置
Confuse還進入了構建CLI的領域,允許我們使用YAML文件來通知可傳遞給CLI的參數及其潛在值:
config = confuse.Configuration('myapp')
parser = argparse.ArgumentParser()
parser.add_argument('--foo', help='a parameter')
args = parser.parse_args()
config.set_args(args)
print(config['foo'].get())
您可以在這里做很多事情。
.ENV文件
環境變量是一種將敏感信息保持在項目代碼庫之外的好方法。我們可以用多種不同的方式存儲環境變量,最簡單的方法是通過命令行:
$ export MY_VARIABLE=AAAAtpl%2Bkvro%2BoQ9wRg77VUEpQv%2F
只要您當前的終端會話處於打開狀態,以這種方式存儲的變量將一直存在,因此在測試之外對我們沒有多大幫助。如果我們要MY_VARIABLE
堅持下去,可以將以上export
行添加到.bash_profile(或等效文件)中,以確保MY_VARIABLE
在系統范圍內始終存在。
特定於項目的變量更適合駐留在我們項目目錄中的.env文件。為了上帝的愛,請勿將這些文件提交給GITHUB。
假設我們有一個.env文件,其中包含與項目相關的變量,如下所示:
FLASK_ENV=development
FLASK_APP=wsgi.py
COMPRESSOR_DEBUG=True
STATIC_FOLDER=static
TEMPLATES_FOLDER=templates
.env
現在,我們可以使用內置的Python提取這些值os.environ
:
config.py
"""App configuration."""
from os import environ
class Config:
"""Set configuration vars from .env file."""
# General Config
SECRET_KEY = environ.get('SECRET_KEY')
FLASK_APP = environ.get('FLASK_APP')
FLASK_ENV = environ.get('FLASK_ENV')
# Flask-Assets
LESS_BIN = environ.get('LESS_BIN')
ASSETS_DEBUG = environ.get('ASSETS_DEBUG')
LESS_RUN_IN_DEBUG = environ.get('LESS_RUN_IN_DEBUG')
隨便使用您想要的
顯然,有很多方法可以在Python中設置環境和項目變量。我們可能會花費一整天的時間來剖析配置文件類型的利弊。這是我們肯定不想過分思考的生活的一個方面。
此外,我需要反思自己的生活。我只寫了兩千個關於配置文件的利弊的詞,在意識到自己的生活毫無意義之前,我寧願忘記這些詞。