python配置文件INI/TOML/YAML/ENV的区别

机翻,如有差异,请查看原文地址 https://hackersandslackers.com/simplify-your-python-projects-configuration/

有一天,我们每个人都会死。也许我们会光荣地走出去,过上幸福的生活后盖章。当我们吸取了无法再继续的无用职业的最后一根稻草时,我们中的一些人可能会内心死亡。无论您的死亡是肉体死亡还是精神死亡,都可以肯定有一件事:您的雇主和同事会认为您永远对他们死。

办公文化使奇怪的成语永存,我最喜欢的是永恒的“被公共汽车撞”的陈词滥调。多年来,每家公司都有相当一部分经验丰富的员工,他们积累了宝贵的知识。随着公司发现自己越来越依赖这些贡献者,组织上的谢意开始转向一种偏执狂。没有人会怀疑:“ 如果我们最好的员工被公交车撞到怎么办?

我感谢一个组织的诗意正义,在剥削员工后无奈。也就是说,还有其他原因可确保您编写的代码易于他人阅读和使用。如果计划构建可继续运行的软件,则需要从逻辑上构建应用程序开始。让我们从第一个方框开始:项目配置。

我们可以使用许多文件类型来存储和访问整个项目中的重要变量。诸如iniYAML或what-have 等文件类型都具有在结构化(或非结构化)层次结构中存储信息的独特方式。根据项目的性质,这些文件结构中的每一个都可以很好地为您服务或妨碍您的工作。我们将研究所有这些选项的优势,以及如何使用其相应的Python库解析这些配置。

认识竞争者

格式化的方法不止一种,但是在现代软件中格式化配置文件的方法甚至更多。我们将介绍一些用于处理项目配置的最常见文件格式(initomlyamlconfjsonenv)和解析它们的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希望将键的值存储为打算用作键的数据类型。打算解析为字符串的变量_必须_作为值存储在引号中,而布尔值必须存储为原始的truefalse值。这消除了我们配置的许多歧义:我们不需要诸如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中设置环境和项目变量。我们可能会花费一整天的时间来剖析配置文件类型的利弊。这是我们肯定不想过分思考的生活的一个方面。

此外,我需要反思自己的生活。我只写了两千个关于配置文件的利弊的词,在意识到自己的生活毫无意义之前,我宁愿忘记这些词。

posted @ 2020-06-09 16:11  jonnyan  阅读(5673)  评论(0编辑  收藏  举报