Commits

Can Xue committed 3472737

options

  • Participants
  • Parent commits fd7d837

Comments (0)

Files changed (1)

kahgean/options.py

+# -*- coding: utf-8 -*-
+# Copyright (C) 2012 Xue Can <xuecan@gmail.com>
+# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license
+
+u"""配置管理
+
+这个模块为提供了一个从命令行参数和配置文件读取配置信息的统一的对象。该模块\
+使用标准库 argparse 定义配置项,并分析和处理命令行参数,如果命令行参数中指\
+定了配置文件,则使用标准库 ConfigParser 读取配置文件,并将配置信息以类似命\
+令行参数的形式使用 ArgumentParser 进行分析处理。
+
+应用程序通过创建 Options 类的实例,随后使用 add_option() 方法添加配置项,\
+通过调用 parse_options() 方法获取配置信息。默认的,命令行参数 --config-file
+被用于指定配置文件,若指定了配置文件,将会从配置文件中读取配置项。应用程序\
+可以修改类属性 config_argument 来更改指定配置文件的参数,修改类属性
+config_file_dest 来更改最终保存该参数的属性名。例如:
+
+    Options.config_argument = ["-k", "--konfig-file"]
+    Options.config_file_dest = 'konfig_file'
+
+这样,就可以通过命令行参数 -k 或者 --konfig-file 指定配置文件的路径,使用
+get('konfig_file') 或直接访问 Options 对象的 konfig_file 属性获取该文件对象。
+
+配置文件中,若小节名为“main”,则直接使用其中的配置项名称,例如,小节“[main]”\
+中有配置“pidfile = /var/run/foo.pid”,则将被当作“--pidfile /var/run/foo.pid”\
+处理。否则,将以形如“小节名-配置名”的形式作为配置项名称。例如,小节“[logging]”\
+下有配置"level = warning",则将被当作 “--logging-level warning”处理。应用程序\
+可以修改类属性 main_section 来改变不进行参数名前缀添加的小节名。
+
+同一个配置项若同时出现在命令行参数和配置文件中且相互冲突,则若命令行参数未\
+指定或指定的是该配置的默认值,则以配置文件指定的值为准;否则,以命令行参数的\
+值为准。
+
+当应用程序调用了 parse_options() 方法之后,可以通过 get() 方法获取经过分析得到\
+的配置,也可以直接访问 Options 对象对应配置名的属性获取配置。例如:
+
+    options = Options()
+    options.add_option('--host', default='0.0.0.0')
+    options.add_option('--port', type=int, default=SUPPRESS)
+    options.parse_options()
+    host = options.host
+    port = options.get('port', 80) # 若无该配置,使用 80 作为默认值
+
+由于本模块主要是基于标准库 argparse 实现,有关定义配置项的详细说明,请参考\
+标准库关于 argparse 模块的说明。
+"""
+
+from argparse import ArgumentParser, HelpFormatter, SUPPRESS
+from ConfigParser import SafeConfigParser
+
+__all__ = ['SUPPRESS', 'Options']
+
+
+class Options(object):
+    """配置对象
+    
+    请参考标准库文档关于 ArgumentParser 类的说明了解实例化该类的参数
+    """
+    
+    config_argument = ['--config-file']
+    config_file_dest = 'config_file'
+    main_section = 'main'
+    
+    def __init__(self, prog=None, description=None, epilog=None,
+                 formatter_class=HelpFormatter):
+        self._arg_parser = ArgumentParser(prog, None, description, epilog,
+                                          formatter_class=formatter_class)
+        self.add_argument(*self.config_argument, dest=self.config_file_dest,
+                          metavar='filename', type=file, default=SUPPRESS,
+                          help=u'配置文件')
+        self._actions = dict()
+        self._namespace = None
+
+    def add_option(self, *args, **kwargs):
+        u"""添加配置项
+        
+        请参考 argparse 模块文档关于 add_argument 方法的说明以了解该方法的参数
+        """
+        action = self._arg_parser.add_argument(*args, **kwargs)
+        self._actions[action.dest] = action
+    
+    def parse_options(self):
+        u"""从命令行和配置文件(如果在命令行中指定的话)中读取配置信息"""
+        namespace = self._parse_args()
+        if hasattr(namespace, self.config_file_dest):
+            ns = self._load_options(getattr(namespace, self.config_file_dest))
+            for key in ns.__dict__:
+                # 若 default 为 SUPPRESS 且未在命令行中指定该参数,
+                # self._namespace 中不会有该 key
+                # 此时使用配置文件的设置
+                if not hasattr(namespace, key):
+                    setattr(namespace, key, ns.__dict__[key])
+                    continue
+                # 若命令行参数中指定的参数为该参数的默认值
+                # 则使用配置文件的配置覆盖该参数
+                if getattr(namespace, key)==self._actions[key].default:
+                    setattr(namespace, key, ns.__dict__[key])
+        self._namespace = namespace
+
+    def _parse_args(self, args=None):
+        u"""从命令行参数中读取配置信息"""
+        namespace = self._arg_parser.parse_args(args)
+        return namespace
+
+    def _option_to_arg(self, section, option, value):
+        # 将 [section] 下的 option = value 转变成 --section-option value
+        # 以便能以同命令行一样的方式处理
+        if section == self.main_section:
+            argument = '--%s' % option
+        else:
+            argument = '--%s-%s' % (section, option)
+        return [argument, value] if value else [argument]
+
+    def _load_options(self, file):
+        u"""从指定的文件中读取配置信息"""
+        # 从配置文件中读取数据并生成形如 --section-option value 的序列
+        # 以便后续可以使用命令行处理器分析处理
+        args = list()
+        parser = SafeConfigParser(allow_no_value=True)
+        parser.readfp(file)
+        sections = parser.sections()
+        for section in sections:
+            options = parser.options(section)
+            for option in options:
+                value = parser.get(section, option)
+                args.extend(self._option_to_arg(section, option, value))
+        file.close()
+        namespace = self._arg_parser.parse_args(args)
+        return namespace
+    
+    def get(self, option, default=None):
+        u"""返回分析后的配置选项"""
+        if not self._namespace:
+            raise RuntimeError("parse_options() has not been call")
+        return getattr(self._namespace, option, default)
+    
+    def __getattr__(self, option):
+        if hasattr(self._namespace, option):
+            return getattr(self._namespace, option)
+        raise AttributeError("'Options' object has no attribute '%s'" % option)