Python 基于多环境的配置方式
部署到不同环境的应用会使用到各自的配置,如 Dev, QA, Stg, Prod 有自己的数据库等资源。Spring Boot 可采用 Profile 对应不同的环境,不同 Profile 选择自己的配置文件
那么在 Python 的项目中应该如何针对不同环境进行配置呢?大概有以下几种
第一种方式是本人推荐的,其他的方式只是在不同格式的配置文件中,按环境组织不同的配置值,其他方式的不同配置读入内存中基本是体现为字典变量。在 Python 配置中要支持像配置的 placeholder(像 ${host} 还需自己实现。
使用
配置环境变量
使用
cfg 是一个字典,所以上面的输出为
每次取值有些麻烦, 更高级一点的玩法是让 YAML 序列化为一个自定义对象,然后在自定义类中做文章,如新的 config.yml 配置中要告诉对应的类名
然后定义 Config 类,并使用相应的配置项
上面的代码输出
Python 的项目管理工具 Poetry 就是用 pyproject.toml 文件来管理依赖配置的。
使用 toml 一般安装
加载该 toml 文件
输出为
变更 APP_ENV 环境变量的值为 qa, prod, 会输出以下相应的值
Config 类的
TOML 配置文件的表现力很丰富,更强大的功能还有待于日后去发掘。
使用代码
输出
json.load() 方法也能由 JSON 格式数据反序列化为一个自定义的对象,直接用 object_hook 参数把有嵌套的 JSON 转换成一个自定义对象可就不那么容易了。但通过自定义的
或者整体 JSON 对象可转换为一个 SimpleNamespace
config.ini
使用
输出为
然后
Python 的 dotenv 有两个实现库
第一个是 python-dotenv, 安装
使用方式
输出
加载文件
先加载默认的 .env, 再加载环境相关的 .env_dev,这样 .env_dev 中的相同属性会覆盖 .env 中的配置。
还有一个 django 的实现
django-environ 中配置的值可以有类型,如 str, bool, int 等。它也像 python-dotenv 一样把 .env 文件中配置加到 os.environ 中去,因此既可通过 os.environ 来获取 .env 文件中配置的值,也能用它自己专有的 environ.Env() 的方式取得值。
另外,比起 python-dotenv 弱的地方就是它不支持 placeholder 的解析,.env 配置中的 ${DOMAIN} 将会被原样输出。 永久链接 https://yanbin.blog/python-multi-envs-configurations/, 来自 隔叶黄莺 Yanbin's Blog
[版权声明]
本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。
application-${profile}.properties。本人还是偏爱在同一个文件中分组配置,容易查错与编辑,类如在 application.properties 文件中以下面的方式1db.host=aaa
2%dev.db.host=bbb
3%prod.db.host=ccc那么在 Python 的项目中应该如何针对不同环境进行配置呢?大概有以下几种
- 不同环境的 Config 类
- YAML 文件
- TOML 文件
- JSON 文件
- INI 文件
- dotenv(.env) 文件
第一种方式是本人推荐的,其他的方式只是在不同格式的配置文件中,按环境组织不同的配置值,其他方式的不同配置读入内存中基本是体现为字典变量。在 Python 配置中要支持像配置的 placeholder(像 ${host} 还需自己实现。
不同环境的 Config 类
在同一个文件中配置,方便用点号引用 1import os
2
3class Config:
4 DB_HOST = "qa.example.com"
5 DB_USER = "sa"
6
7class DevConfig(Config):
8 DB_HOST = "dev.example.com"
9
10class QAConfig(Config):
11 pass
12
13class ProdConfig(Config):
14 DB_HOST = "prod.example.com"
15
16mapping = {
17 'dev': DevConfig,
18 'qa': QAConfig,
19 'prod': ProdConfig
20}
21
22APP_ENV = os.environ.get('APP_ENV', 'dev').lower()
23config = mapping[APP_ENV]()使用
1from config import config
2
3print(config.DB_HOST)配置环境变量
APP_ENV 为 dev, qa, 或 prod 会输出不同的 DB_HOST 值YAML 文件
需安装依赖 pyyamlpip install pyyamlconfig.yml 文件配置
1default:
2 db_host: qa.example.com
3
4dev:
5 db_host: dev.example.com
6
7qa:
8
9prod:
10 db_host: prod.example.com 使用
1import yaml
2from yaml import Loader
3
4with open("config.yml") as ymlfile:
5 cfg = yaml.load(ymlfile, Loader)
6
7print(type(cfg))
8print(cfg)cfg 是一个字典,所以上面的输出为
<class 'dict'>如果结合环境变量 APP_ENV 从 cfg 中获取配置值
{'default': {'db_host': 'qa.example.com'}, 'dev': {'db_host': 'dev.example.com'}, 'qa': None, 'prod': {'db_host': 'prod.example.com'}}
1import os
2
3APP_ENV = os.environ.get('APP_ENV', 'dev').lower()
4print(cfg.get(APP_ENV, "default")["db_host"])每次取值有些麻烦, 更高级一点的玩法是让 YAML 序列化为一个自定义对象,然后在自定义类中做文章,如新的 config.yml 配置中要告诉对应的类名
1--- !Config
2
3default:
4 db_host: qa.example.com
5
6dev:
7 db_host: dev.example.com
8
9qa:
10
11prod:
12 db_host: prod.example.com然后定义 Config 类,并使用相应的配置项
1import yaml
2from yaml import Loader
3import os
4
5APP_ENV = os.environ.get('APP_ENV', 'dev').lower()
6
7class Config(yaml.YAMLObject):
8 yaml_tag = u'!Config'
9
10 def __int__(self, default, dev, qa, prod):
11 self.default = default
12 self.dev = dev
13 self.qa = qa
14 self.prod = prod
15
16 def __getitem__(self, item):
17 env_conf = getattr(self, APP_ENV) if hasattr(self, APP_ENV) else self.default
18 env_conf = env_conf if env_conf else {}
19 return env_conf[item] if item in env_conf else self.default[item]
20
21
22with open("config.yml") as ymlfile:
23 cfg = yaml.load(ymlfile, Loader)
24
25print(type(cfg))
26
27print(cfg["db_host"])上面的代码输出
<class '__main__.Config'>再改变 APP_ENV 环境变量为 qa 和 prod 时对应的
dev.example.com
cfg["db_host"] 的值分别为qa.example.com
prod.example.com
TOML 文件
TOML(Tom's Obvious Minimal Language),初看它的格式像 ini 文件,其实它对 ini 格式强悍许多,支持丰富的数据类型,如布尔型,整数,浮点数,时间,日期,列表和字典等,下面是官方的一个配置样例 1# This is a TOML document
2
3title = "TOML Example"
4
5[owner]
6name = "Tom Preston-Werner"
7dob = 1979-05-27T07:32:00-08:00
8
9[database]
10enabled = true
11ports = [ 8000, 8001, 8002 ]
12data = [ ["delta", "phi"], [3.14] ]
13temp_targets = { cpu = 79.5, case = 72.0 }
14
15[servers]
16
17[servers.alpha]
18ip = "10.0.0.1"
19role = "frontend"
20
21[servers.beta]
22ip = "10.0.0.2"
23role = "backend"Python 的项目管理工具 Poetry 就是用 pyproject.toml 文件来管理依赖配置的。
使用 toml 一般安装
pip install toml把前面的 config.yml 文件转换为 config.toml 文件,内容如下
1[default]
2db_host="qa.example.com"
3
4[dev]
5db_host= "dev.example.com"
6
7[qa]
8
9[prod]
10db_host="prod.example.com"加载该 toml 文件
1import toml
2
3with open('config.toml') as tomlfile:
4 cfg = toml.load(tomlfile)
5
6print(type(cfg))
7print(cfg)输出为
<class 'dict'>要取随 APP_ENV 环境而变的 db_host 的话,代码可实现为
{'default': {'db_host': 'qa.example.com'}, 'dev': {'db_host': 'dev.example.com'}, 'qa': {}, 'prod': {'db_host': 'prod.example.com'}}
1import toml
2import os
3
4APP_ENV = os.environ.get('APP_ENV', 'dev').lower()
5
6class Config:
7 def __init__(self, default, dev, qa, prod):
8 self.default = default
9 self.dev = dev
10 self.qa = qa
11 self.prod = prod
12
13 def __getitem__(self, item):
14 env_conf = getattr(self, APP_ENV) if hasattr(self, APP_ENV) else self.default
15 env_conf = env_conf if env_conf else {}
16 return env_conf[item] if item in env_conf else self.default[item]
17
18
19with open('config.toml') as tomlfile:
20 cfg = Config(**toml.load(tomlfile))
21
22print(cfg['db_host'])变更 APP_ENV 环境变量的值为 qa, prod, 会输出以下相应的值
qa.example.com prod.example.com
Config 类的
__init__ 和 __getitem__ 方法实现与前面的完全一样。TOML 配置文件的表现力很丰富,更强大的功能还有待于日后去发掘。
JSON 文件
上面相应的配置文件变成 config.json 就是 1{
2 "default": {
3 "db_host": "qa.example.com"
4 },
5 "dev": {
6 "db_host": "dev.example.com"
7 },
8 "qa": {},
9 "prod": {
10 "db_host": "prod.example.com"
11 }
12}使用代码
1import json
2import os
3
4with open("config.json") as jsonfile:
5 cfg = json.load(jsonfile)
6
7print(type(cfg))
8print(cfg)
9
10APP_ENV = os.environ.get('APP_ENV', 'dev').lower()
11env_conf = cfg.get(APP_ENV)
12env_conf = env_conf if env_conf else cfg['default']
13print(env_conf['db_host'])输出
<class 'dict'>切换 APP_ENV 环境变量测试不同环境下的 db_host 值
{'default': {'db_host': 'qa.example.com'}, 'dev': {'db_host': 'dev.example.com'}, 'qa': {}, 'prod': {'db_host': 'prod.example.com'}}
qa.example.com
json.load() 方法也能由 JSON 格式数据反序列化为一个自定义的对象,直接用 object_hook 参数把有嵌套的 JSON 转换成一个自定义对象可就不那么容易了。但通过自定义的
__init__ 方法就和前面 YAML 的例子差不多了 1import json
2
3class Config:
4 # 实现代码与前方 TOML 中的 Config 完全相同,故省略
5
6with open("config.json") as jsonfile:
7 cfg = Config(**json.load(jsonfile))
8
9
10print(cfg["db_host"])或者整体 JSON 对象可转换为一个 SimpleNamespace
1from types import SimpleNamespace<br/><br/>
2with open("config.json") as jsonfile:
3 namespace = json.load(jsonfile, object_hook=lambda d: SimpleNamespace(**d))
4 # 再把 namespace 转换为 Config 对象INI 文件
ini 文件以前广泛应用在 Windows 中作为配置文件的格式,Python 也内置了对它的支持,格式上有点像 TOML 但它不支持嵌套类型。这里只提下 INI 文件的简单读取config.ini
1[default]
2db_host=qa.example.com
3
4[dev]
5db_host=dev.example.com
6
7[qa]
8
9[prod]
10db_host=prod.example.com使用
1import configparser
2
3cfg = configparser.ConfigParser()
4cfg.read("config.ini")
5print(type(cfg))
6host = cfg['dev']['db_host']
7print(host)输出为
<class 'configparser.ConfigParser'>由于只有一个层次的 Section 系列,不易于扩展,实际中应用较为狭窄,不作细究。
dev.example.com
dotenv(.env) 文件
基本思路是把.env 文件中的配置转换为环境变量,可由 os.environ().get(key) 获得,相当于 Linux 下的环境配置 env.sh1export DOMAIN=example.org
2export ADMIN_EMAIL=admin@${DOMAIN}然后
source env.sh相应的 DOMAIN 和 ADMIN_EMAIL 就出现在了
env 列出的环境变量中Python 的 dotenv 有两个实现库
第一个是 python-dotenv, 安装
pip install python-dotenv我们在当前目录中创建一个
.env 文件,其中内容为1# Development settings
2DOMAIN=example.org
3ADMIN_EMAIL=admin@${DOMAIN}
4ROOT_URL=${DOMAIN}/app使用方式
1from dotenv import load_dotenv
2import os
3
4load_dotenv()
5
6print(os.environ.get("DOMAIN"))
7print(os.environ.get("ADMIN_EMAIL"))输出
example.org
admin@example.org
load_dotenv() 可以指定不同的文件, 例如采用基于环境区分的文件命名- .env -- 默认的配置
- .env_dev
- .env_qa
- .env_prod
加载文件
1from dotenv import load_dotenv
2import os
3
4load_dotenv('.env') # 先加载默认的 .env 文件
5
6APP_ENV = os.environ.get('APP_ENV', 'dev').lower()
7load_dotenv(f'.env_{APP_ENV}', override=True) # 再加载环境相关的,
8
9print(os.environ)先加载默认的 .env, 再加载环境相关的 .env_dev,这样 .env_dev 中的相同属性会覆盖 .env 中的配置。
还有一个 django 的实现
django-environ, 但其中夹带了太多的私货, 如 environ.Env() 中有一些特定的配置项(db_url, cache_url 等),严格来说,它算不上通用 dotenv 实现。它加载 .env 文件时的行为与 python-dotenv 类似,如 1import environ
2import os
3
4env = environ.Env()
5
6env.read_env() # 加载 .env 文件
7APP_ENV = os.environ.get('APP_ENV', 'dev').lower()
8
9env.read_env(f'.env_{APP_ENV}', overwrite=True) # 加载环境相关的,如 .env_qa
10
11print(os.environ)
12print(os.environ["ADMIN_EMAIL"])
13print(env.str("ADMIN_EMAIL"))django-environ 中配置的值可以有类型,如 str, bool, int 等。它也像 python-dotenv 一样把 .env 文件中配置加到 os.environ 中去,因此既可通过 os.environ 来获取 .env 文件中配置的值,也能用它自己专有的 environ.Env() 的方式取得值。
另外,比起 python-dotenv 弱的地方就是它不支持 placeholder 的解析,.env 配置中的 ${DOMAIN} 将会被原样输出。 永久链接 https://yanbin.blog/python-multi-envs-configurations/, 来自 隔叶黄莺 Yanbin's Blog
[版权声明]
本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。