首页 存档 技术 查看内容

谈 Python 项目的配置管理

2018-3-30 13:00 |来自: 互联网 447 0

摘要: 原文:https://www.keakon.net/2016/10/22/Python项目的配置管理 作者:keakon 全文约 5874 字,读完可能需要 9 分钟。 每次开始一个新的 Python 项目,我都会为怎么管理配置文件而头疼。不过在迁移我的博客时,终 ...

原文:https://www.keakon.net/2016/10/22/Python项目的配置管理

作者:keakon

全文约 5874 字,读完可能需要 9 分钟。

每次开始一个新的 Python 项目,我都会为怎么管理配置文件而头疼。不过在迁移我的博客时,终于有空花了点时间,把这件事想清楚。

一年多的时间过去了,一切似乎都很顺利,连我在知乎所做的新项目也沿用了该方案,于是决定把解决方案记录下来。

先说说我要解决什么哪些问题吧:

  1. 可以区分各种环境。

在开发、测试和生产等环境,都可能用到不同的配置,所以能区分它们是一个很基本的需求。

  1. 可以有通用的配置项。

各种环境的配置中,需要修改的只占一小部分。因此通用的部分应该不需要重复定义,否则会带来维护成本。

  1. 可以分成多个部分/模块。

随着配置项的增多,找起配置来会花大量时间,所以划分它们对维护配置很有帮助。

  1. 可以直接使用 Python 代码。

从文本文件中解析出变量值太麻烦,而且不方便生成批量的数据(例如数组),也不好通过函数调用来生成配置值(例如获取文件路径)。

  1. 可以将公开和私有的配置文件分开管理。

在开源项目中,应只包含公开的配置项,而不包含私有的配置。不过这个需求对私有项目而言,没什么意义。

工作中我先后使用了几种方式,主要使用的就两种:

  1. 为每个环境分别写一个配置文件,到相应的环境里,将该环境的配置文件软链接到正确的路径。

  2. 使用分布式的配置服务,从远程获取配置。

前者用起来其实蛮麻烦的,特别是想在本地跑单元测试时,需要替换成单元测试环境的配置文件。所以我又把环境变量给加了进来,检测到指定的环境变量,就加载单元测试的配置。而其他几个需求也能勉强实现,不过并不优雅。

后者不能直接使用 Python 代码,网络不好时需要降级成使用本地缓存,获取配置服务器的地址需要配置,配置服务器自己也需要配置,而且配置服务器还可能挂掉(知乎内网遇到过全部五台配置服务器都挂掉的情况),所以我用得比较少。

其实仔细想想就能发现,「使用 Python 代码」也就意味着是 Python 源文件,「有通用的配置项」用 Python 实现就是继承,似乎没更好的选择了。

于是定义一个 Config 类,让其他环境的配置都继承这个类:

  1. # config/default.py

  2. class Config(object):

  3. DEBUG_MODE = True

  4. PORT = 12345

  5. COOKIE_SECRET = 'default'

  6. REDIS_CONFIG = {'host': 'localhost', 'port': 6379, 'db': 0}

  7. # ...

  1. # config/development.py

  2. from .default import Config

  3. class DevelopmentConfig(Config):

  4. COOKIE_SECRET = 'dev'

  1. # config/unit_testing.py

  2. from .default import Config

  3. class UnitTestingConfig(Config):

  4. REDIS_CONFIG = {'host': 'localhost', 'port': 6379, 'db': 1}

  1. # config/production.py

  2. from .default import Config

  3. class ProductionConfig(Config):

  4. COOKIE_SECRET = '...'

  5. REDIS_CONFIG = {'unix_socket_path': '/tmp/redis.sock'}

为了让每种环境都只有一个配置生效,还需要加一个策略:

  1. # config/__init__.py

  2. import logging

  3. import os

  4. env = os.getenv('ENV') # 可以改成其他名字,自己进行设置

  5. try:

  6. if env == 'PRODUCTION':

  7. from .production import ProductionConfig as CONFIG

  8. logging.info('Production config loaded.')

  9. elif env == 'TESTING':

  10. from .testing import TestingConfig as CONFIG

  11. logging.info('Testing config loaded.')

  12. elif env == 'UNIT_TESTING':

  13. from .unit_testing import UnitTestingConfig as CONFIG

  14. logging.info('Unit testing config loaded.')

  15. else: # 默认使用本地开发环境的配置,省去设置环境变量的环节

  16. from .development import DevelopmentConfig as CONFIG

  17. logging.info('Development config loaded.')

  18. except ImportError:

  19. logging.warning('Loading config for %s environment failed, use default config instead.', env or 'unspecified')

  20. from .default import Config as CONFIG

这样只需要在跑应用前,设置不同的环境变量即可。如果是用 Supervisor 维护进程的话,加上一行environment=ENV="PRODUCTION"配置即可。

当然还可以加其他的规则,例如没环境变量时,再检查机器名等。

现在前两个需求都解决了,再来看分模块的功能。

这个需求正好对应 Python 的 package,于是把每个配置文件改成一个 package 即可:

接着是如何同时满足第二和第三个需求。

举例来说,有这样的配置:

  1. # config/default.py

  2. class Config(object):

  3. ROOT_PATH = '/'

  4. LOGIN_PATH = ROOT_PATH 'login'

  5. SCHEME = 'http'

  6. DOMAIN = 'localhost'

  7. ROOT_URL = '%s://%s%s' % (SCHEME, DOMAIN, ROOT_PATH)

  1. # config/production.py

  2. from .default import Config

  3. class ProductionConfig(Config):

  4. ROOT_PATH = '/blog/'

  5. LOGIN_PATH = ROOT_PATH 'login'

  6. DOMAIN = 'www.keakon.net'

  7. ROOT_URL = '%s://%s%s' % (Config.SCHEME, DOMAIN, ROOT_PATH)

其中,LOGINPATH 和 LOGINURL 的设置逻辑其实是一样的,但值却不同,在 ProductionConfig 中重新赋值一次有点不太优雅。

于是把这些设置提取出来,在基本设置初始化以后,再进行设置:

  1. class _AfterMeta(type):

  2. def __init__ 声明:文章版权归原作者所有 部分文章转自互联网 如有侵权请联系 [邮箱地址] 删除


路过

雷人

握手

鲜花

鸡蛋

相关分类

返回顶部