OpenStack 实现技术分解 (7) 通用库 — oslo_config
目录
前文列表
OpenStack 实现技术分解 (1) 开发环境 — Devstack 部署案例详解
OpenStack 实现技术分解 (2) 虚拟机初始化工具 — Cloud-Init & metadata & userdata
OpenStack 实现技术分解 (3) 开发工具 — VIM & dotfiles
OpenStack 实现技术分解 (4) 通用技术 — TaskFlow
OpenStack 实现技术分解 (5) 应用开发 — 使用 OpenStackClients 进行二次开发
OpenStack 实现技术分解 (6) 通用库 — oslo_log
扩展阅读
oslo.config — oslo.config 3.23.1.dev1 documentation
argparse - 命令行选项与参数解析(译)
Openstack Oslo.config 学习
oslo.config
(摘自官方文档) An OpenStack library for parsing configuration options from the command line and configuration files.
oslo.config 是一个 OpenStack 通用库, 用于解析并加载来自于 CLI 或配置文件中所提供的参数项或配置项.
oslo.config 属于 OpenStack 中复用率最高的通用库之一, 在网上能够轻易的找到其使用方法与示例, 并且在 oslo.config 的源码中(oslo_config/cfg.py
)也给出了丧心病狂的 443 行使用文档以及大量的注释, 其详尽程度简直前所未见. 所以本篇不再赘述 oslo.config 的使用方法, 主要介绍并记录 oslo.config 的源码实现, 使用技巧及其 单例特性.
本质上, oslo.config 是对 Python’s standard argparse library 标准库的封装, 主要解决了下列两个问题:
- 如何将配置文件中的配置项与 CLI 中的参数项进行了统一管理?
- 如何处理配置文件中与 CLI 中同名选项的优先级?
argparse
在阅读 oslo.config 源码之前, 首先要对 argparse 标准库有一定的了解. 准确来说我们需要对 argparse 定义的概念对象以及 argparse 的使用流程有所了解, 因为 oslo.config 仍然沿用了这些概念对象.
- parser(解析器): 使用 argparse 的第一步就是创建一个解析器对象, 并由解析器对象来完成指令行选项清单的预设与解析.
import argparse
# 创建解析器对象 parser
parser = argparse.ArgumentParser(add_help=True, description='This is a demo of argparse library.')
# 调用 argparse.ArgumentParser.add_argument method 预设指令行选项清单
# 只有在为解析器预设了指令行选项清单后, 解析器才能判别并接收解析指定的指令行选项
parser.add_argument("--show", "-s", desc="show", action="store", help="show message", default="You Know")
NOTE 1: 当参数 add_help=True 时, 会自动生成帮助手册. 即隐式的添加了 -h 选项
NOTE 2: argparse.ArgumentParser.add_argument
method 的原型为 add_argument(self, *args, **kwargs)
, 其中 args 元组冗余形参用于接收指令行选项的使用样式等信息, kwargs 字典冗余形参用于接收指令行选项对应的 dest/action/help/default 等功能定义.
action(动作): 如上例所示, 在向解析器添加指令行选项清单时, 需要指定在解析该指令行选项时触发的动作(action), 常用的 action 有
store/store_const/append/version
等, 具体含义请浏览扩展阅读.namespace(命名空间): 预设完指令行选项清单之后, 就可以从 CLI 接收选项输入并对其进行解析, 这需要调用
argparse.ArgumentParser.parse_args
method 来实现. 该 method 的实参缺省从sys.argv[1:]
(CLI 输入) 中获取, 但你也可以传递自己定义的参数列表, E.G.argparse.ArgumentParser.parse_args(['-s', 'Helloword'])
. 该 method 返回的对象就是 namespace 对象, namespace 对象中就以属性的形式保存了指令行选项解析后的值. 所以可以通过 namespace 对象来检索相应选项在 CLI 中指定的 values.
nsp = parser.parse_args(sys.argv[1:])
EXAMPLE:
import sys
import argparse
parser = argparse.ArgumentParser(add_help=True, description='This is a demo of argparse library.')
parser.add_argument('--show', '-s', action="store", help="show message", default="You Know")
nsp = parser.parse_args(sys.argv[1:])
print(nsp.show)
print('=' * 20)
print nsp
Output:
$ python test.py
You Know
====================
Namespace(show='You Know')
$ python test.py -h
usage: test.py [-h] [--show SHOW]
This is a demo of argparse library.
optional arguments:
-h, --help show this help message and exit
--show SHOW, -s SHOW show message
$ python test.py -s
usage: test.py [-h] [--show SHOW]
test.py: error: argument --show/-s: expected one argument
$ python test.py -s 'Hello world'
Hello world
====================
Namespace(show='Hello world')
$ python test.py --show 'Hello world'
Hello world
====================
Namespace(show='Hello world')
上述是 argparse 标准库的一个简单应用, 主要为了介绍其定义的概念对象, 更复杂的应用方式请浏览扩展阅读.
注意: 上文中反复使用了 指令行选项 这个词语, 是为了与后文中的 options 作出区分. 后文中 options 均为配置文件中的配置项与 CLI 中的参数项的统称, 因为 cfg 模块中大量应用了 OOP 的编程思想.
cfg.py
oslo_config.cfg
是 oslo.config 库的核心模块, 3274 行的代码实现了 oslo.config 提供的绝大部分功能. 其中最重要的类实现就是 Opt
和 ConfigOpts
.
class Opt
回到第一个问题: 如何将配置文件中的配置项与 CLI 中的参数项进行了统一管理?
答案就是 Opt
抽象类以及其衍生出的子类.
class Opt(object):
"""Base class for all configuration options.
The only required parameter is the option's name. However, it is
common to also supply a default and help string for all options.
从 Opt document 可以看出, Opt
是所有 configuration options 的基类, 衍生出了 IntOpt(Opt)/FloatOpt(Opt)/StrOpt(Opt)/...
等不同类型的 options 类, 对应了如下 option types:
==================================== ======
Type Option
==================================== ======
:class:`oslo_config.types.String` - :class:`oslo_config.cfg.StrOpt`
- :class:`oslo_config.cfg.SubCommandOpt`
:class:`oslo_config.types.Boolean` :class:`oslo_config.cfg.BoolOpt`
:class:`oslo_config.types.Integer` :class:`oslo_config.cfg.IntOpt`
:class:`oslo_config.types.Float` :class:`oslo_config.cfg.FloatOpt`
:class:`oslo_config.types.Port` :class:`oslo_config.cfg.PortOpt`
:class:`oslo_config.types.List` :class:`oslo_config.cfg.ListOpt`
:class:`oslo_config.types.Dict` :class:`oslo_config.cfg.DictOpt`
:class:`oslo_config.types.IPAddress` :class:`oslo_config.cfg.IPOpt`
:class:`oslo_config.types.Hostname` :class:`oslo_config.cfg.HostnameOpt`
:class:`oslo_config.types.URI` :class:`oslo_config.cfg.URIOpt`
==================================== ======
而且需要注意的是, Opt
还衍生出了 _ConfigFileOpt(Opt)
与 _ConfigDirOpt(Opt)
. 两者都实现了一个内部类 ConfigDirAction(argparse.Action)
与 ConfigFileAction(argparse.Action)
.
class ConfigFileAction(argparse.Action):
"""An argparse action for --config-file.
As each --config-file option is encountered, this action adds the
value to the config_file attribute on the _Namespace object but also
parses the configuration file and stores the values found also in
the _Namespace object.
"""
正如 ConfigFileAction document 所示, 这是一个 CLI 参数项 --config-file
的 argparse action, 这个 action 同时会解析 CLI 参数项 --config-file
并以 config_file 属性的形式添加到 namespace 对象中. 并且还会将 --config-file
选项指定的配置文件中的配置项解析后也以属性的形式添加到 namespace 中. 同理, --config-dir
也是一样的.
所以看到这里, 大家应该可以体会到, oslo.config 正是通过对 Opt
的封装与衍生出不同类型的 sub-Opt
子类来实现将 CLI 中的参数项 和 配置文件中的配置项 以 options 对象统一起来.
而且 Opt
中有几个 method 是比较重要的:
def _add_to_cli(self, parser, group=None)
def _add_to_cli(self, parser, group=None):
"""Makes the option available in the command line interface.
This is the method ConfigOpts uses to add the opt to the CLI interface
as appropriate for the opt type. Some opt types may extend this method,
others may just extend the helper methods it uses.
:param parser: the CLI option parser
:param group: an optional OptGroup object
"""
container = self._get_argparse_container(parser, group)
kwargs = self._get_argparse_kwargs(group)
prefix = self._get_argparse_prefix('', group.name if group else None)
deprecated_names = []
for opt in self.deprecated_opts:
deprecated_name = self._get_deprecated_cli_name(opt.name,
opt.group)
if deprecated_name is not None:
deprecated_names.append(deprecated_name)
self._add_to_argparse(parser, container, self.name, self.short,
kwargs, prefix,
self.positional, deprecated_names)
该 method 接收一个 parser(the CLI option parser), 主要用于 Makes the option available in the command line interface. 生成可用于 CLI 的 options. 类比上文中 argparse 的应用过程, 该 method 就是做的事情就是为 argparse.ArgumentParser.add_argument
准备好用于预设 options 的 args 和 kwargs 实参.
NOTE 1: Opt._add_to_cli
method 语句块中的 container(container: an argparse._ArgumentGroup object
) 对象才是 argparse 的 parser.
NOTE 2: 在该 method 语句块中调用的 Opt._add_to_argparse
才是真正将 args 和 kwargs 传入到 argparse 的解析器对象中的实现.
NOTE 3: 最后可以通过 Opt._get_from_namespace
来从命名空间中获取 options 的值.
class ConfigOpts
第二个问题: 如何处理配置文件中与 CLI 中同名选项的优先级?
答案就是: ConfigOpts
class ConfigOpts(collections.Mapping):
"""Config options which may be set on the command line or in config files.
ConfigOpts is a configuration option manager with APIs for registering
option schemas, grouping options, parsing option values and retrieving
the values of options.
It has built-in support for :oslo.config:option:`config_file` and
:oslo.config:option:`config_dir` options.
"""
从 ConfigOpts document 中的「ConfigOpts is a configuration option manager with APIs for registering option schemas, grouping options, parsing option values and retrieving the values of options.」可以看出, class ConfigOpts 是一个提供了 注册 option 与 option group, 解析 options, 检索 options 值 的管理接口. 所以同样由 ConfigOpts
来负责处理配置文件中与 CLI 中同名选项的优先级.
先来看 oslo.config 在程序中的使用例子, EXAMPLE:
from config import cfg
opts = [
cfg.StrOpt('bind_host', default='0.0.0.0'),
]
cli_opts = [
cfg.IntOpt('bind_port', default=9292),
]
CONF = cfg.CONF
CONF.register_opts(opts) # 此时没有注册任何 CLI 参数项或配置文件配置项, 仅仅注册了一些动态 options
CONF.register_cli_opts(cli_opts) # 注册 CLI 参数项
print CONF.bind_host
print CONF.bind_port
CONF(args=sys.argv[1:], default_config_files=['./test.conf']) # 接收 CLI 参数项与配置文件配置项
print CONF.bind_host
print CONF.bind_port
Output 1: 没有传递 CLI 参数项, 并且 test.conf 没有写入配置项
$ python test.py
0.0.0.0
9292
0.0.0.0
9292
可以看见当没有指定任何配置文件或 CLI 选项时, 只解析了动态定义的 options
Output 2: 没有传递 CLI 参数项, 但有写入 test.conf 配置项 bind_host/bind_port
$ python test.py
0.0.0.0
9292
192.168.0.1
55553
配置文件中的配置项会覆盖动态定义的 options.
Output 3: 有传递 CLI 参数项, 也有写入 test.conf 配置项 bind_host/bind_port
$ python test.py --config-file ./test.conf --bind_port 9090
0.0.0.0
9292
192.168.0.1
9090
CLI 参数项会覆盖配置文件中的配置项.
所以有两点是我们在使用 oslo.config 时需要注意的:
- 优先级: CLI 参数项 > 配置文件配置项 > 动态定义的 options
- 如果需要接收并解析最新的 CLI 参数项时, 需要实现 CLI 注册语句
CONF.register_cli_opts(cli_opts)
并且 传入 CLI 参数项值args=sys.argv[1:]
CONF 对象的单例模式
大家如果有使用 osls.config 的经历, 一定会有一个疑问: 为什么在不同模块中添加的 options 值能够通过 CONF 对象实现共用?
答案是: 其实没有什么共用一说, 因为在整个应用程序中的 CONF 对象都可能是同一个对象. 因为实例化 CONF 对象的 ConfigOpt 类应用了由 import
语句支持的单例模式.
先看一个例子, EXAMPLE:
- vim test.py
class ConfigOpts(object):
def foo(self):
pass
CONF = ConfigOpts()
- vim test_1.py
from test import CONF
print __name__, CONF, id(CONF)
- vim test_2.py
from test import CONF
import test_1
print __name__, CONF, id(CONF)
Output:
$ python test_2.py
test_1 <test.ConfigOpts object at 0x7fd347eec710> 140545421657872
__main__ <test.ConfigOpts object at 0x7fd347eec710> 140545421657872
可以看见在模块 test_1 和 test_2 中生成的 CONF 对象在内存中是同一个对象, 这是 Python 的特性决定的 作为 Python 的模块是天然的单例模式. 只要多个模块之间有互相 import
的实现, 那么这个模块之间所生成的类实例化对象就是同一个内存对象.
再回头看看 cfg 模块中的 CONF 对象是如何实例化的:
CONF = ConfigOpts()
这条语句是顶格代码, 所以当在程序中执行 from oslo_config import cfg
的时候, 就会实例化 class ConfigOpts 的实例对象 CONF, 满足单例模式的实现. 所以程序员在使用 oslo.config 的过程中, 会感到非常的便利, 因为整个程序中的 CONF 都可能是同一个 CONF, 那么程序之中的 options 传递就会显得那么的轻松与优雅.
OpenStack 实现技术分解 (7) 通用库 — oslo_config的更多相关文章
- OpenStack 实现技术分解 (6) 通用库 — oslo_log
目录 目录 前文列表 扩展阅读 日志级别 oslolog 初始化设置 DEMO oslolog 的相关配置项 oslolog 的日志级别 oslolog 的使用技巧 推荐使用 LOGdebug 的地方 ...
- Openstack 实现技术分解 (4) 通用技术 — TaskFlow
目录 目录 前文列表 扩展阅读 简介 基本概念 实现样例 最后 前文列表 Openstack 实现技术分解 (1) 开发环境 - Devstack 部署案例详解 Openstack 实现技术分解 (2 ...
- OpenStack 实现技术分解 (5) 应用开发 — 使用 OpenStackClients 进行二次开发
文件夹 文件夹 前文列表 參考阅读 前言 OpenStackClients 使用 OpenStackClients 获取 project_client object 的 demo 调用 project ...
- Openstack 实现技术分解 (3) 开发工具 — VIM & dotfiles
目录 目录 前文列表 扩展阅读 前言 插件管理 Vundle 主题 Solarized 浏览项目目录结构 Nerdtree Symbol 窗口 Tagbar 文件模糊查询 CtrlP 代码补全 You ...
- Openstack 实现技术分解 (1) 开发环境 — Devstack 部署案例详解
目录 目录 前言 系统环境 Devstack 下载源码 配置文件 local.conf & localrc 简易的环境脚本 openrc 部署 Devstack 自动化部署流程 部署案例 单节 ...
- Openstack 实现技术分解 (2) 虚拟机初始化工具 — Cloud-Init & metadata & userdata
目录 目录 前文列表 扩展阅读 系统环境 前言 Cloud-init Cloud-init 的配置文件 metadata userdata metadata 和 userdata 的区别 metada ...
- Google之Chromium浏览器源码学习——base公共通用库(一)
Google的优秀C++开源项目繁多,其中的Chromium浏览器项目可以说是很具有代表性的,此外还包括其第三开发开源库或是自己的优秀开源库,可以根据需要抽取自己感兴趣的部分.在研究.学习该项目前的时 ...
- Google之Chromium浏览器源码学习——base公共通用库(三)
本节将介绍base公共通用库中的containers,其包含堆栈.列表.集合.以及Most Recently Used cache(最近使用缓存模板). linked_list.h:一个简单的列表类型 ...
- Google之Chromium浏览器源码学习——base公共通用库(二)
上次提到Chromium浏览器中base公共通用库中的内存分配器allocator,其中用到了三方库tcmalloc.jemalloc:对于这两个内存分配器,个人建议,对于内存,最好是自己维护内存池: ...
随机推荐
- 一、模型验证CoreWebApi 管道方式(非过滤器处理)2(IApplicationBuilder扩展方法的另一种写法)
一. 自定义中间件类的方式用一个单独类文件进行验证处理 Configure下添加配置 //app.AddAuthorize(); AddAuthorize因为参数(this IApplicationB ...
- KVM虚拟化网卡管理
brctl常用命令 查看当前虚拟网桥状态 brctl show 添加一个网桥 addbr 删除一个网桥 delbr 添加网口 addif 删除网口 delif VALN LAN 表示 Local Ar ...
- scrapy 知乎关键字爬虫spider代码
以下是spider部分的代码.爬知乎是需要登录的,建议使用cookie就可以了,如果需要爬的数量预计不多,请不要使用过大的线程数量,否则会过快的被封杀,需要等十几个小时账号才能重新使用,比起损失的这十 ...
- itchat的使用
python 微信接口 -- itchat 文档 发表于 2018-03-16 | 分类于 Python | itchat 一. 安装 $ pip install itchat 特殊的字典使用方式通过 ...
- 第02课:启动GDB调试
使用GDB调试程序一般有三种方式: gdb filename gdb attach pid dgb filename corename 也对应这本节课的核心内容: 直接调试目标程序 附加进程 调试co ...
- 【NOIP2016提高A组模拟8.19】(雅礼联考day2)公约数
题目 给定一个正整数,在[1,n]的范围内,求出有多少个无序数对(a,b)满足gcd(a,b)=a xor b. 分析 显然a=b是一定不满足, 我们设\(a>b\), 易得gcd(a,b)&l ...
- 【leetcode】1125. Smallest Sufficient Team
题目如下: In a project, you have a list of required skills req_skills, and a list of people. The i-th p ...
- Java 数组复制之clone方法
一.源码 public class Test1 { public static void main(String[] args) { // Student[] arrs = new Student[] ...
- CondaHTTPError问题的解决
我是在配置pytorch时遇到的这个错误,截图如下: 这是某个网址访问失败导致的,我们可以通过添加其他路径解决这个问题,分别添加如下4个镜像路径,解决问题: 1)conda config --add ...
- 10.django的一些方法理解
django get_object_or_404 get_object_or_404是django的django shortcuts的方法,用来查询数据,或者抛出一个DoesNotExist的异常 用 ...