python配置文件INI/TOML/YAML/ENV的区别
机翻,如有差异,请查看原文地址 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中设置环境和项目变量。我们可能会花费一整天的时间来剖析配置文件类型的利弊。这是我们肯定不想过分思考的生活的一个方面。
此外,我需要反思自己的生活。我只写了两千个关于配置文件的利弊的词,在意识到自己的生活毫无意义之前,我宁愿忘记这些词。