├── configman ├── tests │ ├── __init__.py │ ├── test_def_for_mappings.py │ ├── test_def_sources.py │ ├── test_dotdict.py │ ├── test_val_for_json.py │ ├── test_datetime_util.py │ ├── test_val_for_configobj.py │ ├── test_val_for_getopt.py │ ├── test_val_for_conf.py │ ├── test_val_for_configparse.py │ └── test_namespace.py ├── value_sources │ ├── for_xml.py │ ├── for_argparse.py │ ├── source_exceptions.py │ ├── for_mapping.py │ ├── for_json.py │ ├── for_configobj.py │ ├── for_conf.py │ ├── for_configparse.py │ └── __init__.py ├── required_config.py ├── config_file_future_proxy.py ├── def_sources │ ├── for_argparse.py │ ├── for_modules.py │ ├── for_json.py │ ├── __init__.py │ └── for_mappings.py ├── config_exceptions.py ├── __init__.py ├── namespace.py ├── datetime_util.py ├── option.py └── dotdict.py ├── AUTHORS ├── MANIFEST.in ├── .gitignore ├── demo ├── tutorial01.py ├── demo1j.json ├── tutorial02.py ├── tutorial03.py ├── tutorial04.py ├── fakedb.py ├── demo1j.py ├── advanced_demo1.py ├── dyn_app.py ├── demo1.py ├── demo3.py ├── generic_app.py ├── demo2.py └── data_store.py ├── docs ├── index.rst ├── gettingstarted.rst ├── introduction.rst ├── Makefile ├── typeconversion.rst └── conf.py ├── README.md ├── setup.py └── LICENSE /configman/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /configman/value_sources/for_xml.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | K Lars Lohn 2 | Peter Bengtsson 3 | -------------------------------------------------------------------------------- /configman/value_sources/for_argparse.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include AUTHORS 2 | include README.md 3 | include LICENSE 4 | recursive-include tests *.py 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .coverage 2 | build/ 3 | cover/ 4 | demo/sample-chain.* 5 | demo/demo1.conf 6 | demo/demo1.ini 7 | demo/demo1.json 8 | demo/demo2.ini 9 | docs/_build/ 10 | -------------------------------------------------------------------------------- /demo/tutorial01.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | def backwards(x): 4 | return x[::-1] 5 | 6 | if __name__ == '__main__': 7 | import sys 8 | output_string = ' '.join(sys.argv[1:]) 9 | print backwards(output_string) 10 | -------------------------------------------------------------------------------- /demo/demo1j.json: -------------------------------------------------------------------------------- 1 | {"action": {"name": "action", "default": "barf", "doc": "the action to take [echo, backwards, upper]", "value": "upper", "from_string_converter": "str", "short_form": "a"}, "text": {"name": "text", "default": "Socorro Forever", "doc": "the text input value", "value": "Socorro Forever", "from_string_converter": "str", "short_form": "t"}} 2 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. configman documentation master file, created by 2 | sphinx-quickstart on Thu Nov 17 09:40:50 2011. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to configman's documentation! 7 | ===================================== 8 | 9 | Contents: 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | 14 | introduction 15 | gettingstarted 16 | tutorial 17 | typeconversion 18 | 19 | 20 | 21 | Indices and tables 22 | ================== 23 | 24 | * :ref:`genindex` 25 | * :ref:`modindex` 26 | * :ref:`search` 27 | -------------------------------------------------------------------------------- /docs/gettingstarted.rst: -------------------------------------------------------------------------------- 1 | .. highlight:: python 2 | 3 | Getting started 4 | =============== 5 | 6 | Once you've :ref:`understood what configman is `, all you 7 | need to do is to install it:: 8 | 9 | $ pip install configman 10 | 11 | The code is available on github: https://github.com/mozilla/configman 12 | To clone it all you need to do is:: 13 | 14 | $ git clone git://github.com/mozilla/configman.git 15 | 16 | Once you have it installed, you usually start by importing 17 | ``configman`` in your scripts and programs, define the options in 18 | Python and then start exploring how you can :ref:`use config files 19 | ` and more advanced :ref:`type conversions `. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | configman 2 | ========= 3 | 4 | (c) Mozilla 5 | 6 | General tool for setting up configuration options per namespaces. 7 | Supports reading and writing configs generally from and into config 8 | files. 9 | 10 | 11 | Running tests 12 | ------------- 13 | 14 | We use [nose](http://code.google.com/p/python-nose/) to run all the 15 | unit tests. To run the whole suite just run: 16 | 17 | cd configman 18 | nosetests 19 | 20 | If you want to run a specific test in a testcase class you might 21 | consider this command: 22 | 23 | nosetests configman.tests.test_config_manager:TestCase.test_write_flat 24 | 25 | To run with test coverage calculation run ``nosetests`` like this: 26 | 27 | nosetests --with-coverage --cover-html --cover-package=configman 28 | -------------------------------------------------------------------------------- /demo/tutorial02.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from configman import Namespace, ConfigurationManager 4 | 5 | def backwards(x, capitalize=False): 6 | return x[::-1] 7 | 8 | import re 9 | vowels_regex = re.compile('[AEIOUY]', re.IGNORECASE) 10 | 11 | def devowel(x): 12 | return vowels_regex.sub('', x) 13 | 14 | def define_config(): 15 | definition = Namespace() 16 | definition.add_option( 17 | name='devowel', 18 | default=False 19 | ) 20 | return definition 21 | 22 | if __name__ == '__main__': 23 | definition = define_config() 24 | config_manager = ConfigurationManager(definition) 25 | config = config_manager.get_config() 26 | output_string = ' '.join(config_manager.args) 27 | if config.devowel: 28 | output_string = devowel(output_string) 29 | print backwards(output_string) 30 | -------------------------------------------------------------------------------- /configman/required_config.py: -------------------------------------------------------------------------------- 1 | from .namespace import Namespace 2 | 3 | 4 | #============================================================================== 5 | class RequiredConfig(object): 6 | #-------------------------------------------------------------------------- 7 | @classmethod 8 | def get_required_config(cls): 9 | result = Namespace() 10 | for a_class in cls.__mro__: 11 | try: 12 | result.update(a_class.required_config) 13 | except AttributeError: 14 | pass 15 | return result 16 | 17 | #-------------------------------------------------------------------------- 18 | def config_assert(self, config): 19 | for a_parameter in self.required_config.keys(): 20 | assert a_parameter in config, \ 21 | '%s missing from config' % a_parameter 22 | 23 | 24 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | from distutils.core import setup 3 | import configman 4 | 5 | def read(fname): 6 | return open(os.path.join(os.path.dirname(__file__), fname)).read() 7 | 8 | setup( 9 | name='configman', 10 | version=configman.__version__, 11 | description='Flexible reading and writing of namespaced configuration options', 12 | long_description=read('README.md'), 13 | author='Lars Lohn, Peter Bengtsson', 14 | author_email='lars@mozilla.com, peterbe@mozilla.com', 15 | url='https://github.com/twobraids/configman', 16 | classifiers=[ 17 | 'Development Status :: 5 - Production/Stable', 18 | 'License :: OSI Approved :: Mozilla Public License 1.1 (MPL 1.1)', 19 | 'Programming Language :: Python', 20 | 'Intended Audience :: Developers', 21 | 'Environment :: Console', 22 | ], 23 | packages=['configman'], 24 | package_data={'configman': ['*/*']}, 25 | scripts=[], 26 | zip_safe=False, 27 | ), 28 | -------------------------------------------------------------------------------- /demo/tutorial03.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from configman import Namespace, ConfigurationManager 4 | 5 | def backwards(x, capitalize=False): 6 | return x[::-1] 7 | 8 | import re 9 | vowels_regex = re.compile('[AEIOUY]', re.IGNORECASE) 10 | 11 | def devowel(x): 12 | return vowels_regex.sub('', x) 13 | 14 | def define_config(): 15 | definition = Namespace() 16 | definition.add_option( 17 | name='devowel', 18 | default=False, 19 | doc='Removes all vowels (including Y)', 20 | short_form='d' 21 | ) 22 | definition.add_option( 23 | name='file', 24 | default='', 25 | doc='file name for the input text', 26 | short_form='f' 27 | ) 28 | return definition 29 | 30 | if __name__ == '__main__': 31 | definition = define_config() 32 | config_manager = ConfigurationManager(definition) 33 | config = config_manager.get_config() 34 | if config.file: 35 | with open(config.file) as f: 36 | output_string = f.read().strip() 37 | else: 38 | output_string = ' '.join(config_manager.args) 39 | if config.devowel: 40 | output_string = devowel(output_string) 41 | print backwards(output_string) 42 | -------------------------------------------------------------------------------- /demo/tutorial04.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from configman import Namespace, ConfigurationManager 4 | 5 | def backwards(x, capitalize=False): 6 | return x[::-1] 7 | 8 | import re 9 | vowels_regex = re.compile('[AEIOUY]', re.IGNORECASE) 10 | 11 | def devowel(x): 12 | return vowels_regex.sub('', x) 13 | 14 | def define_config(): 15 | definition = Namespace() 16 | definition.add_option( 17 | name='devowel', 18 | default=False, 19 | doc='Removes all vowels (including Y)', 20 | short_form='d' 21 | ) 22 | definition.add_option( 23 | name='file', 24 | default='', 25 | doc='file name for the input text', 26 | short_form='f' 27 | ) 28 | return definition 29 | 30 | if __name__ == '__main__': 31 | import os, getopt 32 | definition = define_config() 33 | value_sources = ('./backwards.ini', os.environ, getopt) 34 | config_manager = ConfigurationManager(definition, 35 | values_source_list=value_sources) 36 | config = config_manager.get_config() 37 | if config.file: 38 | with open(config.file) as f: 39 | output_string = f.read().strip() 40 | else: 41 | output_string = ' '.join(config_manager.args) 42 | if config.devowel: 43 | output_string = devowel(output_string) 44 | print backwards(output_string) 45 | -------------------------------------------------------------------------------- /demo/fakedb.py: -------------------------------------------------------------------------------- 1 | # This is just a hack to simulate the minimal api of psycopg2 for the purposes 2 | # of a demo. There is nothing of any real interest here, please move along. 3 | 4 | 5 | class FakeDatabaseObjects(object): 6 | # This class provides an interface to a fake relational database 7 | # loosely modeled after the psycopg2 library. It actually does nothing 8 | # at all execept offer an API and track if it is in a transaction or not. 9 | in_transaction = 0 10 | 11 | @staticmethod 12 | def connect(dsn): 13 | print 'connnected to database with "%s"' % dsn 14 | FakeDatabaseObjects.in_transaction = 1 15 | return FakeDatabaseObjects 16 | 17 | @staticmethod 18 | def cursor(): 19 | print 'new cursor created' 20 | return FakeDatabaseObjects 21 | 22 | @staticmethod 23 | def execute(sql): 24 | print 'executing: "%s"' % sql 25 | 26 | @staticmethod 27 | def close(): 28 | print 'closing connection' 29 | 30 | @staticmethod 31 | def commit(): 32 | FakeDatabaseObjects.in_transaction = 0 33 | print 'commiting transaction' 34 | 35 | @staticmethod 36 | def rollback(): 37 | FakeDatabaseObjects.in_transaction = 0 38 | print 'rolling back transaction' 39 | 40 | @staticmethod 41 | def get_transaction_status(): 42 | return FakeDatabaseObjects.in_transaction 43 | 44 | STATUS_IN_TRANSACTION = 1 45 | -------------------------------------------------------------------------------- /docs/introduction.rst: -------------------------------------------------------------------------------- 1 | .. _introduction: 2 | 3 | .. highlight:: python 4 | 5 | ========================= 6 | Introduction (start here) 7 | ========================= 8 | 9 | ``configman`` is a package that paves over the differences between 10 | various configuration methods to achieve a smooth road of cooperation 11 | between them. 12 | 13 | We use it here at Mozilla to tie together all the different scripts 14 | and programs in `Socorro `_. 15 | 16 | The modules typically used for configuration in Python applications 17 | have inconsistent APIs. You cannot simply swap ``getopt`` for 18 | ``argparse`` and neither of them will do anything at all with 19 | configuration files like ``ini`` or ``json``. And if applications do 20 | work with some configuration file of choice it usually doesn't support 21 | rich types such as classes, functions and Python types that aren't 22 | built in. 23 | 24 | For example, it is possible with ``configman`` to define 25 | configuration in ``json`` and then automatically have ``ini`` file and 26 | command line support. Further, configman enables configuration values 27 | to be dynamically loaded Python objects, functions, classes or 28 | modules. These dynamically loaded values can, in turn, pull in more 29 | configuration definitions and more dynamic loading. This enables 30 | configman to offer configurable plugins for nearly any aspect of a 31 | Python application. 32 | 33 | 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011, Mozilla Corporation 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of the Mozilla Corporation nor the names of its contributors 15 | may be used to endorse or promote products derived from this software without 16 | specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /configman/config_file_future_proxy.py: -------------------------------------------------------------------------------- 1 | # ***** BEGIN LICENSE BLOCK ***** 2 | # Version: MPL 1.1/GPL 2.0/LGPL 2.1 3 | # 4 | # The contents of this file are subject to the Mozilla Public License Version 5 | # 1.1 (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # http://www.mozilla.org/MPL/ 8 | # 9 | # Software distributed under the License is distributed on an "AS IS" basis, 10 | # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | # for the specific language governing rights and limitations under the 12 | # License. 13 | # 14 | # The Original Code is configman 15 | # 16 | # The Initial Developer of the Original Code is 17 | # Mozilla Foundation 18 | # Portions created by the Initial Developer are Copyright (C) 2011 19 | # the Initial Developer. All Rights Reserved. 20 | # 21 | # Contributor(s): 22 | # K Lars Lohn, lars@mozilla.com 23 | # Peter Bengtsson, peterbe@mozilla.com 24 | # 25 | # Alternatively, the contents of this file may be used under the terms of 26 | # either the GNU General Public License Version 2 or later (the "GPL"), or 27 | # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 28 | # in which case the provisions of the GPL or the LGPL are applicable instead 29 | # of those above. If you wish to allow use of your version of this file only 30 | # under the terms of either the GPL or the LGPL, and not to allow others to 31 | # use your version of this file under the terms of the MPL, indicate your 32 | # decision by deleting the provisions above and replace them with the notice 33 | # and other provisions required by the GPL or the LGPL. If you do not delete 34 | # the provisions above, a recipient may use your version of this file under 35 | # the terms of any one of the MPL, the GPL or the LGPL. 36 | # 37 | # ***** END LICENSE BLOCK ***** 38 | 39 | 40 | #============================================================================== 41 | class ConfigFileFutureProxy(object): 42 | pass 43 | 44 | -------------------------------------------------------------------------------- /configman/def_sources/for_argparse.py: -------------------------------------------------------------------------------- 1 | # ***** BEGIN LICENSE BLOCK ***** 2 | # Version: MPL 1.1/GPL 2.0/LGPL 2.1 3 | # 4 | # The contents of this file are subject to the Mozilla Public License Version 5 | # 1.1 (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # http://www.mozilla.org/MPL/ 8 | # 9 | # Software distributed under the License is distributed on an "AS IS" basis, 10 | # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | # for the specific language governing rights and limitations under the 12 | # License. 13 | # 14 | # The Original Code is configman 15 | # 16 | # The Initial Developer of the Original Code is 17 | # Mozilla Foundation 18 | # Portions created by the Initial Developer are Copyright (C) 2011 19 | # the Initial Developer. All Rights Reserved. 20 | # 21 | # Contributor(s): 22 | # K Lars Lohn, lars@mozilla.com 23 | # Peter Bengtsson, peterbe@mozilla.com 24 | # 25 | # Alternatively, the contents of this file may be used under the terms of 26 | # either the GNU General Public License Version 2 or later (the "GPL"), or 27 | # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 28 | # in which case the provisions of the GPL or the LGPL are applicable instead 29 | # of those above. If you wish to allow use of your version of this file only 30 | # under the terms of either the GPL or the LGPL, and not to allow others to 31 | # use your version of this file under the terms of the MPL, indicate your 32 | # decision by deleting the provisions above and replace them with the notice 33 | # and other provisions required by the GPL or the LGPL. If you do not delete 34 | # the provisions above, a recipient may use your version of this file under 35 | # the terms of any one of the MPL, the GPL or the LGPL. 36 | # 37 | # ***** END LICENSE BLOCK ***** 38 | 39 | # this is a stub for future implementation 40 | 41 | try: 42 | import argparse 43 | 44 | def setup_definitions(source, destination): 45 | pass 46 | 47 | except ImportError: 48 | pass 49 | -------------------------------------------------------------------------------- /configman/def_sources/for_modules.py: -------------------------------------------------------------------------------- 1 | # ***** BEGIN LICENSE BLOCK ***** 2 | # Version: MPL 1.1/GPL 2.0/LGPL 2.1 3 | # 4 | # The contents of this file are subject to the Mozilla Public License Version 5 | # 1.1 (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # http://www.mozilla.org/MPL/ 8 | # 9 | # Software distributed under the License is distributed on an "AS IS" basis, 10 | # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | # for the specific language governing rights and limitations under the 12 | # License. 13 | # 14 | # The Original Code is configman 15 | # 16 | # The Initial Developer of the Original Code is 17 | # Mozilla Foundation 18 | # Portions created by the Initial Developer are Copyright (C) 2011 19 | # the Initial Developer. All Rights Reserved. 20 | # 21 | # Contributor(s): 22 | # K Lars Lohn, lars@mozilla.com 23 | # Peter Bengtsson, peterbe@mozilla.com 24 | # 25 | # Alternatively, the contents of this file may be used under the terms of 26 | # either the GNU General Public License Version 2 or later (the "GPL"), or 27 | # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 28 | # in which case the provisions of the GPL or the LGPL are applicable instead 29 | # of those above. If you wish to allow use of your version of this file only 30 | # under the terms of either the GPL or the LGPL, and not to allow others to 31 | # use your version of this file under the terms of the MPL, indicate your 32 | # decision by deleting the provisions above and replace them with the notice 33 | # and other provisions required by the GPL or the LGPL. If you do not delete 34 | # the provisions above, a recipient may use your version of this file under 35 | # the terms of any one of the MPL, the GPL or the LGPL. 36 | # 37 | # ***** END LICENSE BLOCK ***** 38 | 39 | import for_mappings 40 | 41 | 42 | def setup_definitions(source, destination): 43 | module_dict = source.__dict__.copy() 44 | del module_dict['__builtins__'] 45 | for_mappings.setup_definitions(module_dict, destination) 46 | -------------------------------------------------------------------------------- /configman/def_sources/for_json.py: -------------------------------------------------------------------------------- 1 | # ***** BEGIN LICENSE BLOCK ***** 2 | # Version: MPL 1.1/GPL 2.0/LGPL 2.1 3 | # 4 | # The contents of this file are subject to the Mozilla Public License Version 5 | # 1.1 (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # http://www.mozilla.org/MPL/ 8 | # 9 | # Software distributed under the License is distributed on an "AS IS" basis, 10 | # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | # for the specific language governing rights and limitations under the 12 | # License. 13 | # 14 | # The Original Code is configman 15 | # 16 | # The Initial Developer of the Original Code is 17 | # Mozilla Foundation 18 | # Portions created by the Initial Developer are Copyright (C) 2011 19 | # the Initial Developer. All Rights Reserved. 20 | # 21 | # Contributor(s): 22 | # K Lars Lohn, lars@mozilla.com 23 | # Peter Bengtsson, peterbe@mozilla.com 24 | # 25 | # Alternatively, the contents of this file may be used under the terms of 26 | # either the GNU General Public License Version 2 or later (the "GPL"), or 27 | # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 28 | # in which case the provisions of the GPL or the LGPL are applicable instead 29 | # of those above. If you wish to allow use of your version of this file only 30 | # under the terms of either the GPL or the LGPL, and not to allow others to 31 | # use your version of this file under the terms of the MPL, indicate your 32 | # decision by deleting the provisions above and replace them with the notice 33 | # and other provisions required by the GPL or the LGPL. If you do not delete 34 | # the provisions above, a recipient may use your version of this file under 35 | # the terms of any one of the MPL, the GPL or the LGPL. 36 | # 37 | # ***** END LICENSE BLOCK ***** 38 | import json 39 | import for_mappings 40 | 41 | 42 | def setup_definitions(source, destination): 43 | try: 44 | json_dict = json.loads(source) 45 | except ValueError: 46 | with open(source) as j: 47 | json_dict = json.load(j) 48 | for_mappings.setup_definitions(json_dict, destination) 49 | -------------------------------------------------------------------------------- /configman/config_exceptions.py: -------------------------------------------------------------------------------- 1 | # ***** BEGIN LICENSE BLOCK ***** 2 | # Version: MPL 1.1/GPL 2.0/LGPL 2.1 3 | # 4 | # The contents of this file are subject to the Mozilla Public License Version 5 | # 1.1 (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # http://www.mozilla.org/MPL/ 8 | # 9 | # Software distributed under the License is distributed on an "AS IS" basis, 10 | # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | # for the specific language governing rights and limitations under the 12 | # License. 13 | # 14 | # The Original Code is configman 15 | # 16 | # The Initial Developer of the Original Code is 17 | # Mozilla Foundation 18 | # Portions created by the Initial Developer are Copyright (C) 2011 19 | # the Initial Developer. All Rights Reserved. 20 | # 21 | # Contributor(s): 22 | # K Lars Lohn, lars@mozilla.com 23 | # Peter Bengtsson, peterbe@mozilla.com 24 | # 25 | # Alternatively, the contents of this file may be used under the terms of 26 | # either the GNU General Public License Version 2 or later (the "GPL"), or 27 | # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 28 | # in which case the provisions of the GPL or the LGPL are applicable instead 29 | # of those above. If you wish to allow use of your version of this file only 30 | # under the terms of either the GPL or the LGPL, and not to allow others to 31 | # use your version of this file under the terms of the MPL, indicate your 32 | # decision by deleting the provisions above and replace them with the notice 33 | # and other provisions required by the GPL or the LGPL. If you do not delete 34 | # the provisions above, a recipient may use your version of this file under 35 | # the terms of any one of the MPL, the GPL or the LGPL. 36 | # 37 | # ***** END LICENSE BLOCK ***** 38 | 39 | 40 | class ConfigmanException(Exception): 41 | pass 42 | 43 | 44 | class ConfigFileMissingError(IOError, ConfigmanException): 45 | pass 46 | 47 | 48 | class ConfigFileOptionNameMissingError(ConfigmanException): 49 | pass 50 | 51 | 52 | class NotAnOptionError(ConfigmanException): 53 | pass 54 | 55 | 56 | class OptionError(ConfigmanException): 57 | pass 58 | 59 | 60 | class CannotConvertError(ConfigmanException): 61 | pass 62 | -------------------------------------------------------------------------------- /configman/__init__.py: -------------------------------------------------------------------------------- 1 | # ***** BEGIN LICENSE BLOCK ***** 2 | # Version: MPL 1.1/GPL 2.0/LGPL 2.1 3 | # 4 | # The contents of this file are subject to the Mozilla Public License Version 5 | # 1.1 (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # http://www.mozilla.org/MPL/ 8 | # 9 | # Software distributed under the License is distributed on an "AS IS" basis, 10 | # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | # for the specific language governing rights and limitations under the 12 | # License. 13 | # 14 | # The Original Code is configman 15 | # 16 | # The Initial Developer of the Original Code is 17 | # Mozilla Foundation 18 | # Portions created by the Initial Developer are Copyright (C) 2011 19 | # the Initial Developer. All Rights Reserved. 20 | # 21 | # Contributor(s): 22 | # K Lars Lohn, lars@mozilla.com 23 | # Peter Bengtsson, peterbe@mozilla.com 24 | # 25 | # Alternatively, the contents of this file may be used under the terms of 26 | # either the GNU General Public License Version 2 or later (the "GPL"), or 27 | # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 28 | # in which case the provisions of the GPL or the LGPL are applicable instead 29 | # of those above. If you wish to allow use of your version of this file only 30 | # under the terms of either the GPL or the LGPL, and not to allow others to 31 | # use your version of this file under the terms of the MPL, indicate your 32 | # decision by deleting the provisions above and replace them with the notice 33 | # and other provisions required by the GPL or the LGPL. If you do not delete 34 | # the provisions above, a recipient may use your version of this file under 35 | # the terms of any one of the MPL, the GPL or the LGPL. 36 | # 37 | # ***** END LICENSE BLOCK ***** 38 | 39 | __version__ = '1.0.1' 40 | 41 | # Having these here makes it possible to easily import once configman is 42 | # installed. 43 | # For example:: 44 | # 45 | # from configman import Namespace, ConfigurationManager 46 | # 47 | 48 | from .config_manager import ConfigurationManager, RequiredConfig 49 | from .namespace import Namespace 50 | 51 | from .converters import class_converter, regex_converter, timedelta_converter 52 | 53 | 54 | # constants used to refer to Value Source concepts generically 55 | from config_file_future_proxy import ConfigFileFutureProxy 56 | from os import environ as environment 57 | import getopt as command_line 58 | -------------------------------------------------------------------------------- /configman/value_sources/source_exceptions.py: -------------------------------------------------------------------------------- 1 | # ***** BEGIN LICENSE BLOCK ***** 2 | # Version: MPL 1.1/GPL 2.0/LGPL 2.1 3 | # 4 | # The contents of this file are subject to the Mozilla Public License Version 5 | # 1.1 (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # http://www.mozilla.org/MPL/ 8 | # 9 | # Software distributed under the License is distributed on an "AS IS" basis, 10 | # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | # for the specific language governing rights and limitations under the 12 | # License. 13 | # 14 | # The Original Code is configman 15 | # 16 | # The Initial Developer of the Original Code is 17 | # Mozilla Foundation 18 | # Portions created by the Initial Developer are Copyright (C) 2011 19 | # the Initial Developer. All Rights Reserved. 20 | # 21 | # Contributor(s): 22 | # K Lars Lohn, lars@mozilla.com 23 | # Peter Bengtsson, peterbe@mozilla.com 24 | # 25 | # Alternatively, the contents of this file may be used under the terms of 26 | # either the GNU General Public License Version 2 or later (the "GPL"), or 27 | # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 28 | # in which case the provisions of the GPL or the LGPL are applicable instead 29 | # of those above. If you wish to allow use of your version of this file only 30 | # under the terms of either the GPL or the LGPL, and not to allow others to 31 | # use your version of this file under the terms of the MPL, indicate your 32 | # decision by deleting the provisions above and replace them with the notice 33 | # and other provisions required by the GPL or the LGPL. If you do not delete 34 | # the provisions above, a recipient may use your version of this file under 35 | # the terms of any one of the MPL, the GPL or the LGPL. 36 | # 37 | # ***** END LICENSE BLOCK ***** 38 | 39 | from .. import config_exceptions 40 | 41 | 42 | class ValueException(config_exceptions.ConfigmanException): 43 | pass 44 | 45 | 46 | class NotEnoughInformationException(ValueException): 47 | pass 48 | 49 | 50 | class LoadingIniFileFailsException(ValueException): 51 | pass 52 | 53 | 54 | class UnknownFileExtensionException(ValueException): 55 | pass 56 | 57 | 58 | class ModuleHandlesNothingException(ValueException): 59 | pass 60 | 61 | 62 | class NoHandlerForType(ValueException): 63 | pass 64 | 65 | 66 | class AllHandlersFailedException(ValueException): 67 | pass 68 | 69 | 70 | class CantHandleTypeException(ValueException): 71 | pass 72 | 73 | 74 | class UnknownFileExtension(ValueException): 75 | pass 76 | -------------------------------------------------------------------------------- /configman/value_sources/for_mapping.py: -------------------------------------------------------------------------------- 1 | # ***** BEGIN LICENSE BLOCK ***** 2 | # Version: MPL 1.1/GPL 2.0/LGPL 2.1 3 | # 4 | # The contents of this file are subject to the Mozilla Public License Version 5 | # 1.1 (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # http://www.mozilla.org/MPL/ 8 | # 9 | # Software distributed under the License is distributed on an "AS IS" basis, 10 | # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | # for the specific language governing rights and limitations under the 12 | # License. 13 | # 14 | # The Original Code is configman 15 | # 16 | # The Initial Developer of the Original Code is 17 | # Mozilla Foundation 18 | # Portions created by the Initial Developer are Copyright (C) 2011 19 | # the Initial Developer. All Rights Reserved. 20 | # 21 | # Contributor(s): 22 | # K Lars Lohn, lars@mozilla.com 23 | # Peter Bengtsson, peterbe@mozilla.com 24 | # 25 | # Alternatively, the contents of this file may be used under the terms of 26 | # either the GNU General Public License Version 2 or later (the "GPL"), or 27 | # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 28 | # in which case the provisions of the GPL or the LGPL are applicable instead 29 | # of those above. If you wish to allow use of your version of this file only 30 | # under the terms of either the GPL or the LGPL, and not to allow others to 31 | # use your version of this file under the terms of the MPL, indicate your 32 | # decision by deleting the provisions above and replace them with the notice 33 | # and other provisions required by the GPL or the LGPL. If you do not delete 34 | # the provisions above, a recipient may use your version of this file under 35 | # the terms of any one of the MPL, the GPL or the LGPL. 36 | # 37 | # ***** END LICENSE BLOCK ***** 38 | 39 | import collections 40 | import os 41 | 42 | from source_exceptions import CantHandleTypeException 43 | 44 | can_handle = (os.environ, 45 | collections.Mapping, 46 | ) 47 | 48 | 49 | class ValueSource(object): 50 | def __init__(self, source, the_config_manager=None): 51 | if source is os.environ: 52 | self.always_ignore_mismatches = True 53 | elif isinstance(source, collections.Mapping): 54 | self.always_ignore_mismatches = False 55 | else: 56 | raise CantHandleTypeException( 57 | "don't know how to handle %s." % source) 58 | self.source = source 59 | 60 | def get_values(self, config_manager, ignore_mismatches): 61 | return self.source 62 | -------------------------------------------------------------------------------- /configman/def_sources/__init__.py: -------------------------------------------------------------------------------- 1 | import collections 2 | 3 | # ***** BEGIN LICENSE BLOCK ***** 4 | # Version: MPL 1.1/GPL 2.0/LGPL 2.1 5 | # 6 | # The contents of this file are subject to the Mozilla Public License Version 7 | # 1.1 (the "License"); you may not use this file except in compliance with 8 | # the License. You may obtain a copy of the License at 9 | # http://www.mozilla.org/MPL/ 10 | # 11 | # Software distributed under the License is distributed on an "AS IS" basis, 12 | # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 13 | # for the specific language governing rights and limitations under the 14 | # License. 15 | # 16 | # The Original Code is configman 17 | # 18 | # The Initial Developer of the Original Code is 19 | # Mozilla Foundation 20 | # Portions created by the Initial Developer are Copyright (C) 2011 21 | # the Initial Developer. All Rights Reserved. 22 | # 23 | # Contributor(s): 24 | # K Lars Lohn, lars@mozilla.com 25 | # Peter Bengtsson, peterbe@mozilla.com 26 | # 27 | # Alternatively, the contents of this file may be used under the terms of 28 | # either the GNU General Public License Version 2 or later (the "GPL"), or 29 | # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 30 | # in which case the provisions of the GPL or the LGPL are applicable instead 31 | # of those above. If you wish to allow use of your version of this file only 32 | # under the terms of either the GPL or the LGPL, and not to allow others to 33 | # use your version of this file under the terms of the MPL, indicate your 34 | # decision by deleting the provisions above and replace them with the notice 35 | # and other provisions required by the GPL or the LGPL. If you do not delete 36 | # the provisions above, a recipient may use your version of this file under 37 | # the terms of any one of the MPL, the GPL or the LGPL. 38 | # 39 | # ***** END LICENSE BLOCK ***** 40 | 41 | # TODO: This is a temporary dispatch mechanism. This whole system 42 | # is to be changed to automatic discovery of the for_* modules 43 | 44 | import for_mappings 45 | import for_modules 46 | #import for_list 47 | import for_json 48 | #import for_class 49 | 50 | definition_dispatch = { 51 | collections.Mapping: for_mappings.setup_definitions, 52 | type(for_modules): for_modules.setup_definitions, 53 | #list: for_list.setup_definitions, 54 | str: for_json.setup_definitions, 55 | unicode: for_json.setup_definitions, 56 | #type: for_class.setup_definitions, 57 | } 58 | 59 | 60 | class UnknownDefinitionTypeException(Exception): 61 | pass 62 | 63 | 64 | def setup_definitions(source, destination): 65 | target_setup_func = None 66 | try: 67 | target_setup_func = definition_dispatch[type(source)] 68 | except KeyError: 69 | for a_key in definition_dispatch.keys(): 70 | if isinstance(source, a_key): 71 | target_setup_func = definition_dispatch[a_key] 72 | break 73 | if not target_setup_func: 74 | raise UnknownDefinitionTypeException(repr(type(source))) 75 | target_setup_func(source, destination) 76 | -------------------------------------------------------------------------------- /configman/tests/test_def_for_mappings.py: -------------------------------------------------------------------------------- 1 | # ***** BEGIN LICENSE BLOCK ***** 2 | # Version: MPL 1.1/GPL 2.0/LGPL 2.1 3 | # 4 | # The contents of this file are subject to the Mozilla Public License Version 5 | # 1.1 (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # http://www.mozilla.org/MPL/ 8 | # 9 | # Software distributed under the License is distributed on an "AS IS" basis, 10 | # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | # for the specific language governing rights and limitations under the 12 | # License. 13 | # 14 | # The Original Code is configman 15 | # 16 | # The Initial Developer of the Original Code is 17 | # Mozilla Foundation 18 | # Portions created by the Initial Developer are Copyright (C) 2011 19 | # the Initial Developer. All Rights Reserved. 20 | # 21 | # Contributor(s): 22 | # K Lars Lohn, lars@mozilla.com 23 | # Peter Bengtsson, peterbe@mozilla.com 24 | # 25 | # Alternatively, the contents of this file may be used under the terms of 26 | # either the GNU General Public License Version 2 or later (the "GPL"), or 27 | # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 28 | # in which case the provisions of the GPL or the LGPL are applicable instead 29 | # of those above. If you wish to allow use of your version of this file only 30 | # under the terms of either the GPL or the LGPL, and not to allow others to 31 | # use your version of this file under the terms of the MPL, indicate your 32 | # decision by deleting the provisions above and replace them with the notice 33 | # and other provisions required by the GPL or the LGPL. If you do not delete 34 | # the provisions above, a recipient may use your version of this file under 35 | # the terms of any one of the MPL, the GPL or the LGPL. 36 | # 37 | # ***** END LICENSE BLOCK ***** 38 | 39 | import unittest 40 | 41 | from configman import option, dotdict, namespace 42 | from configman.def_sources import for_mappings 43 | 44 | 45 | class TestCase(unittest.TestCase): 46 | 47 | def test_setup_definitions_1(self): 48 | s = dotdict.DotDict() 49 | s.x = option.Option('x', 17, 'the x') 50 | s.n = {'name': 'n', 'doc': 'the n', 'default': 23} 51 | s.__forbidden__ = option.Option('__forbidden__', 52 | 'no, you cannot', 53 | 38) 54 | s.t = namespace.Namespace() 55 | s.t.add_option('kk', 999, 'the kk') 56 | s.w = 89 57 | s.z = None 58 | s.t2 = namespace.Namespace('empty namespace') 59 | d = dotdict.DotDict() 60 | for_mappings.setup_definitions(s, d) 61 | self.assertTrue(len(d) == 5) 62 | self.assertTrue(isinstance(d.x, option.Option)) 63 | self.assertTrue(isinstance(d.n, option.Option)) 64 | self.assertTrue(d.n.name == 'n') 65 | self.assertTrue(d.n.default == 23) 66 | self.assertTrue(d.n.doc == 'the n') 67 | self.assertTrue(isinstance(d.t, namespace.Namespace)) 68 | self.assertTrue(d.t.kk.name == 'kk') 69 | self.assertTrue(d.t.kk.default == 999) 70 | self.assertTrue(d.t.kk.doc == 'the kk') 71 | self.assertTrue(isinstance(d.w, namespace.Option)) 72 | self.assertTrue(d.w.name == 'w') 73 | self.assertTrue(d.w.default == 89) 74 | self.assertTrue(d.w.doc == 'w') 75 | self.assertTrue(isinstance(d.t2, namespace.Namespace)) 76 | self.assertTrue(len(d.t2) == 0) 77 | -------------------------------------------------------------------------------- /configman/def_sources/for_mappings.py: -------------------------------------------------------------------------------- 1 | # ***** BEGIN LICENSE BLOCK ***** 2 | # Version: MPL 1.1/GPL 2.0/LGPL 2.1 3 | # 4 | # The contents of this file are subject to the Mozilla Public License Version 5 | # 1.1 (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # http://www.mozilla.org/MPL/ 8 | # 9 | # Software distributed under the License is distributed on an "AS IS" basis, 10 | # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | # for the specific language governing rights and limitations under the 12 | # License. 13 | # 14 | # The Original Code is configman 15 | # 16 | # The Initial Developer of the Original Code is 17 | # Mozilla Foundation 18 | # Portions created by the Initial Developer are Copyright (C) 2011 19 | # the Initial Developer. All Rights Reserved. 20 | # 21 | # Contributor(s): 22 | # K Lars Lohn, lars@mozilla.com 23 | # Peter Bengtsson, peterbe@mozilla.com 24 | # 25 | # Alternatively, the contents of this file may be used under the terms of 26 | # either the GNU General Public License Version 2 or later (the "GPL"), or 27 | # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 28 | # in which case the provisions of the GPL or the LGPL are applicable instead 29 | # of those above. If you wish to allow use of your version of this file only 30 | # under the terms of either the GPL or the LGPL, and not to allow others to 31 | # use your version of this file under the terms of the MPL, indicate your 32 | # decision by deleting the provisions above and replace them with the notice 33 | # and other provisions required by the GPL or the LGPL. If you do not delete 34 | # the provisions above, a recipient may use your version of this file under 35 | # the terms of any one of the MPL, the GPL or the LGPL. 36 | # 37 | # ***** END LICENSE BLOCK ***** 38 | 39 | import collections 40 | 41 | from .. import converters 42 | from .. import namespace 43 | from .. import option 44 | 45 | 46 | #------------------------------------------------------------------------------ 47 | def setup_definitions(source, destination): 48 | for key, val in source.items(): 49 | if key.startswith('__'): 50 | continue # ignore these 51 | if isinstance(val, option.Option): 52 | destination[key] = val 53 | if not val.name: 54 | val.name = key 55 | val.set_value(val.default) 56 | elif isinstance(val, option.Aggregation): 57 | destination[key] = val 58 | elif isinstance(val, collections.Mapping): 59 | if 'name' in val and 'default' in val: 60 | # this is an Option in the form of a dict, not a Namespace 61 | params = converters.str_dict_keys(val) 62 | destination[key] = option.Option(**params) 63 | elif 'function' in val: # this is an Aggregation 64 | params = converters.str_dict_keys(val) 65 | destination[key] = option.Aggregation(**params) 66 | else: 67 | # this is a Namespace 68 | if key not in destination: 69 | try: 70 | destination[key] = namespace.Namespace(doc=val._doc) 71 | except AttributeError: 72 | destination[key] = namespace.Namespace() 73 | # recurse! 74 | setup_definitions(val, destination[key]) 75 | elif isinstance(val, (int, long, float, str, unicode)): 76 | destination[key] = option.Option(name=key, 77 | doc=key, 78 | default=val) 79 | -------------------------------------------------------------------------------- /configman/namespace.py: -------------------------------------------------------------------------------- 1 | # ***** BEGIN LICENSE BLOCK ***** 2 | # Version: MPL 1.1/GPL 2.0/LGPL 2.1 3 | # 4 | # The contents of this file are subject to the Mozilla Public License Version 5 | # 1.1 (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # http://www.mozilla.org/MPL/ 8 | # 9 | # Software distributed under the License is distributed on an "AS IS" basis, 10 | # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | # for the specific language governing rights and limitations under the 12 | # License. 13 | # 14 | # The Original Code is configman 15 | # 16 | # The Initial Developer of the Original Code is 17 | # Mozilla Foundation 18 | # Portions created by the Initial Developer are Copyright (C) 2011 19 | # the Initial Developer. All Rights Reserved. 20 | # 21 | # Contributor(s): 22 | # K Lars Lohn, lars@mozilla.com 23 | # Peter Bengtsson, peterbe@mozilla.com 24 | # 25 | # Alternatively, the contents of this file may be used under the terms of 26 | # either the GNU General Public License Version 2 or later (the "GPL"), or 27 | # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 28 | # in which case the provisions of the GPL or the LGPL are applicable instead 29 | # of those above. If you wish to allow use of your version of this file only 30 | # under the terms of either the GPL or the LGPL, and not to allow others to 31 | # use your version of this file under the terms of the MPL, indicate your 32 | # decision by deleting the provisions above and replace them with the notice 33 | # and other provisions required by the GPL or the LGPL. If you do not delete 34 | # the provisions above, a recipient may use your version of this file under 35 | # the terms of any one of the MPL, the GPL or the LGPL. 36 | # 37 | # ***** END LICENSE BLOCK ***** 38 | 39 | import dotdict 40 | from option import Option, Aggregation 41 | 42 | 43 | class Namespace(dotdict.DotDict): 44 | 45 | def __init__(self, doc=''): 46 | super(Namespace, self).__init__() 47 | object.__setattr__(self, '_doc', doc) # force into attributes 48 | 49 | #-------------------------------------------------------------------------- 50 | def __setattr__(self, name, value): 51 | if isinstance(value, (Option, Namespace, Aggregation)): 52 | # then they know what they're doing already 53 | o = value 54 | else: 55 | o = Option(name=name, default=value, value=value) 56 | super(Namespace, self).__setattr__(name, o) 57 | 58 | #-------------------------------------------------------------------------- 59 | def add_option(self, name, *args, **kwargs): 60 | an_option = Option(name, *args, **kwargs) 61 | setattr(self, name, an_option) 62 | 63 | #-------------------------------------------------------------------------- 64 | def add_aggregation(self, name, function): 65 | an_aggregation = Aggregation(name, function) 66 | setattr(self, name, an_aggregation) 67 | 68 | #-------------------------------------------------------------------------- 69 | def namespace(self, name, doc=''): 70 | setattr(self, name, Namespace(doc=doc)) 71 | 72 | #-------------------------------------------------------------------------- 73 | def set_value(self, name, value, strict=True): 74 | 75 | name_parts = name.split('.', 1) 76 | prefix = name_parts[0] 77 | try: 78 | candidate = getattr(self, prefix) 79 | except KeyError: 80 | if strict: 81 | raise 82 | candidate = Option(name) 83 | setattr(self, prefix, candidate) 84 | candidate_type = type(candidate) 85 | if candidate_type == Namespace: 86 | candidate.set_value(name_parts[1], value, strict) 87 | else: 88 | candidate.set_value(value) 89 | -------------------------------------------------------------------------------- /configman/tests/test_def_sources.py: -------------------------------------------------------------------------------- 1 | # ***** BEGIN LICENSE BLOCK ***** 2 | # Version: MPL 1.1/GPL 2.0/LGPL 2.1 3 | # 4 | # The contents of this file are subject to the Mozilla Public License Version 5 | # 1.1 (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # http://www.mozilla.org/MPL/ 8 | # 9 | # Software distributed under the License is distributed on an "AS IS" basis, 10 | # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | # for the specific language governing rights and limitations under the 12 | # License. 13 | # 14 | # The Original Code is configman 15 | # 16 | # The Initial Developer of the Original Code is 17 | # Mozilla Foundation 18 | # Portions created by the Initial Developer are Copyright (C) 2011 19 | # the Initial Developer. All Rights Reserved. 20 | # 21 | # Contributor(s): 22 | # K Lars Lohn, lars@mozilla.com 23 | # Peter Bengtsson, peterbe@mozilla.com 24 | # 25 | # Alternatively, the contents of this file may be used under the terms of 26 | # either the GNU General Public License Version 2 or later (the "GPL"), or 27 | # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 28 | # in which case the provisions of the GPL or the LGPL are applicable instead 29 | # of those above. If you wish to allow use of your version of this file only 30 | # under the terms of either the GPL or the LGPL, and not to allow others to 31 | # use your version of this file under the terms of the MPL, indicate your 32 | # decision by deleting the provisions above and replace them with the notice 33 | # and other provisions required by the GPL or the LGPL. If you do not delete 34 | # the provisions above, a recipient may use your version of this file under 35 | # the terms of any one of the MPL, the GPL or the LGPL. 36 | # 37 | # ***** END LICENSE BLOCK ***** 38 | 39 | import unittest 40 | import collections 41 | 42 | import configman.config_manager as config_manager 43 | import configman.dotdict as dd 44 | import configman.def_sources as defsrc 45 | 46 | 47 | class TestCase(unittest.TestCase): 48 | 49 | def test_setup_definitions_1(self): 50 | d = dd.DotDict() 51 | 52 | def fake_mapping_func(source, destination): 53 | self.assertTrue(isinstance(source, collections.Mapping)) 54 | self.assertEqual(d, destination) 55 | saved_original = defsrc.definition_dispatch.copy() 56 | try: 57 | defsrc.definition_dispatch[collections.Mapping] = fake_mapping_func 58 | s = {} 59 | defsrc.setup_definitions(s, d) 60 | s = dd.DotDict() 61 | defsrc.setup_definitions(s, d) 62 | s = config_manager.Namespace() 63 | defsrc.setup_definitions(s, d) 64 | finally: 65 | defsrc.definition_dispatch = saved_original 66 | 67 | def test_setup_definitions_2(self): 68 | d = dd.DotDict() 69 | 70 | def fake_mapping_func(source, destination): 71 | self.assertTrue(source is collections) 72 | self.assertEqual(d, destination) 73 | saved_original = defsrc.definition_dispatch.copy() 74 | try: 75 | defsrc.definition_dispatch[type(collections)] = fake_mapping_func 76 | s = collections 77 | defsrc.setup_definitions(s, d) 78 | finally: 79 | defsrc.definition_dispatch = saved_original 80 | 81 | def test_setup_definitions_3(self): 82 | d = dd.DotDict() 83 | 84 | def fake_mapping_func(source, destination): 85 | self.assertTrue(isinstance(source, str)) 86 | self.assertEqual(d, destination) 87 | saved_original = defsrc.definition_dispatch.copy() 88 | try: 89 | defsrc.definition_dispatch[str] = fake_mapping_func 90 | s = "{}" 91 | defsrc.setup_definitions(s, d) 92 | finally: 93 | defsrc.definition_dispatch = saved_original 94 | -------------------------------------------------------------------------------- /demo/demo1j.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # ***** BEGIN LICENSE BLOCK ***** 3 | # Version: MPL 1.1/GPL 2.0/LGPL 2.1 4 | # 5 | # The contents of this file are subject to the Mozilla Public License Version 6 | # 1.1 (the "License"); you may not use this file except in compliance with 7 | # the License. You may obtain a copy of the License at 8 | # http://www.mozilla.org/MPL/ 9 | # 10 | # Software distributed under the License is distributed on an "AS IS" basis, 11 | # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 12 | # for the specific language governing rights and limitations under the 13 | # License. 14 | # 15 | # The Original Code is configman 16 | # 17 | # The Initial Developer of the Original Code is 18 | # Mozilla Foundation 19 | # Portions created by the Initial Developer are Copyright (C) 2011 20 | # the Initial Developer. All Rights Reserved. 21 | # 22 | # Contributor(s): 23 | # K Lars Lohn, lars@mozilla.com 24 | # Peter Bengtsson, peterbe@mozilla.com 25 | # 26 | # Alternatively, the contents of this file may be used under the terms of 27 | # either the GNU General Public License Version 2 or later (the "GPL"), or 28 | # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 29 | # in which case the provisions of the GPL or the LGPL are applicable instead 30 | # of those above. If you wish to allow use of your version of this file only 31 | # under the terms of either the GPL or the LGPL, and not to allow others to 32 | # use your version of this file under the terms of the MPL, indicate your 33 | # decision by deleting the provisions above and replace them with the notice 34 | # and other provisions required by the GPL or the LGPL. If you do not delete 35 | # the provisions above, a recipient may use your version of this file under 36 | # the terms of any one of the MPL, the GPL or the LGPL. 37 | # 38 | # ***** END LICENSE BLOCK ***** 39 | 40 | """This sample application demonstrates the external way to use configman.""" 41 | # this varient of the first demo shows how to use configman with the 42 | # definitions of the configuration parameters entirely in an external json 43 | # file. Just like the original demo1, we have a collection of functions that 44 | # embody the business logic of the application. We setup configuration 45 | # parameters that will control the command line and config file forms. Then 46 | # we run the application. 47 | 48 | import sys 49 | from configman import ConfigurationManager 50 | 51 | 52 | # the following three functions are the business logic of the application. 53 | def echo(x): 54 | print x 55 | 56 | 57 | def backwards(x): 58 | print x[::-1] 59 | 60 | 61 | def upper(x): 62 | print x.upper() 63 | 64 | # create an iterable collection of definition sources 65 | # internally, this list will be appended to, so a tuple won't do. 66 | # the definitions are in the json file listed below. 67 | definition_source = 'demo1j.json' 68 | 69 | # set up the manager with the option definitions along with the 'app_name' and 70 | # 'app_description'. They will both be used later to create the output of the 71 | # automatically created '--help' command line switch. 72 | # By default, when assigning values to the options loaded from the json file, 73 | # the ConfigurationManager will take, in turn: the default from the definition, 74 | # any values loaded from a config file specified by the --admin.conf command 75 | # line switch, values from the os environment and finally overrides from the 76 | # commandline. 77 | c = ConfigurationManager(definition_source, 78 | app_name='demo1j', 79 | app_description=__doc__) 80 | 81 | # fetch the DOM-like instance that gives access to the configuration info 82 | config = c.get_config() 83 | 84 | # use the config 85 | if config.action == 'echo': 86 | echo(config.text) 87 | elif config.action == 'backwards': 88 | backwards(config.text) 89 | elif config.action == 'upper': 90 | upper(config.text) 91 | else: 92 | print >>sys.stderr, config.action, "is not a valid action" 93 | -------------------------------------------------------------------------------- /configman/datetime_util.py: -------------------------------------------------------------------------------- 1 | # ***** BEGIN LICENSE BLOCK ***** 2 | # Version: MPL 1.1/GPL 2.0/LGPL 2.1 3 | # 4 | # The contents of this file are subject to the Mozilla Public License Version 5 | # 1.1 (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # http://www.mozilla.org/MPL/ 8 | # 9 | # Software distributed under the License is distributed on an "AS IS" basis, 10 | # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | # for the specific language governing rights and limitations under the 12 | # License. 13 | # 14 | # The Original Code is configman 15 | # 16 | # The Initial Developer of the Original Code is 17 | # Mozilla Foundation 18 | # Portions created by the Initial Developer are Copyright (C) 2011 19 | # the Initial Developer. All Rights Reserved. 20 | # 21 | # Contributor(s): 22 | # K Lars Lohn, lars@mozilla.com 23 | # Peter Bengtsson, peterbe@mozilla.com 24 | # 25 | # Alternatively, the contents of this file may be used under the terms of 26 | # either the GNU General Public License Version 2 or later (the "GPL"), or 27 | # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 28 | # in which case the provisions of the GPL or the LGPL are applicable instead 29 | # of those above. If you wish to allow use of your version of this file only 30 | # under the terms of either the GPL or the LGPL, and not to allow others to 31 | # use your version of this file under the terms of the MPL, indicate your 32 | # decision by deleting the provisions above and replace them with the notice 33 | # and other provisions required by the GPL or the LGPL. If you do not delete 34 | # the provisions above, a recipient may use your version of this file under 35 | # the terms of any one of the MPL, the GPL or the LGPL. 36 | # 37 | # ***** END LICENSE BLOCK ***** 38 | 39 | import datetime 40 | 41 | 42 | def datetime_from_ISO_string(s): 43 | """ Take an ISO date string of the form YYYY-MM-DDTHH:MM:SS.S 44 | and convert it into an instance of datetime.datetime 45 | """ 46 | try: 47 | return datetime.datetime.strptime(s, '%Y-%m-%dT%H:%M:%S') 48 | except ValueError: 49 | try: 50 | return datetime.datetime.strptime(s, '%Y-%m-%d') 51 | except ValueError: 52 | return datetime.datetime.strptime(s, '%Y-%m-%dT%H:%M:%S.%f') 53 | 54 | 55 | def date_from_ISO_string(s): 56 | """ Take an ISO date string of the form YYYY-MM-DD 57 | and convert it into an instance of datetime.date 58 | """ 59 | return datetime.datetime.strptime(s, '%Y-%m-%d').date() 60 | 61 | 62 | def datetime_to_ISO_string(aDate): 63 | """ Take a datetime and convert to string of the form YYYY-MM-DDTHH:MM:SS.S 64 | """ 65 | return aDate.isoformat() 66 | 67 | 68 | def date_to_ISO_string(aDate): 69 | """ Take a datetime and convert to string of the form YYYY-MM-DD 70 | """ 71 | return aDate.strftime('%Y-%m-%d') 72 | 73 | 74 | def hours_str_to_timedelta(hoursAsString): 75 | return datetime.timedelta(hours=int(hoursAsString)) 76 | 77 | 78 | def timedelta_to_seconds(td): 79 | return td.days * 24 * 60 * 60 + td.seconds 80 | 81 | 82 | def str_to_timedelta(input_str): 83 | """ a string conversion function for timedelta for strings in the format 84 | DD:HH:MM:SS 85 | """ 86 | days, hours, minutes, seconds = 0, 0, 0, 0 87 | details = input_str.split(':') 88 | if len(details) >= 4: 89 | days = int(details[-4]) 90 | if len(details) >= 3: 91 | hours = int(details[-3]) 92 | if len(details) >= 2: 93 | minutes = int(details[-2]) 94 | if len(details) >= 1: 95 | seconds = int(details[-1]) 96 | return datetime.timedelta(days=days, 97 | hours=hours, 98 | minutes=minutes, 99 | seconds=seconds) 100 | 101 | 102 | def timedelta_to_str(aTimedelta): 103 | """ a conversion function for time deltas to string in the form 104 | DD:HH:MM:SS 105 | """ 106 | days = aTimedelta.days 107 | temp_seconds = aTimedelta.seconds 108 | hours = temp_seconds / 3600 109 | minutes = (temp_seconds - hours * 3600) / 60 110 | seconds = temp_seconds - hours * 3600 - minutes * 60 111 | return '%d:%d:%d:%d' % (days, hours, minutes, seconds) 112 | -------------------------------------------------------------------------------- /demo/advanced_demo1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import contextlib 3 | 4 | from configman import Namespace, ConfigurationManager 5 | from fakedb import FakeDatabaseObjects 6 | 7 | 8 | # this is the interesting function in this example. It is used as a 9 | # from aggregation function for an Aggregation object that live within a 10 | # configman's option definition. It takes a database connection string (DSN) 11 | # and emits a fuction that returns a database connection object wrapped 12 | # in a contextmanager. This allows a configuration value to serve as a 13 | # factory for database transaction objects suitable for use in a 'with' 14 | # statement. 15 | def transaction_context_factory(config_unused, local_namespace, args_unused): 16 | dsn = ("host=%(host)s " 17 | "dbname=%(dbname)s " 18 | "user=%(user)s " 19 | "password=%(password)s") % local_namespace 20 | 21 | @contextlib.contextmanager 22 | def transaction_context(): 23 | conn = FakeDatabaseObjects.connect(dsn) 24 | try: 25 | yield conn 26 | finally: 27 | status = conn.get_transaction_status() 28 | if status == FakeDatabaseObjects.STATUS_IN_TRANSACTION: 29 | conn.rollback() 30 | conn.close() 31 | return transaction_context 32 | 33 | 34 | # this function defines the connection parameters required to connect to 35 | # a database. 36 | def define_config(): 37 | definition = Namespace() 38 | # here we're setting up the minimal parameters required for connecting 39 | # to a database. 40 | definition.add_option( 41 | name='host', 42 | default='localhost', 43 | doc='the hostname of the database', 44 | short_form='h' 45 | ) 46 | definition.add_option( 47 | name='dbname', 48 | default='', 49 | doc='the name of the database', 50 | short_form='d' 51 | ) 52 | definition.add_option( 53 | name='user', 54 | default='', 55 | doc='the name of the user within the database', 56 | short_form='u' 57 | ) 58 | definition.add_option( 59 | name='password', 60 | default='', 61 | doc='the name of the database', 62 | short_form='p' 63 | ) 64 | # This final aggregation object is the most interesting one. Its final 65 | # value depends on the final values of options within the same Namespace. 66 | # After configman is done doing all its value overlays, there is 67 | # one final pass through the option definitions with the sole purpose of 68 | # expanding the Aggregations. To do so, the Aggregations' aggregation_fn 69 | # is called passing the whole config set to the function. That function 70 | # can then use any values within the config values to come up with its 71 | # own value. In this case, the function returns a factory function that 72 | # return functions that return database connections wrapped in 73 | # contextmanagers. 74 | definition.add_aggregation( 75 | name='db_transaction', 76 | function=transaction_context_factory 77 | ) 78 | return definition 79 | 80 | 81 | if __name__ == '__main__': 82 | definition = define_config() 83 | config_manager = ConfigurationManager(definition) 84 | config = config_manager.get_config() 85 | 86 | # In this example we do two transactions. 87 | # This first one succeeds so we call the 'commit' function to indicate 88 | # that fact. The actions in the database are logged to stdout, you can 89 | # see the order of events: connection opens, we fetch a cursor, we execute 90 | # some sql, we commit the transaction and the connection 91 | # is automatically closed 92 | try: 93 | with config.db_transaction() as transaction: 94 | cursor = transaction.cursor() 95 | cursor.execute('select * from pg_tables') 96 | transaction.commit() 97 | except Exception, x: 98 | print str(x) 99 | 100 | # This second transaction fails with a (contrived) exception being raised. 101 | # Because no commit was called during the context of the 'with' statement, 102 | # the transaction will be automatically rolled back. This behavior is shown 103 | # in the stdout logging when the app is run: 104 | try: 105 | with config.db_transaction() as transaction: 106 | cursor = transaction.cursor() 107 | cursor.execute('select * from pg_tables') 108 | raise Exception("we failed for some reason") 109 | except Exception, x: 110 | print str(x) 111 | -------------------------------------------------------------------------------- /demo/dyn_app.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # ***** BEGIN LICENSE BLOCK ***** 3 | # Version: MPL 1.1/GPL 2.0/LGPL 2.1 4 | # 5 | # The contents of this file are subject to the Mozilla Public License Version 6 | # 1.1 (the "License"); you may not use this file except in compliance with 7 | # the License. You may obtain a copy of the License at 8 | # http://www.mozilla.org/MPL/ 9 | # 10 | # Software distributed under the License is distributed on an "AS IS" basis, 11 | # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 12 | # for the specific language governing rights and limitations under the 13 | # License. 14 | # 15 | # The Original Code is configman 16 | # 17 | # The Initial Developer of the Original Code is 18 | # Mozilla Foundation 19 | # Portions created by the Initial Developer are Copyright (C) 2011 20 | # the Initial Developer. All Rights Reserved. 21 | # 22 | # Contributor(s): 23 | # K Lars Lohn, lars@mozilla.com 24 | # Peter Bengtsson, peterbe@mozilla.com 25 | # 26 | # Alternatively, the contents of this file may be used under the terms of 27 | # either the GNU General Public License Version 2 or later (the "GPL"), or 28 | # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 29 | # in which case the provisions of the GPL or the LGPL are applicable instead 30 | # of those above. If you wish to allow use of your version of this file only 31 | # under the terms of either the GPL or the LGPL, and not to allow others to 32 | # use your version of this file under the terms of the MPL, indicate your 33 | # decision by deleting the provisions above and replace them with the notice 34 | # and other provisions required by the GPL or the LGPL. If you do not delete 35 | # the provisions above, a recipient may use your version of this file under 36 | # the terms of any one of the MPL, the GPL or the LGPL. 37 | # 38 | # ***** END LICENSE BLOCK ***** 39 | 40 | "This sample application demonstrates dynamic class loading with configman." 41 | # there are two ways to invoke this app: 42 | # .../generic_app.py --admin.application=dyn_app.Dyn_app 43 | # .../dyn_app.py 44 | 45 | # this app simulates passing a group of records from one datasource to 46 | # another. It offers fake versions of Postgres, MySQL and HBase as the 47 | # data sources and sinks. 48 | 49 | from configman import RequiredConfig, Namespace 50 | from configman.converters import class_converter 51 | 52 | 53 | # the following class embodies the business logic of the application. 54 | class DynApp(RequiredConfig): 55 | 56 | app_name = 'dyn' 57 | app_version = '0.1' 58 | app_description = __doc__ 59 | 60 | # create the definitions for the parameters that are to come from 61 | # the command line or config file. 62 | required_config = Namespace() 63 | # we're going to have two namespaces, one for the source and another 64 | # for the destination. We use separate namespaces to avoid name 65 | # collisions. For example, both the source and destination are going 66 | # to have 'hostname' and we don't want to mix them up. 67 | required_config.source = s = Namespace() 68 | required_config.destination = d = Namespace() 69 | # when the data source class is loaded, it will bring in more 70 | # configuration parameters gleaned from the loaded class itself. 71 | s.add_option('storage', 'data_store.CannedDataSource', 72 | 'the class to handle database interaction for input', 73 | short_form='s', 74 | from_string_converter=class_converter) 75 | d.add_option('storage', 'data_store.CannedDataSink', 76 | 'the class to handle database interaction for output', 77 | short_form='d', 78 | from_string_converter=class_converter) 79 | 80 | def __init__(self, config): 81 | super(DynApp, self).__init__() 82 | self.config = config 83 | 84 | def main(self): 85 | # the config object now has reference to a source and destination 86 | # classes. We need to instantiate the classes 87 | print self.config.source.storage 88 | source = self.config.source.storage(self.config) 89 | destination = self.config.destination.storage(self.config) 90 | # this is the actual functional part of the script. Read rows from 91 | # source's 'fetch' iterator and spool them into the destination's 92 | # write function 93 | for row in source.fetch(): 94 | destination.write(row) 95 | 96 | # if you'd rather invoke the app directly with its source file, this will 97 | # allow it. 98 | if __name__ == "__main__": 99 | import generic_app 100 | generic_app.main(DynApp) 101 | -------------------------------------------------------------------------------- /demo/demo1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # ***** BEGIN LICENSE BLOCK ***** 3 | # Version: MPL 1.1/GPL 2.0/LGPL 2.1 4 | # 5 | # The contents of this file are subject to the Mozilla Public License Version 6 | # 1.1 (the "License"); you may not use this file except in compliance with 7 | # the License. You may obtain a copy of the License at 8 | # http://www.mozilla.org/MPL/ 9 | # 10 | # Software distributed under the License is distributed on an "AS IS" basis, 11 | # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 12 | # for the specific language governing rights and limitations under the 13 | # License. 14 | # 15 | # The Original Code is configman 16 | # 17 | # The Initial Developer of the Original Code is 18 | # Mozilla Foundation 19 | # Portions created by the Initial Developer are Copyright (C) 2011 20 | # the Initial Developer. All Rights Reserved. 21 | # 22 | # Contributor(s): 23 | # K Lars Lohn, lars@mozilla.com 24 | # Peter Bengtsson, peterbe@mozilla.com 25 | # 26 | # Alternatively, the contents of this file may be used under the terms of 27 | # either the GNU General Public License Version 2 or later (the "GPL"), or 28 | # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 29 | # in which case the provisions of the GPL or the LGPL are applicable instead 30 | # of those above. If you wish to allow use of your version of this file only 31 | # under the terms of either the GPL or the LGPL, and not to allow others to 32 | # use your version of this file under the terms of the MPL, indicate your 33 | # decision by deleting the provisions above and replace them with the notice 34 | # and other provisions required by the GPL or the LGPL. If you do not delete 35 | # the provisions above, a recipient may use your version of this file under 36 | # the terms of any one of the MPL, the GPL or the LGPL. 37 | # 38 | # ***** END LICENSE BLOCK ***** 39 | 40 | """This sample application demonstrates the simlpest way to use configman.""" 41 | # this first demo shows how to use configman in the same manner that one would 42 | # use other libraries like argparse. We have a collection of functions that 43 | # embody the business logic of the application. We setup configuration 44 | # parameters that will control the command line and config file forms. Then 45 | # we run the application. 46 | 47 | import sys 48 | from configman import ConfigurationManager, Namespace 49 | 50 | 51 | # the following three functions are the business logic of the application. 52 | def echo(x): 53 | print x 54 | 55 | 56 | def backwards(x): 57 | print x[::-1] 58 | 59 | 60 | def upper(x): 61 | print x.upper() 62 | 63 | # create the definitions for the parameters that are to come from 64 | # the command line or config file. First we create a container called a 65 | # namespace for the configuration parameters. 66 | definition_source = Namespace() 67 | # now we start adding options to the container. This first option 68 | # defines on the command line '--text' and '-t' swiches. For configuration 69 | # files, this defines a top level entry of 'text' and assigns the value 70 | # 'Socorro Forever' to it. 71 | definition_source.add_option('text', 72 | default='Socorro Forever', 73 | doc='the text input value', 74 | short_form='t') 75 | # this second option definition defines the command line switches '--action' 76 | # and '-a' 77 | definition_source.add_option('action', 78 | default='echo', 79 | doc='the action to take [echo, backwards, upper]', 80 | short_form='a') 81 | 82 | # set up the manager with the option definitions along with the 'app_name' and 83 | # 'app_description'. They will both be used later to create the output of the 84 | # automatically created '--help' command line switch. 85 | # By default, when assigning values to the options defined above, the 86 | # ConfigurationManager will take, in turn: the default from the definition, 87 | # any values loaded from a config file specified by the --admin.conf command 88 | # line switch, values from the os environment and finally overrides from the 89 | # commandline. 90 | c = ConfigurationManager(definition_source, 91 | app_name='demo1', 92 | app_description=__doc__) 93 | 94 | # fetch the DOM-like instance that gives access to the configuration info 95 | config = c.get_config() 96 | 97 | # use the config 98 | if config.action == 'echo': 99 | echo(config.text) 100 | elif config.action == 'backwards': 101 | backwards(config.text) 102 | elif config.action == 'upper': 103 | upper(config.text) 104 | else: 105 | print >>sys.stderr, config.action, "is not a valid action" 106 | -------------------------------------------------------------------------------- /demo/demo3.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # ***** BEGIN LICENSE BLOCK ***** 3 | # Version: MPL 1.1/GPL 2.0/LGPL 2.1 4 | # 5 | # The contents of this file are subject to the Mozilla Public License Version 6 | # 1.1 (the "License"); you may not use this file except in compliance with 7 | # the License. You may obtain a copy of the License at 8 | # http://www.mozilla.org/MPL/ 9 | # 10 | # Software distributed under the License is distributed on an "AS IS" basis, 11 | # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 12 | # for the specific language governing rights and limitations under the 13 | # License. 14 | # 15 | # The Original Code is configman 16 | # 17 | # The Initial Developer of the Original Code is 18 | # Mozilla Foundation 19 | # Portions created by the Initial Developer are Copyright (C) 2011 20 | # the Initial Developer. All Rights Reserved. 21 | # 22 | # Contributor(s): 23 | # K Lars Lohn, lars@mozilla.com 24 | # Peter Bengtsson, peterbe@mozilla.com 25 | # 26 | # Alternatively, the contents of this file may be used under the terms of 27 | # either the GNU General Public License Version 2 or later (the "GPL"), or 28 | # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 29 | # in which case the provisions of the GPL or the LGPL are applicable instead 30 | # of those above. If you wish to allow use of your version of this file only 31 | # under the terms of either the GPL or the LGPL, and not to allow others to 32 | # use your version of this file under the terms of the MPL, indicate your 33 | # decision by deleting the provisions above and replace them with the notice 34 | # and other provisions required by the GPL or the LGPL. If you do not delete 35 | # the provisions above, a recipient may use your version of this file under 36 | # the terms of any one of the MPL, the GPL or the LGPL. 37 | # 38 | # ***** END LICENSE BLOCK ***** 39 | 40 | """This sample application demonstrates the app class way to use configman.""" 41 | # there are two ways to invoke this app: 42 | # .../generic_app.py --admin.application=demo3.Demo3App 43 | # .../demo3.py 44 | # this demo differs from demo2.py in the manner in which it works with 45 | # configman. Rather than being a linear procedure, this app defines a app 46 | # class with five features: 47 | # 1) the app class derives from 'RequiredConfig'. This instruments the class 48 | # with the mechanism for discovery of required configuration parameters. 49 | # 2) closely aligned with point 1, this class defines a class level constant 50 | # called 'required_config' that sets up Namespaces and Options to define 51 | # the configuration requirements. 52 | # 3) the app class defines three class level constants that identify the app. 53 | # 'app_name', 'app_version', 'app_description' 54 | # 4) the app class defines a constructor that accepts a DotDict derivative 55 | # of configuration values. 56 | # 5) the app class defines a parameterless 'main' function that executes the 57 | # business logic of the application 58 | 59 | from configman import RequiredConfig, Namespace 60 | 61 | 62 | # the following class embodies the business logic of the application. 63 | class Demo3App(RequiredConfig): 64 | 65 | app_name = 'demo3_app' 66 | app_version = '0.1' 67 | app_description = __doc__ 68 | 69 | # create the definitions for the parameters that are to come from 70 | # the command line or config file. 71 | required_config = Namespace() 72 | required_config.add_option('text', 'Socorro Forever', 'the input value', 73 | short_form='t') 74 | 75 | def __init__(self, config): 76 | self.text = config.text 77 | self.action_fn = Demo3App.action_converter(config.action) 78 | 79 | def main(self): 80 | self.action_fn(self.text) 81 | 82 | @staticmethod 83 | def echo_action(x): 84 | print x 85 | 86 | @staticmethod 87 | def backwards_action(x): 88 | print x[::-1] 89 | 90 | @staticmethod 91 | def upper_action(x): 92 | print x.upper() 93 | 94 | @staticmethod 95 | def action_converter(action): 96 | try: 97 | return getattr(Demo3App, "%s_action" % action) 98 | except AttributeError: 99 | raise Exception("'%s' is not a valid action" % action) 100 | 101 | # normally, all the parameters are defined within the class, but 102 | # the methods of this class itself are used in the configuration parameters. 103 | # Python doesn't allow reference to class members until the class is entirely 104 | # defined. This tag along code injects the final config parameter after 105 | # the class has been fully defined 106 | list_of_actions = [x[:-7] for x in dir(Demo3App) if x.endswith('_action')] 107 | doc_string = 'the action to take [%s]' % ', '.join(list_of_actions) 108 | Demo3App.required_config.add_option('action', 'echo', doc_string, 109 | short_form='a') 110 | 111 | # if you'd rather invoke the app directly with its source file, this will 112 | # allow it. 113 | if __name__ == "__main__": 114 | import generic_app 115 | generic_app.main(Demo3App) 116 | -------------------------------------------------------------------------------- /configman/value_sources/for_json.py: -------------------------------------------------------------------------------- 1 | # ***** BEGIN LICENSE BLOCK ***** 2 | # Version: MPL 1.1/GPL 2.0/LGPL 2.1 3 | # 4 | # The contents of this file are subject to the Mozilla Public License Version 5 | # 1.1 (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # http://www.mozilla.org/MPL/ 8 | # 9 | # Software distributed under the License is distributed on an "AS IS" basis, 10 | # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | # for the specific language governing rights and limitations under the 12 | # License. 13 | # 14 | # The Original Code is configman 15 | # 16 | # The Initial Developer of the Original Code is 17 | # Mozilla Foundation 18 | # Portions created by the Initial Developer are Copyright (C) 2011 19 | # the Initial Developer. All Rights Reserved. 20 | # 21 | # Contributor(s): 22 | # K Lars Lohn, lars@mozilla.com 23 | # Peter Bengtsson, peterbe@mozilla.com 24 | # 25 | # Alternatively, the contents of this file may be used under the terms of 26 | # either the GNU General Public License Version 2 or later (the "GPL"), or 27 | # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 28 | # in which case the provisions of the GPL or the LGPL are applicable instead 29 | # of those above. If you wish to allow use of your version of this file only 30 | # under the terms of either the GPL or the LGPL, and not to allow others to 31 | # use your version of this file under the terms of the MPL, indicate your 32 | # decision by deleting the provisions above and replace them with the notice 33 | # and other provisions required by the GPL or the LGPL. If you do not delete 34 | # the provisions above, a recipient may use your version of this file under 35 | # the terms of any one of the MPL, the GPL or the LGPL. 36 | # 37 | # ***** END LICENSE BLOCK ***** 38 | 39 | import json 40 | import collections 41 | import sys 42 | 43 | from .. import converters as conv 44 | from ..namespace import Namespace 45 | from ..option import Option, Aggregation 46 | 47 | from source_exceptions import (ValueException, NotEnoughInformationException, 48 | NoHandlerForType) 49 | 50 | can_handle = (basestring, 51 | json 52 | ) 53 | 54 | file_name_extension = 'json' 55 | 56 | 57 | class LoadingJsonFileFailsException(ValueException): 58 | pass 59 | 60 | 61 | class ValueSource(object): 62 | 63 | def __init__(self, source, the_config_manager=None): 64 | self.values = None 65 | if source is json: 66 | try: 67 | app = the_config_manager._get_option( 68 | 'admin.application') 69 | source = "%s.%s" % (app.value.app_name, file_name_extension) 70 | except (AttributeError, KeyError): 71 | raise NotEnoughInformationException("Can't setup an json " 72 | "file without knowing " 73 | "the file name") 74 | if (isinstance(source, basestring) and 75 | source.endswith(file_name_extension)): 76 | try: 77 | with open(source) as fp: 78 | self.values = json.load(fp) 79 | except IOError, x: 80 | # The file doesn't exist. That's ok, we'll give warning 81 | # but this isn't a fatal error 82 | import warnings 83 | warnings.warn("%s doesn't exist" % source) 84 | self.values = {} 85 | except ValueError: 86 | raise LoadingJsonFileFailsException("Cannot load json: %s" % 87 | str(x)) 88 | else: 89 | raise NoHandlerForType("json can't handle: %s" % 90 | str(source)) 91 | 92 | def get_values(self, config_manager, ignore_mismatches): 93 | return self.values 94 | 95 | @staticmethod 96 | def recursive_default_dict(): 97 | return collections.defaultdict(ValueSource.recursive_default_dict) 98 | 99 | @staticmethod 100 | def write(option_iter, output_stream=sys.stdout): 101 | json_dict = ValueSource.recursive_default_dict() 102 | for qkey, key, val in option_iter(): 103 | if isinstance(val, Namespace): 104 | continue 105 | d = json_dict 106 | for x in qkey.split('.'): 107 | d = d[x] 108 | if isinstance(val, Option): 109 | for okey, oval in val.__dict__.iteritems(): 110 | try: 111 | d[okey] = conv.to_string_converters[type(oval)](oval) 112 | except KeyError: 113 | d[okey] = str(oval) 114 | d['default'] = d['value'] 115 | elif isinstance(val, Aggregation): 116 | d['name'] = val.name 117 | fn = val.function 118 | d['function'] = conv.to_string_converters[type(fn)](fn) 119 | json.dump(json_dict, output_stream) 120 | -------------------------------------------------------------------------------- /demo/generic_app.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # ***** BEGIN LICENSE BLOCK ***** 3 | # Version: MPL 1.1/GPL 2.0/LGPL 2.1 4 | # 5 | # The contents of this file are subject to the Mozilla Public License Version 6 | # 1.1 (the "License"); you may not use this file except in compliance with 7 | # the License. You may obtain a copy of the License at 8 | # http://www.mozilla.org/MPL/ 9 | # 10 | # Software distributed under the License is distributed on an "AS IS" basis, 11 | # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 12 | # for the specific language governing rights and limitations under the 13 | # License. 14 | # 15 | # The Original Code is configman 16 | # 17 | # The Initial Developer of the Original Code is 18 | # Mozilla Foundation 19 | # Portions created by the Initial Developer are Copyright (C) 2011 20 | # the Initial Developer. All Rights Reserved. 21 | # 22 | # Contributor(s): 23 | # K Lars Lohn, lars@mozilla.com 24 | # Peter Bengtsson, peterbe@mozilla.com 25 | # 26 | # Alternatively, the contents of this file may be used under the terms of 27 | # either the GNU General Public License Version 2 or later (the "GPL"), or 28 | # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 29 | # in which case the provisions of the GPL or the LGPL are applicable instead 30 | # of those above. If you wish to allow use of your version of this file only 31 | # under the terms of either the GPL or the LGPL, and not to allow others to 32 | # use your version of this file under the terms of the MPL, indicate your 33 | # decision by deleting the provisions above and replace them with the notice 34 | # and other provisions required by the GPL or the LGPL. If you do not delete 35 | # the provisions above, a recipient may use your version of this file under 36 | # the terms of any one of the MPL, the GPL or the LGPL. 37 | # 38 | # ***** END LICENSE BLOCK ***** 39 | 40 | import ConfigParser 41 | import getopt 42 | import os.path 43 | import inspect 44 | 45 | import configman as cm 46 | from configman import ConfigurationManager, Namespace 47 | from configman import ConfigFileFutureProxy, environment, command_line 48 | from configman.converters import class_converter 49 | 50 | 51 | # This main function will load an application object, initialize it and then 52 | # call its 'main' function 53 | def main(app_object=None): 54 | if isinstance(app_object, basestring): 55 | app_object = class_converter(app_object) 56 | 57 | # the only config parameter is a special one that refers to a class or 58 | # module that defines an application. In order to qualify, a class must 59 | # have a constructor that accepts a DotDict derivative as the sole 60 | # input parameter. It must also have a 'main' function that accepts no 61 | # parameters. For a module to be acceptable, it must have a main 62 | # function that accepts a DotDict derivative as its input parameter. 63 | app_definition = Namespace() 64 | app_definition.admin = admin = Namespace() 65 | admin.add_option('application', 66 | doc='the fully qualified module or class of the ' 67 | 'application', 68 | default=app_object, 69 | from_string_converter=class_converter 70 | ) 71 | app_name = getattr(app_object, 'app_name', 'unknown') 72 | app_version = getattr(app_object, 'app_version', '0.0') 73 | app_description = getattr(app_object, 'app_description', 'no idea') 74 | 75 | 76 | # create an iterable collection of value sources 77 | # the order is important as these will supply values for the sources 78 | # defined in the_definition_source. The values will be overlain in turn. 79 | # First the os.environ values will be applied. Then any values from an ini 80 | # file parsed by getopt. Finally any values supplied on the command line 81 | # will be applied. 82 | value_sources = (ConfigFileFutureProxy, # alias for allowing the user 83 | # to specify a config file on 84 | # the command line 85 | environment, # alias for os.environ 86 | command_line) # alias for getopt 87 | 88 | # set up the manager with the definitions and values 89 | # it isn't necessary to provide the app_name because the 90 | # app_object passed in or loaded by the ConfigurationManager will alredy 91 | # have that information. 92 | config_manager = ConfigurationManager(app_definition, 93 | value_sources, 94 | app_name=app_name, 95 | app_version=app_version, 96 | app_description=app_description, 97 | ) 98 | config = config_manager.get_config() 99 | 100 | app_object = config.admin.application 101 | 102 | if isinstance(app_object, type): 103 | # invocation of the app if the app_object was a class 104 | instance = app_object(config) 105 | instance.main() 106 | elif inspect.ismodule(app_object): 107 | # invocation of the app if the app_object was a module 108 | app_object.main(config) 109 | elif inspect.isfunction(app_object): 110 | # invocation of the app if the app_object was a function 111 | app_object(config) 112 | 113 | if __name__ == '__main__': 114 | main() 115 | -------------------------------------------------------------------------------- /configman/tests/test_dotdict.py: -------------------------------------------------------------------------------- 1 | # ***** BEGIN LICENSE BLOCK ***** 2 | # Version: MPL 1.1/GPL 2.0/LGPL 2.1 3 | # 4 | # The contents of this file are subject to the Mozilla Public License Version 5 | # 1.1 (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # http://www.mozilla.org/MPL/ 8 | # 9 | # Software distributed under the License is distributed on an "AS IS" basis, 10 | # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | # for the specific language governing rights and limitations under the 12 | # License. 13 | # 14 | # The Original Code is configman 15 | # 16 | # The Initial Developer of the Original Code is 17 | # Mozilla Foundation 18 | # Portions created by the Initial Developer are Copyright (C) 2011 19 | # the Initial Developer. All Rights Reserved. 20 | # 21 | # Contributor(s): 22 | # K Lars Lohn, lars@mozilla.com 23 | # Peter Bengtsson, peterbe@mozilla.com 24 | # 25 | # Alternatively, the contents of this file may be used under the terms of 26 | # either the GNU General Public License Version 2 or later (the "GPL"), or 27 | # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 28 | # in which case the provisions of the GPL or the LGPL are applicable instead 29 | # of those above. If you wish to allow use of your version of this file only 30 | # under the terms of either the GPL or the LGPL, and not to allow others to 31 | # use your version of this file under the terms of the MPL, indicate your 32 | # decision by deleting the provisions above and replace them with the notice 33 | # and other provisions required by the GPL or the LGPL. If you do not delete 34 | # the provisions above, a recipient may use your version of this file under 35 | # the terms of any one of the MPL, the GPL or the LGPL. 36 | # 37 | # ***** END LICENSE BLOCK ***** 38 | 39 | import unittest 40 | from configman.dotdict import DotDict, DotDictWithAcquisition 41 | 42 | 43 | class TestCase(unittest.TestCase): 44 | 45 | def test_setting_and_getting(self): 46 | dd = DotDict() 47 | dd.name = u'Peter' 48 | dd['age'] = 31 49 | setattr(dd, 'gender', 'male') 50 | 51 | self.assertEqual(dd['name'], u'Peter') 52 | self.assertEqual(dd.age, 31) 53 | self.assertEqual(dd['gender'], dd.gender) 54 | self.assertEqual(dd.get('name'), u'Peter') 55 | self.assertEqual(getattr(dd, 'gender'), 'male') 56 | self.assertEqual(dd.get('gender'), 'male') 57 | self.assertEqual(dd.get('junk'), None) 58 | self.assertEqual(dd.get('junk', 'trash'), 'trash') 59 | 60 | def test_deleting_attributes(self): 61 | dd = DotDict() 62 | dd.name = 'peter' 63 | dd.age = 31 64 | del dd.name 65 | del dd.age 66 | self.assertEqual(dict(dd), {}) 67 | 68 | def test_key_errors(self): 69 | dd = DotDict() 70 | 71 | self.assertRaises(KeyError, dd.get('name')) 72 | try: 73 | dd.name 74 | raise AssertionError("should have raised KeyError") 75 | except KeyError: 76 | pass 77 | #self.assertRaises(KeyError, getattr(dd, 'name')) 78 | self.assertEqual(dd.get('age'), None) 79 | self.assertEqual(dd.get('age', 0), 0) 80 | 81 | def test_nesting(self): 82 | d = DotDictWithAcquisition() 83 | d.e = 1 84 | d.dd = DotDictWithAcquisition() 85 | d.dd.f = 2 86 | d.dd.ddd = DotDictWithAcquisition() 87 | d.dd.ddd.g = 3 88 | d['a'] = 21 89 | d.dd['a'] = 22 90 | 91 | self.assertEqual(d.dd.ddd.e, 1) 92 | self.assertEqual(d.dd.e, 1) 93 | self.assertEqual(d.e, 1) 94 | 95 | self.assertEqual(d.dd.ddd.a, 22) 96 | self.assertEqual(d.dd.a, 22) 97 | self.assertEqual(d.a, 21) 98 | 99 | self.assertEqual(d.dd.ddd.f, 2) 100 | self.assertEqual(d.dd.f, 2) 101 | try: 102 | d.f 103 | raise AssertionError("should have raised KeyError") 104 | except KeyError: 105 | pass 106 | self.assertEqual(d.dd.dd.dd.dd.ddd.f, 2) 107 | self.assertEqual(d.dd.ddd.dd.ddd.dd.ddd.e, 1) 108 | 109 | self.assertEqual(len(d), 3) 110 | _keys = [x for x in d] 111 | self.assertEqual(_keys, ['a', 'dd', 'e']) 112 | self.assertEqual(d.keys(), ['a', 'dd', 'e']) 113 | self.assertEqual(list(d.iterkeys()), ['a', 'dd', 'e']) 114 | 115 | d.xxx = DotDictWithAcquisition() 116 | d.xxx.p = 69 117 | del d.xxx.p 118 | try: 119 | d.xxx.p 120 | assert 0 121 | except KeyError: 122 | pass 123 | 124 | # initialization 125 | d.yy = DotDictWithAcquisition(dict(foo='bar')) 126 | self.assertEqual(d.yy.foo, 'bar') 127 | 128 | # clashing names 129 | d.zzz = DotDictWithAcquisition() 130 | d.zzz.Bool = 'True' 131 | d.zzz.www = DotDictWithAcquisition() 132 | self.assertEqual(d.zzz.www.Bool, 'True') 133 | d.zzz.www.Bool = 'False' 134 | self.assertEqual(d.zzz.www.Bool, 'False') 135 | 136 | # test __setitem__ and __getitem__ 137 | d = DotDictWithAcquisition() 138 | d.a = 17 139 | d['dd'] = DotDictWithAcquisition() 140 | self.assertEqual(d.dd.a, 17) 141 | d['dd']['ddd'] = DotDictWithAcquisition() 142 | self.assertEqual(d.dd.ddd.a, 17) 143 | self.assertEqual(d['dd']['ddd'].a, 17) 144 | self.assertEqual(d['dd']['ddd']['dd'].a, 17) 145 | 146 | 147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /configman/value_sources/for_configobj.py: -------------------------------------------------------------------------------- 1 | # ***** BEGIN LICENSE BLOCK ***** 2 | # Version: MPL 1.1/GPL 2.0/LGPL 2.1 3 | # 4 | # The contents of this file are subject to the Mozilla Public License Version 5 | # 1.1 (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # http://www.mozilla.org/MPL/ 8 | # 9 | # Software distributed under the License is distributed on an "AS IS" basis, 10 | # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | # for the specific language governing rights and limitations under the 12 | # License. 13 | # 14 | # The Original Code is configman 15 | # 16 | # The Initial Developer of the Original Code is 17 | # Mozilla Foundation 18 | # Portions created by the Initial Developer are Copyright (C) 2011 19 | # the Initial Developer. All Rights Reserved. 20 | # 21 | # Contributor(s): 22 | # K Lars Lohn, lars@mozilla.com 23 | # Peter Bengtsson, peterbe@mozilla.com 24 | # 25 | # Alternatively, the contents of this file may be used under the terms of 26 | # either the GNU General Public License Version 2 or later (the "GPL"), or 27 | # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 28 | # in which case the provisions of the GPL or the LGPL are applicable instead 29 | # of those above. If you wish to allow use of your version of this file only 30 | # under the terms of either the GPL or the LGPL, and not to allow others to 31 | # use your version of this file under the terms of the MPL, indicate your 32 | # decision by deleting the provisions above and replace them with the notice 33 | # and other provisions required by the GPL or the LGPL. If you do not delete 34 | # the provisions above, a recipient may use your version of this file under 35 | # the terms of any one of the MPL, the GPL or the LGPL. 36 | # 37 | # ***** END LICENSE BLOCK ***** 38 | 39 | import sys 40 | import collections 41 | 42 | import configobj 43 | 44 | from source_exceptions import (CantHandleTypeException, ValueException, 45 | NotEnoughInformationException) 46 | from ..namespace import Namespace 47 | from ..option import Option 48 | from .. import converters as conv 49 | 50 | file_name_extension = 'ini' 51 | 52 | can_handle = (configobj, 53 | configobj.ConfigObj, 54 | basestring, 55 | ) 56 | 57 | 58 | class LoadingIniFileFailsException(ValueException): 59 | pass 60 | 61 | 62 | class ValueSource(object): 63 | 64 | def __init__(self, source, 65 | config_manager=None, 66 | top_level_section_name=''): 67 | self.delayed_parser_instantiation = False 68 | self.top_level_section_name = top_level_section_name 69 | if source is configobj.ConfigObj: 70 | try: 71 | app = config_manager._get_option('admin.application') 72 | source = "%s.%s" % (app.value.app_name, file_name_extension) 73 | except AttributeError: 74 | # we likely don't have the admin.application object set up yet. 75 | # we need to delay the instantiation of the ConfigParser 76 | # until later. 77 | if source is None: 78 | raise NotEnoughInformationException("Can't setup an ini " 79 | "file without knowing " 80 | "the file name") 81 | self.delayed_parser_instantiation = True 82 | return 83 | if (isinstance(source, basestring) and 84 | source.endswith(file_name_extension)): 85 | try: 86 | self.config_obj = configobj.ConfigObj(source) 87 | except Exception, x: 88 | raise LoadingIniFileFailsException( 89 | "ConfigObj cannot load ini: %s" % str(x)) 90 | else: 91 | raise CantHandleTypeException( 92 | "ConfigObj doesn't know how to handle %s." % source) 93 | 94 | def get_values(self, config_manager, ignore_mismatches): 95 | """Return a nested dictionary representing the values in the ini file. 96 | In the case of this ValueSource implementation, both parameters are 97 | dummies.""" 98 | if self.delayed_parser_instantiation: 99 | try: 100 | app = config_manager._get_option('admin.application') 101 | source = "%s%s" % (app.value.app_name, file_name_extension) 102 | self.config_obj = configobj.ConfigObj(source) 103 | self.delayed_parser_instantiation = False 104 | except AttributeError: 105 | # we don't have enough information to get the ini file 106 | # yet. we'll ignore the error for now 107 | return {} 108 | return self.config_obj 109 | 110 | @staticmethod 111 | def recursive_default_dict(): 112 | return collections.defaultdict(ValueSource.recursive_default_dict) 113 | 114 | @staticmethod 115 | def write(option_iter, output_stream=sys.stdout): 116 | # must construct a dict from the iter 117 | destination_dict = ValueSource.recursive_default_dict() 118 | for qkey, key, val in option_iter(): 119 | if isinstance(val, Namespace): 120 | continue 121 | d = destination_dict 122 | for x in qkey.split('.')[:-1]: 123 | d = d[x] 124 | if isinstance(val, Option): 125 | v = val.value 126 | v_str = conv.to_string_converters[type(v)](v) 127 | d[key] = v_str 128 | config = configobj.ConfigObj(destination_dict) 129 | config.write(outfile=output_stream) 130 | -------------------------------------------------------------------------------- /configman/tests/test_val_for_json.py: -------------------------------------------------------------------------------- 1 | # ***** BEGIN LICENSE BLOCK ***** 2 | # Version: MPL 1.1/GPL 2.0/LGPL 2.1 3 | # 4 | # The contents of this file are subject to the Mozilla Public License Version 5 | # 1.1 (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # http://www.mozilla.org/MPL/ 8 | # 9 | # Software distributed under the License is distributed on an "AS IS" basis, 10 | # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | # for the specific language governing rights and limitations under the 12 | # License. 13 | # 14 | # The Original Code is configman 15 | # 16 | # The Initial Developer of the Original Code is 17 | # Mozilla Foundation 18 | # Portions created by the Initial Developer are Copyright (C) 2011 19 | # the Initial Developer. All Rights Reserved. 20 | # 21 | # Contributor(s): 22 | # K Lars Lohn, lars@mozilla.com 23 | # Peter Bengtsson, peterbe@mozilla.com 24 | # 25 | # Alternatively, the contents of this file may be used under the terms of 26 | # either the GNU General Public License Version 2 or later (the "GPL"), or 27 | # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 28 | # in which case the provisions of the GPL or the LGPL are applicable instead 29 | # of those above. If you wish to allow use of your version of this file only 30 | # under the terms of either the GPL or the LGPL, and not to allow others to 31 | # use your version of this file under the terms of the MPL, indicate your 32 | # decision by deleting the provisions above and replace them with the notice 33 | # and other provisions required by the GPL or the LGPL. If you do not delete 34 | # the provisions above, a recipient may use your version of this file under 35 | # the terms of any one of the MPL, the GPL or the LGPL. 36 | # 37 | # ***** END LICENSE BLOCK ***** 38 | 39 | import unittest 40 | import os 41 | import json 42 | import tempfile 43 | from cStringIO import StringIO 44 | 45 | 46 | import configman.config_manager as config_manager 47 | import configman.datetime_util as dtu 48 | from configman.value_sources.for_json import ValueSource 49 | #from ..value_sources.for_json import ValueSource 50 | 51 | 52 | def bbb_minus_one(config, local_config, args): 53 | return config.bbb - 1 54 | 55 | 56 | class TestCase(unittest.TestCase): 57 | 58 | def test_for_json_basics(self): 59 | tmp_filename = os.path.join(tempfile.gettempdir(), 'test.json') 60 | j = {'fred': 'wilma', 61 | 'number': 23, 62 | } 63 | with open(tmp_filename, 'w') as f: 64 | json.dump(j, f) 65 | try: 66 | jvs = ValueSource(tmp_filename) 67 | vals = jvs.get_values(None, True) 68 | self.assertEqual(vals['fred'], 'wilma') 69 | self.assertEqual(vals['number'], 23) 70 | finally: 71 | if os.path.isfile(tmp_filename): 72 | os.remove(tmp_filename) 73 | 74 | def test_write_json(self): 75 | n = config_manager.Namespace(doc='top') 76 | n.add_option('aaa', '2011-05-04T15:10:00', 'the a', 77 | short_form='a', 78 | from_string_converter=dtu.datetime_from_ISO_string 79 | ) 80 | 81 | def value_iter(): 82 | yield 'aaa', 'aaa', n.aaa 83 | 84 | s = StringIO() 85 | ValueSource.write(value_iter, output_stream=s) 86 | received = s.getvalue() 87 | s.close() 88 | jrec = json.loads(received) 89 | 90 | expect_to_find = { 91 | "short_form": "a", 92 | "default": "2011-05-04T15:10:00", 93 | "doc": "the a", 94 | "value": "2011-05-04T15:10:00", 95 | "from_string_converter": 96 | "configman.datetime_util.datetime_from_ISO_string", 97 | "name": "aaa" 98 | } 99 | for key, value in expect_to_find.items(): 100 | self.assertEqual(jrec['aaa'][key], value) 101 | 102 | def test_json_round_trip(self): 103 | n = config_manager.Namespace(doc='top') 104 | n.add_option('aaa', '2011-05-04T15:10:00', 'the a', 105 | short_form='a', 106 | from_string_converter=dtu.datetime_from_ISO_string 107 | ) 108 | expected_date = dtu.datetime_from_ISO_string('2011-05-04T15:10:00') 109 | n.add_option('bbb', '37', 'the a', 110 | short_form='a', 111 | from_string_converter=int 112 | ) 113 | n.add_option('write', 'json') 114 | n.add_aggregation('bbb_minus_one', bbb_minus_one) 115 | #t = tempfile.NamedTemporaryFile('w', suffix='.json', delete=False) 116 | name = '/tmp/test.json' 117 | import functools 118 | opener = functools.partial(open, name, 'w') 119 | c1 = config_manager.ConfigurationManager([n], [], 120 | use_admin_controls=True, 121 | use_auto_help=False, 122 | app_name='/tmp/test', 123 | app_version='0', 124 | app_description='', 125 | argv_source=[]) 126 | c1.write_conf('json', opener) 127 | d1 = {'bbb': 88} 128 | d2 = {'bbb': '-99'} 129 | try: 130 | with open(name) as jfp: 131 | j = json.load(jfp) 132 | c2 = config_manager.ConfigurationManager((j,), (d1, d2), 133 | use_admin_controls=True, 134 | use_auto_help=False, 135 | argv_source=[]) 136 | config = c2.get_config() 137 | self.assertEqual(config.aaa, expected_date) 138 | self.assertEqual(config.bbb, -99) 139 | self.assertEqual(config.bbb_minus_one, -100) 140 | 141 | finally: 142 | os.unlink(name) 143 | -------------------------------------------------------------------------------- /demo/demo2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # ***** BEGIN LICENSE BLOCK ***** 3 | # Version: MPL 1.1/GPL 2.0/LGPL 2.1 4 | # 5 | # The contents of this file are subject to the Mozilla Public License Version 6 | # 1.1 (the "License"); you may not use this file except in compliance with 7 | # the License. You may obtain a copy of the License at 8 | # http://www.mozilla.org/MPL/ 9 | # 10 | # Software distributed under the License is distributed on an "AS IS" basis, 11 | # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 12 | # for the specific language governing rights and limitations under the 13 | # License. 14 | # 15 | # The Original Code is configman 16 | # 17 | # The Initial Developer of the Original Code is 18 | # Mozilla Foundation 19 | # Portions created by the Initial Developer are Copyright (C) 2011 20 | # the Initial Developer. All Rights Reserved. 21 | # 22 | # Contributor(s): 23 | # K Lars Lohn, lars@mozilla.com 24 | # Peter Bengtsson, peterbe@mozilla.com 25 | # 26 | # Alternatively, the contents of this file may be used under the terms of 27 | # either the GNU General Public License Version 2 or later (the "GPL"), or 28 | # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 29 | # in which case the provisions of the GPL or the LGPL are applicable instead 30 | # of those above. If you wish to allow use of your version of this file only 31 | # under the terms of either the GPL or the LGPL, and not to allow others to 32 | # use your version of this file under the terms of the MPL, indicate your 33 | # decision by deleting the provisions above and replace them with the notice 34 | # and other provisions required by the GPL or the LGPL. If you do not delete 35 | # the provisions above, a recipient may use your version of this file under 36 | # the terms of any one of the MPL, the GPL or the LGPL. 37 | # 38 | # ***** END LICENSE BLOCK ***** 39 | 40 | """This sample application demonstrates a simple way to use configman.""" 41 | # this second demo shows how to use configman in the same manner that one would 42 | # use other libraries like argparse. We have a collection of functions that 43 | # embody the business logic of the application. We setup configuration 44 | # parameters that will control the command line and config file forms. Then 45 | # we run the application. 46 | # In this case, there is no need for a 'main' function. The action done by the 47 | # application is specified in configuration. The last line of the file invokes 48 | # the action. 49 | 50 | from configman import ConfigurationManager, Namespace 51 | from configman import environment, command_line 52 | from configman.converters import class_converter 53 | 54 | 55 | # the following three functions are the business logic of the application. 56 | def echo(x): 57 | print x 58 | 59 | 60 | def backwards(x): 61 | print x[::-1] 62 | 63 | 64 | def upper(x): 65 | print x.upper() 66 | 67 | action_dispatch = {'echo': echo, 68 | 'backwards': backwards, 69 | 'upper': upper 70 | } 71 | 72 | 73 | def action_converter(action): 74 | try: 75 | return action_dispatch[action] 76 | except KeyError: 77 | try: 78 | f = class_converter(action) 79 | except Exception: 80 | raise Exception("'%s' is not a valid action" % action) 81 | if f in action_dispatch.values(): 82 | return f 83 | raise Exception("'%s' is not a valid action" % action) 84 | 85 | # create the definitions for the parameters that are to come from 86 | # the command line or config file. 87 | definition_source = Namespace() 88 | definition_source.add_option('text', 89 | 'Socorro Forever', 90 | 'the text input value', 91 | short_form='t') 92 | # this application doesn't have a main function. This parameter 93 | # definition sets up what function will be executed on invocation of 94 | # of this script. 95 | definition_source.add_option('action', 96 | 'echo', 97 | 'the action to take [%s]' % 98 | ', '.join(action_dispatch), 99 | short_form='a', 100 | from_string_converter=action_converter) 101 | 102 | # this time, we're not going to accept the default list of value sources for 103 | # the definitions created above. 'value_sources' is a sequence of objects that 104 | # can be interpretted by the ConfigurationManager as a source of values for the 105 | # options. Each source will be queried in turn for values. Values gleaned 106 | # from sources on the left may be overridden by values from sources to the 107 | # right. In this example, we're hard coding the the demo2.ini file as a 108 | # source of values. The user will not be given the opporuntity to specify a 109 | # config file of their own. After reading the hard coded config file, the 110 | # ConfigurationManager will apply values it got from the environment and then, 111 | # finally, apply values that it gets from the command line. 112 | value_sources = ('demo2.ini', environment, command_line) 113 | # the value_sources sequence can contian any object that is a derivation of the 114 | # type collections.Mapping, a module, or instances of any of the registered 115 | # handlers. cm.environment is just an alias for os.environ. cm.command_line is 116 | # an alias for the 'getopt' module, a registerd handler. 117 | 118 | # set up the manager with the definitions and values 119 | c = ConfigurationManager(definition_source, 120 | value_sources, 121 | app_name='demo2', 122 | app_description=__doc__) 123 | 124 | # fetch the DotDict version of the values 125 | config = c.get_config() 126 | 127 | # use the config 128 | config.action(config.text) 129 | -------------------------------------------------------------------------------- /configman/option.py: -------------------------------------------------------------------------------- 1 | # ***** BEGIN LICENSE BLOCK ***** 2 | # Version: MPL 1.1/GPL 2.0/LGPL 2.1 3 | # 4 | # The contents of this file are subject to the Mozilla Public License Version 5 | # 1.1 (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # http://www.mozilla.org/MPL/ 8 | # 9 | # Software distributed under the License is distributed on an "AS IS" basis, 10 | # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | # for the specific language governing rights and limitations under the 12 | # License. 13 | # 14 | # The Original Code is configman 15 | # 16 | # The Initial Developer of the Original Code is 17 | # Mozilla Foundation 18 | # Portions created by the Initial Developer are Copyright (C) 2011 19 | # the Initial Developer. All Rights Reserved. 20 | # 21 | # Contributor(s): 22 | # K Lars Lohn, lars@mozilla.com 23 | # Peter Bengtsson, peterbe@mozilla.com 24 | # 25 | # Alternatively, the contents of this file may be used under the terms of 26 | # either the GNU General Public License Version 2 or later (the "GPL"), or 27 | # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 28 | # in which case the provisions of the GPL or the LGPL are applicable instead 29 | # of those above. If you wish to allow use of your version of this file only 30 | # under the terms of either the GPL or the LGPL, and not to allow others to 31 | # use your version of this file under the terms of the MPL, indicate your 32 | # decision by deleting the provisions above and replace them with the notice 33 | # and other provisions required by the GPL or the LGPL. If you do not delete 34 | # the provisions above, a recipient may use your version of this file under 35 | # the terms of any one of the MPL, the GPL or the LGPL. 36 | # 37 | # ***** END LICENSE BLOCK ***** 38 | 39 | import collections 40 | 41 | import converters as conv 42 | from config_exceptions import CannotConvertError 43 | 44 | 45 | #============================================================================== 46 | class Option(object): 47 | #-------------------------------------------------------------------------- 48 | def __init__(self, 49 | name, 50 | default=None, 51 | doc=None, 52 | from_string_converter=None, 53 | value=None, 54 | short_form=None, 55 | exclude_from_print_conf=False, 56 | exclude_from_dump_conf=False, 57 | ): 58 | self.name = name 59 | self.short_form = short_form 60 | self.default = default 61 | self.doc = doc 62 | if from_string_converter is None: 63 | if default is not None: 64 | # take a qualified guess from the default value 65 | from_string_converter = self._deduce_converter(default) 66 | if isinstance(from_string_converter, basestring): 67 | from_string_converter = conv.class_converter(from_string_converter) 68 | self.from_string_converter = from_string_converter 69 | if value is None: 70 | value = default 71 | self.set_value(value) 72 | if (type(self.value) != type(self.default) 73 | and self.from_string_converter): 74 | # need to convert the default too 75 | self.default = self.from_string_converter(self.default) 76 | self.exclude_from_print_conf = exclude_from_print_conf 77 | self.exclude_from_dump_conf = exclude_from_dump_conf 78 | 79 | def __eq__(self, other): 80 | if isinstance(other, Option): 81 | return (self.name == other.name 82 | and 83 | self.default == other.default 84 | and 85 | self.doc == other.doc 86 | and 87 | self.short_form == other.short_form 88 | and 89 | self.value == other.value 90 | ) 91 | 92 | def __repr__(self): # pragma: no cover 93 | if self.default is None: 94 | return '' % self.name 95 | else: 96 | return '' % (self.name, self.default) 97 | 98 | #-------------------------------------------------------------------------- 99 | def _deduce_converter(self, default): 100 | default_type = type(default) 101 | return conv.from_string_converters.get(default_type, default_type) 102 | 103 | #-------------------------------------------------------------------------- 104 | def set_value(self, val): 105 | if isinstance(val, basestring): 106 | try: 107 | self.value = self.from_string_converter(val) 108 | except TypeError: 109 | self.value = val 110 | except ValueError: 111 | raise CannotConvertError(val) 112 | elif isinstance(val, Option): 113 | self.value = val.default 114 | elif isinstance(val, collections.Mapping) and 'default' in val: 115 | self.set_value(val["default"]) 116 | else: 117 | self.value = val 118 | 119 | 120 | #============================================================================== 121 | class Aggregation(object): 122 | #-------------------------------------------------------------------------- 123 | def __init__(self, 124 | name, 125 | function): 126 | self.name = name 127 | if isinstance(function, basestring): 128 | self.function = conv.class_converter(function) 129 | else: 130 | self.function = function 131 | self.value = None 132 | 133 | #-------------------------------------------------------------------------- 134 | def aggregate(self, all_options, local_namespace, args): 135 | self.value = self.function(all_options, local_namespace, args) 136 | 137 | 138 | -------------------------------------------------------------------------------- /configman/tests/test_datetime_util.py: -------------------------------------------------------------------------------- 1 | # ***** BEGIN LICENSE BLOCK ***** 2 | # Version: MPL 1.1/GPL 2.0/LGPL 2.1 3 | # 4 | # The contents of this file are subject to the Mozilla Public License Version 5 | # 1.1 (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # http://www.mozilla.org/MPL/ 8 | # 9 | # Software distributed under the License is distributed on an "AS IS" basis, 10 | # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | # for the specific language governing rights and limitations under the 12 | # License. 13 | # 14 | # The Original Code is configman 15 | # 16 | # The Initial Developer of the Original Code is 17 | # Mozilla Foundation 18 | # Portions created by the Initial Developer are Copyright (C) 2011 19 | # the Initial Developer. All Rights Reserved. 20 | # 21 | # Contributor(s): 22 | # K Lars Lohn, lars@mozilla.com 23 | # Peter Bengtsson, peterbe@mozilla.com 24 | # 25 | # Alternatively, the contents of this file may be used under the terms of 26 | # either the GNU General Public License Version 2 or later (the "GPL"), or 27 | # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 28 | # in which case the provisions of the GPL or the LGPL are applicable instead 29 | # of those above. If you wish to allow use of your version of this file only 30 | # under the terms of either the GPL or the LGPL, and not to allow others to 31 | # use your version of this file under the terms of the MPL, indicate your 32 | # decision by deleting the provisions above and replace them with the notice 33 | # and other provisions required by the GPL or the LGPL. If you do not delete 34 | # the provisions above, a recipient may use your version of this file under 35 | # the terms of any one of the MPL, the GPL or the LGPL. 36 | # 37 | # ***** END LICENSE BLOCK ***** 38 | 39 | import unittest 40 | import datetime 41 | import configman.datetime_util as datetime_util 42 | 43 | 44 | class TestCase(unittest.TestCase): 45 | 46 | def test_datetime_from_ISO_string(self): 47 | function = datetime_util.datetime_from_ISO_string 48 | 49 | inp = '2011-05-04T15:10:00' 50 | out = function(inp) 51 | self.assertTrue(isinstance(out, datetime.datetime)) 52 | self.assertEqual(out.strftime('%Y-%m-%dT%H:%M:%S'), inp) 53 | 54 | inp = '2011-05-04' 55 | out = function(inp) 56 | self.assertTrue(isinstance(out, datetime.datetime)) 57 | self.assertEqual(out.strftime('%Y-%m-%dT%H:%M:%S'), inp + 'T00:00:00') 58 | 59 | inp = '2011-05-04T15:10:00.666000' 60 | out = function(inp) 61 | self.assertTrue(isinstance(out, datetime.datetime)) 62 | self.assertEqual(out.microsecond, 666000) 63 | self.assertEqual(out.strftime('%Y-%m-%dT%H:%M:%S.%f'), inp) 64 | 65 | # failing conditions 66 | self.assertRaises(ValueError, function, '211-05-04T15:10:00') 67 | self.assertRaises(ValueError, function, '2011-02-30T15:10:00') 68 | self.assertRaises(ValueError, function, '2011-02-26T24:10:00') 69 | self.assertRaises(ValueError, function, '2011-02-26T23:10:00.xxx') 70 | self.assertRaises(ValueError, function, '211-05-32') 71 | self.assertRaises(ValueError, function, '2011-05-32') 72 | 73 | def test_date_from_ISO_string(self): 74 | function = datetime_util.date_from_ISO_string 75 | 76 | inp = '2011-05-04' 77 | out = function(inp) 78 | self.assertTrue(isinstance(out, datetime.date)) 79 | self.assertEqual(out.strftime('%Y-%m-%d'), inp) 80 | 81 | inp = '2011-1-2' 82 | out = function(inp) 83 | self.assertTrue(isinstance(out, datetime.date)) 84 | self.assertEqual(out.month, 1) 85 | self.assertEqual(out.day, 2) 86 | 87 | # failing conditions 88 | self.assertRaises(ValueError, function, '2011-05-04T15:10:00') 89 | self.assertRaises(ValueError, function, '211-05-04') 90 | self.assertRaises(ValueError, function, '2011-05-32') 91 | 92 | def test_hours_str_to_timedelta(self): 93 | function = datetime_util.hours_str_to_timedelta 94 | self.assertEqual(function(1), datetime.timedelta(hours=1)) 95 | 96 | def test_date_to_ISO_string(self): 97 | function = datetime_util.date_to_ISO_string 98 | d = datetime.datetime.now() 99 | self.assertEqual(function(d), 100 | d.strftime('%Y-%m-%d')) 101 | 102 | d = datetime.date.today() 103 | self.assertEqual(function(d), 104 | d.strftime('%Y-%m-%d')) 105 | 106 | def test_timedelta_to_seconds(self): 107 | function = datetime_util.timedelta_to_seconds 108 | self.assertEqual(function(datetime.timedelta(hours=1)), 3600) 109 | self.assertEqual(function(datetime.timedelta(hours=2)), 2 * 3600) 110 | self.assertEqual(function(datetime.timedelta(minutes=1)), 60) 111 | self.assertEqual(function(datetime.timedelta(seconds=1)), 1) 112 | 113 | def test_str_to_timedelta(self): 114 | function = datetime_util.str_to_timedelta 115 | self.assertEqual(function('1:1:1:01'), 116 | datetime.timedelta(days=1, 117 | hours=1, 118 | minutes=1, 119 | seconds=1)) 120 | 121 | self.assertEqual(function('1:1:1'), 122 | datetime.timedelta(hours=1, 123 | minutes=1, 124 | seconds=1)) 125 | 126 | self.assertEqual(function('1:1'), 127 | datetime.timedelta(minutes=1, 128 | seconds=1)) 129 | 130 | self.assertEqual(function('1'), 131 | datetime.timedelta(seconds=1)) 132 | self.assertRaises(ValueError, function, 'not a number') 133 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | # the i18n builder cannot share the environment and doctrees with the others 15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 16 | 17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 18 | 19 | help: 20 | @echo "Please use \`make ' where is one of" 21 | @echo " html to make standalone HTML files" 22 | @echo " dirhtml to make HTML files named index.html in directories" 23 | @echo " singlehtml to make a single large HTML file" 24 | @echo " pickle to make pickle files" 25 | @echo " json to make JSON files" 26 | @echo " htmlhelp to make HTML files and a HTML help project" 27 | @echo " qthelp to make HTML files and a qthelp project" 28 | @echo " devhelp to make HTML files and a Devhelp project" 29 | @echo " epub to make an epub" 30 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 31 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 32 | @echo " text to make text files" 33 | @echo " man to make manual pages" 34 | @echo " texinfo to make Texinfo files" 35 | @echo " info to make Texinfo files and run them through makeinfo" 36 | @echo " gettext to make PO message catalogs" 37 | @echo " changes to make an overview of all changed/added/deprecated items" 38 | @echo " linkcheck to check all external links for integrity" 39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 40 | 41 | clean: 42 | -rm -rf $(BUILDDIR)/* 43 | 44 | html: 45 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 48 | 49 | dirhtml: 50 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 51 | @echo 52 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 53 | 54 | singlehtml: 55 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 56 | @echo 57 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 58 | 59 | pickle: 60 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 61 | @echo 62 | @echo "Build finished; now you can process the pickle files." 63 | 64 | json: 65 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 66 | @echo 67 | @echo "Build finished; now you can process the JSON files." 68 | 69 | htmlhelp: 70 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 71 | @echo 72 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 73 | ".hhp project file in $(BUILDDIR)/htmlhelp." 74 | 75 | qthelp: 76 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 77 | @echo 78 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 79 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 80 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/configman.qhcp" 81 | @echo "To view the help file:" 82 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/configman.qhc" 83 | 84 | devhelp: 85 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 86 | @echo 87 | @echo "Build finished." 88 | @echo "To view the help file:" 89 | @echo "# mkdir -p $$HOME/.local/share/devhelp/configman" 90 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/configman" 91 | @echo "# devhelp" 92 | 93 | epub: 94 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 95 | @echo 96 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 97 | 98 | latex: 99 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 100 | @echo 101 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 102 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 103 | "(use \`make latexpdf' here to do that automatically)." 104 | 105 | latexpdf: 106 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 107 | @echo "Running LaTeX files through pdflatex..." 108 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 109 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 110 | 111 | text: 112 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 113 | @echo 114 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 115 | 116 | man: 117 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 118 | @echo 119 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 120 | 121 | texinfo: 122 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 123 | @echo 124 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 125 | @echo "Run \`make' in that directory to run these through makeinfo" \ 126 | "(use \`make info' here to do that automatically)." 127 | 128 | info: 129 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 130 | @echo "Running Texinfo files through makeinfo..." 131 | make -C $(BUILDDIR)/texinfo info 132 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 133 | 134 | gettext: 135 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 136 | @echo 137 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 138 | 139 | changes: 140 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 141 | @echo 142 | @echo "The overview file is in $(BUILDDIR)/changes." 143 | 144 | linkcheck: 145 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 146 | @echo 147 | @echo "Link check complete; look for any errors in the above output " \ 148 | "or in $(BUILDDIR)/linkcheck/output.txt." 149 | 150 | doctest: 151 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 152 | @echo "Testing of doctests in the sources finished, look at the " \ 153 | "results in $(BUILDDIR)/doctest/output.txt." 154 | -------------------------------------------------------------------------------- /configman/value_sources/for_conf.py: -------------------------------------------------------------------------------- 1 | # ***** BEGIN LICENSE BLOCK ***** 2 | # Version: MPL 1.1/GPL 2.0/LGPL 2.1 3 | # 4 | # The contents of this file are subject to the Mozilla Public License Version 5 | # 1.1 (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # http://www.mozilla.org/MPL/ 8 | # 9 | # Software distributed under the License is distributed on an "AS IS" basis, 10 | # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | # for the specific language governing rights and limitations under the 12 | # License. 13 | # 14 | # The Original Code is configman 15 | # 16 | # The Initial Developer of the Original Code is 17 | # Mozilla Foundation 18 | # Portions created by the Initial Developer are Copyright (C) 2011 19 | # the Initial Developer. All Rights Reserved. 20 | # 21 | # Contributor(s): 22 | # K Lars Lohn, lars@mozilla.com 23 | # Peter Bengtsson, peterbe@mozilla.com 24 | # 25 | # Alternatively, the contents of this file may be used under the terms of 26 | # either the GNU General Public License Version 2 or later (the "GPL"), or 27 | # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 28 | # in which case the provisions of the GPL or the LGPL are applicable instead 29 | # of those above. If you wish to allow use of your version of this file only 30 | # under the terms of either the GPL or the LGPL, and not to allow others to 31 | # use your version of this file under the terms of the MPL, indicate your 32 | # decision by deleting the provisions above and replace them with the notice 33 | # and other provisions required by the GPL or the LGPL. If you do not delete 34 | # the provisions above, a recipient may use your version of this file under 35 | # the terms of any one of the MPL, the GPL or the LGPL. 36 | # 37 | # ***** END LICENSE BLOCK ***** 38 | 39 | """This module implements a configuration value source comprising a stream of 40 | textual key/value pairs. The implementation uses a ContextManger to iterate 41 | through the stream. The ContextManager can represent any number of sources, 42 | like files or database results. If supplied with a simple string rather than 43 | a ContextManager, the value source will assume it is a file pathname and try 44 | to open it. 45 | """ 46 | 47 | import functools 48 | import sys 49 | 50 | from .. import namespace 51 | from .. import option as opt 52 | from .. import converters as conv 53 | 54 | from source_exceptions import ValueException, CantHandleTypeException 55 | 56 | function_type = type(lambda x: x) # TODO: just how do you express the Fuction 57 | # type as a constant? 58 | # (peter: why not use inspect.isfunction()?) 59 | 60 | # the list of types that the contstuctor can handle. 61 | can_handle = (basestring, 62 | function_type # this is to say that this ValueSource is willing 63 | # to try a function that will return a 64 | # context manager 65 | ) 66 | 67 | file_name_extension = 'conf' 68 | 69 | 70 | class NotAConfigFileError(ValueException): 71 | pass 72 | 73 | 74 | class ValueSource(object): 75 | 76 | def __init__(self, candidate, the_config_manager=None): 77 | if (isinstance(candidate, basestring) and 78 | candidate.endswith(file_name_extension)): 79 | # we're trusting the string represents a filename 80 | opener = functools.partial(open, candidate) 81 | elif isinstance(candidate, function_type): 82 | # we're trusting that the function when called with no parameters 83 | # will return a Context Manager Type. 84 | opener = candidate 85 | else: 86 | raise CantHandleTypeException("Conf doesn't know how to handle" 87 | " %s." % str(candidate)) 88 | self.values = {} 89 | try: 90 | with opener() as f: 91 | previous_key = None 92 | for line in f: 93 | if line.strip().startswith('#') or not line.strip(): 94 | continue 95 | if line[0] in ' \t' and previous_key: 96 | line = line[1:] 97 | self.values[previous_key] = '%s%s' % \ 98 | (self.values[previous_key], 99 | line.rstrip()) 100 | continue 101 | try: 102 | key, value = line.split("=", 1) 103 | self.values[key.strip()] = value.strip() 104 | previous_key = key 105 | except ValueError: 106 | self.values[line] = '' 107 | except Exception, x: 108 | raise NotAConfigFileError("Conf couldn't interpret %s as a config " 109 | "file: %s" % (candidate, str(x))) 110 | 111 | def get_values(self, config_manager, ignore_mismatches): 112 | """the 'config_manager' and 'ignore_mismatches' are dummy values for 113 | this implementation of a ValueSource.""" 114 | return self.values 115 | 116 | @staticmethod 117 | def write(option_iter, output_stream=sys.stdout, comments=True): 118 | for qkey, key, val in option_iter(): 119 | if isinstance(val, namespace.Namespace): 120 | print >> output_stream, '#%s' % ('-' * 79) 121 | print >> output_stream, '# %s - %s\n' % (key, val._doc) 122 | elif isinstance(val, opt.Option): 123 | if comments: 124 | print >> output_stream, '# name:', qkey 125 | print >> output_stream, '# doc:', val.doc 126 | print >> output_stream, '# converter:', \ 127 | conv.py_obj_to_str(val.from_string_converter) 128 | val_str = conv.option_value_str(val) 129 | print >> output_stream, '%s=%s\n' % (qkey, val_str) 130 | elif isinstance(val, opt.Aggregation): 131 | # there is nothing to do for Aggregations at this time 132 | # it appears here anyway as a marker for future enhancements 133 | pass 134 | -------------------------------------------------------------------------------- /docs/typeconversion.rst: -------------------------------------------------------------------------------- 1 | .. _typeconversion: 2 | 3 | =============== 4 | Type conversion 5 | =============== 6 | 7 | ``configman`` comes with an advanced set of type conversion utilities. 8 | This is necessary since config files don't allow rich python type to 9 | be expressed. The way this is done is by turning things into strings 10 | and turning strings into rich python objects by labelling what type 11 | conversion script to use. 12 | 13 | A basic example is that of booleans as seen in the :ref:`Tutorial 14 | ` 15 | when it dumps the boolean ``devowel`` option as into an ``ini`` file. 16 | It looks like this:: 17 | 18 | [top_level] 19 | # name: devowel 20 | # doc: Removes all vowels (including Y) 21 | # converter: configman.converters.boolean_converter 22 | devowel=False 23 | 24 | As you can see it automatically figured out that the convertor should 25 | be ``configman.converters.boolean_converter``. As you can imagine; 26 | under the hood ``configman`` does something like this:: 27 | 28 | # pseudo code 29 | converter = __import__('configman.converters.boolean_converter') 30 | actual_value = converter('False') 31 | 32 | So, how did it know you wanted a boolean converter? It picked this up 33 | from the definition's default value's type itself. Reminder; from the 34 | :ref:`Tutorial `:: 35 | 36 | definition = Namespace() 37 | definition.add_option( 38 | 'devowel', 39 | default=False 40 | ) 41 | 42 | Built-ins 43 | --------- 44 | 45 | The list of ``configman`` built-in converters will get you very far for 46 | basic python types. The complete list is this: 47 | 48 | * **int** 49 | * **float** 50 | * **str** 51 | * **unicode** 52 | * **bool** 53 | * **datetime.datetime** (``%Y-%m-%dT%H:%M:%S`` or ``%Y-%m-%dT%H:%M:%S.%f``) 54 | * **datetime.date** (``%Y-%m-%d``) 55 | * **datetime.timedelta** (for example, ``1:2:0:3`` becomes ``days=1, 56 | hours=2, minutes=0, seconds=3``) 57 | * **type** (see below) 58 | * **types.FunctionType** (see below) 59 | * **compiled_regexp_type** 60 | 61 | The **type** amd **types.FunctionType** built-ins are simpler than 62 | they might seem. It's basically the same example pseudo code above. 63 | This example should demostrate how it might work:: 64 | 65 | import morse 66 | namespace.add_option( 67 | 'morsecode', 68 | '', 69 | 'Turns morse code into real letters', 70 | from_string_converter=morse.morse_load 71 | ) 72 | 73 | What this will do is it will import the python module ``morse`` and 74 | expect to find a function in there called ``morse_load``. Suppose we 75 | have one that looks like this:: 76 | 77 | # This is morse/__init__.py 78 | dictionary = { 79 | '.-.': 'p', 80 | '.': 'e', 81 | '-': 't', 82 | '.--.': 'r', 83 | } 84 | 85 | 86 | def morse_load(s): 87 | o = [] 88 | for e in s.split(','): 89 | o.append(dictionary.get(e.lower(), '?')) 90 | return ''.join(o) 91 | 92 | 93 | Another more advanced example is to load a *class* rather than a simple 94 | value. To do this you'll need to use one of the pre-defined ``configman`` 95 | converters as the ``from_string_converter`` value. To our example 96 | above we're going to add a configurable class:: 97 | 98 | from configman.converters import class_converter 99 | namespace.add_option( 100 | 'dialect', 101 | 'morse.ScottishDialect', 102 | 'A Scottish dialect class for the morse code converter', 103 | from_string_converter=class_converter 104 | ) 105 | 106 | That needs to exist as an importable class. So we add it:: 107 | 108 | # This is morse/__init__.py 109 | class ScottishDialect(object): 110 | def __init__(self, text): 111 | self.text = text 112 | 113 | def render(self): 114 | return self.text.replace('e', 'i').replace('E','I') 115 | 116 | 117 | Now, this means that the class is configurable and you can refer to a 118 | specific class simply by name and it becomes available in your 119 | program. For example, in this trivial example we can use it like this:: 120 | 121 | if __name__ == '__main__': 122 | config = create_config() 123 | dialect = config.dialect(config.morsecode) 124 | print dialect.render() 125 | 126 | If you run this like this:: 127 | 128 | $ python morse-communicator.py --morsecode=.,-,.--.,-,. 129 | itrti 130 | 131 | This is just an example to whet your appetite but a more realistic 132 | example is that you might have a configurable class for 133 | sending emails. In production you might have it wired to be to 134 | something like this:: 135 | 136 | namespace.add_option( 137 | 'email_send_class', 138 | 'backends.SMTP', 139 | 'Which backend should send the emails', 140 | from_string_converter=class_converter 141 | ) 142 | namespace.add_option( 143 | 'smtp_hostname', 144 | default='smtp.mozilla.org', 145 | ) 146 | namespace.add_option( 147 | 'smtp_username', 148 | doc='username for using the SMTP server' 149 | ) 150 | namespace.add_option( 151 | 'smtp_password', 152 | doc='password for using the SMTP server' 153 | ) 154 | 155 | Then, suppose you have different backends for sending SMTP available 156 | you might want to run it like this when doing local development:: 157 | 158 | # name: email_send_class 159 | # doc: Which backend should send the emails 160 | # converter: configman.converters.class_converter 161 | dialect=backends.StdoutLogDumper 162 | 163 | So that instead of sending over the network (which was default) it 164 | uses another class which knows to just print the emails being sent on 165 | the stdout or some log file or something. 166 | 167 | Not built-ins 168 | ------------- 169 | 170 | Suppose none of the built-ins in ``configman`` is what you want. There's 171 | nothing stopping you from just writing down your own. Consider this 172 | tip calculator for example:: 173 | 174 | import getopt 175 | from configman import Namespace, ConfigurationManager 176 | 177 | 178 | def create_config(): 179 | namespace = Namespace() 180 | namespace.add_option( 181 | 'tip', 182 | default=20 183 | ) 184 | import decimal 185 | namespace.add_option( 186 | 'amount', 187 | from_string_converter=decimal.Decimal 188 | ) 189 | value_sources = ('tipcalc.ini', getopt, ) 190 | config_manager = ConfigurationManager([namespace], value_sources) 191 | return config_manager.get_config() 192 | 193 | 194 | if __name__ == '__main__': 195 | config = create_config() 196 | tip_amount = config.amount * config.tip / 100 197 | print "(exact amount: %r)" % tip_amount 198 | print '$%.2f' % tip_amount 199 | 200 | When run it will automatically convert whatever number you give it to 201 | a python ``Decimal`` type. Note how in the example it prints the 202 | ``repr`` of the calculated value:: 203 | 204 | $ python tipcalc.py --amount 100.59 --tip=25 205 | (exact amount: Decimal('25.1475')) 206 | $25.15 207 | 208 | 209 | 210 | -------------------------------------------------------------------------------- /demo/data_store.py: -------------------------------------------------------------------------------- 1 | # ***** BEGIN LICENSE BLOCK ***** 2 | # Version: MPL 1.1/GPL 2.0/LGPL 2.1 3 | # 4 | # The contents of this file are subject to the Mozilla Public License Version 5 | # 1.1 (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # http://www.mozilla.org/MPL/ 8 | # 9 | # Software distributed under the License is distributed on an "AS IS" basis, 10 | # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | # for the specific language governing rights and limitations under the 12 | # License. 13 | # 14 | # The Original Code is configman 15 | # 16 | # The Initial Developer of the Original Code is 17 | # Mozilla Foundation 18 | # Portions created by the Initial Developer are Copyright (C) 2011 19 | # the Initial Developer. All Rights Reserved. 20 | # 21 | # Contributor(s): 22 | # K Lars Lohn, lars@mozilla.com 23 | # Peter Bengtsson, peterbe@mozilla.com 24 | # 25 | # Alternatively, the contents of this file may be used under the terms of 26 | # either the GNU General Public License Version 2 or later (the "GPL"), or 27 | # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 28 | # in which case the provisions of the GPL or the LGPL are applicable instead 29 | # of those above. If you wish to allow use of your version of this file only 30 | # under the terms of either the GPL or the LGPL, and not to allow others to 31 | # use your version of this file under the terms of the MPL, indicate your 32 | # decision by deleting the provisions above and replace them with the notice 33 | # and other provisions required by the GPL or the LGPL. If you do not delete 34 | # the provisions above, a recipient may use your version of this file under 35 | # the terms of any one of the MPL, the GPL or the LGPL. 36 | # 37 | # ***** END LICENSE BLOCK ***** 38 | 39 | """This file is the source of the simulated database classes used in the 40 | dyn_app example.""" 41 | 42 | import sys 43 | import datetime as dt 44 | 45 | import configman.config_manager as cm 46 | import configman.namespace as ns 47 | import configman.converters as conv 48 | 49 | def log(message): 50 | print >>sys.stderr, dt.datetime.now(), message 51 | 52 | 53 | class Database(cm.RequiredConfig): 54 | """This is the base class for the Postgres and MySQL simulators. It 55 | defines the common config parameters and implements the input and 56 | output functions. Since these are fake and don't really connect to 57 | any database, we arbitrarily chose to use a tuple as a "connection" 58 | object""" 59 | # setup common config parameters to be used by Postgres and MySQL 60 | required_config = ns.Namespace() 61 | required_config.add_option('hostname', 'localhost', 'the hostname') 62 | required_config.add_option('username', 'fred', 'the username') 63 | required_config.add_option('password', 'secrets', 'the password') 64 | 65 | def __init__(self, config): 66 | super(Database, self).__init__() 67 | self.config = config 68 | self.class_as_string = conv.py_obj_to_str(self.__class__) 69 | self.connection = (0, 0) 70 | 71 | def canned_query(self): 72 | """a generator that yield simulated database rows, logging each one""" 73 | for i in range(*self.connection): 74 | row = (i, i*10, chr(i+32)) 75 | log('%s fetching row: %s' % (self.class_as_string, row)) 76 | yield row 77 | 78 | def write(self, row): 79 | """since this is a simulation, no actual insert take place. We just 80 | log that we've gotten a row to insert""" 81 | log('%s inserting: %s' % (self.class_as_string, row)) 82 | 83 | class Postgres(Database): 84 | required_config = ns.Namespace() 85 | # we setup the 'port' config parameter to match the default Postgres 86 | # port number. 87 | required_config.add_option('port', 5432, 'the connection port') 88 | 89 | def __init__(self, config): 90 | super(Postgres, self).__init__(config) 91 | log('connecting to Fake Postgres with %s' % self.class_as_string) 92 | self.connection = (10,20) 93 | 94 | 95 | class MySQL(Database): 96 | required_config = ns.Namespace() 97 | # we setup the 'port' config parameter to match the default MySQL 98 | # port number. 99 | required_config.add_option('port', 3306, 'the connection port') 100 | 101 | def __init__(self, config): 102 | super(MySQL, self).__init__(config) 103 | log('connecting to Fake MySQL with %s' % self.class_as_string) 104 | self.connection = (50,60) 105 | 106 | 107 | class HBase(cm.RequiredConfig): 108 | """Since HBase isn't really a relational database, we use a separate 109 | class hierarchy for it.""" 110 | required_config = ns.Namespace() 111 | required_config.add_option('hostname', 'localhost', 'the HBase hostname') 112 | required_config.add_option('port', 9090, 'the HBase port') 113 | 114 | def __init__(self, config): 115 | super(HBase, self).__init__() 116 | self.class_as_string = conv.py_obj_to_str(self.__class__) 117 | self.config = config 118 | log('connecting to Fake HBase with %s' % self.class_as_string) 119 | self.connection = (100, 90, -1) 120 | 121 | def canned_query(self): 122 | for i in range(*self.connection): 123 | log('%s fetching row: %s' % (self.class_as_string, i)) 124 | yield i, i*10, chr(i+32) 125 | 126 | def write(self, row): 127 | log('%s inserting %s' % (self.class_as_string, str(row))) 128 | 129 | 130 | class CannedDataSource(cm.RequiredConfig): 131 | required_config = ns.Namespace() 132 | required_config.add_option('database_type', 'data_store.Postgres', 133 | 'the type of database to connect to', 134 | from_string_converter=conv.class_converter) 135 | 136 | def __init__(self, config): 137 | print self.__class__ 138 | self.class_as_string = conv.py_obj_to_str(self.__class__) 139 | log('starting %s' % self.class_as_string) 140 | self.config = config 141 | self.datasource = config.source.database_type(config) 142 | 143 | def fetch(self): 144 | log('starting fetch from %s' % self.class_as_string) 145 | return self.datasource.canned_query() 146 | 147 | class CannedDataSink(cm.RequiredConfig): 148 | required_config = ns.Namespace() 149 | required_config.add_option('database_type', 'data_store.HBase', 150 | 'the type of database to connect to', 151 | from_string_converter=conv.class_converter) 152 | 153 | def __init__(self, config): 154 | self.class_as_string = conv.py_obj_to_str(self.__class__) 155 | log('starting %s' % self.class_as_string) 156 | self.config = config 157 | self.data_sink = config.destination.database_type(config) 158 | 159 | def write(self, row): 160 | self.data_sink.write(row) -------------------------------------------------------------------------------- /configman/value_sources/for_configparse.py: -------------------------------------------------------------------------------- 1 | # ***** BEGIN LICENSE BLOCK ***** 2 | # Version: MPL 1.1/GPL 2.0/LGPL 2.1 3 | # 4 | # The contents of this file are subject to the Mozilla Public License Version 5 | # 1.1 (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # http://www.mozilla.org/MPL/ 8 | # 9 | # Software distributed under the License is distributed on an "AS IS" basis, 10 | # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | # for the specific language governing rights and limitations under the 12 | # License. 13 | # 14 | # The Original Code is configman 15 | # 16 | # The Initial Developer of the Original Code is 17 | # Mozilla Foundation 18 | # Portions created by the Initial Developer are Copyright (C) 2011 19 | # the Initial Developer. All Rights Reserved. 20 | # 21 | # Contributor(s): 22 | # K Lars Lohn, lars@mozilla.com 23 | # Peter Bengtsson, peterbe@mozilla.com 24 | # 25 | # Alternatively, the contents of this file may be used under the terms of 26 | # either the GNU General Public License Version 2 or later (the "GPL"), or 27 | # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 28 | # in which case the provisions of the GPL or the LGPL are applicable instead 29 | # of those above. If you wish to allow use of your version of this file only 30 | # under the terms of either the GPL or the LGPL, and not to allow others to 31 | # use your version of this file under the terms of the MPL, indicate your 32 | # decision by deleting the provisions above and replace them with the notice 33 | # and other provisions required by the GPL or the LGPL. If you do not delete 34 | # the provisions above, a recipient may use your version of this file under 35 | # the terms of any one of the MPL, the GPL or the LGPL. 36 | # 37 | # ***** END LICENSE BLOCK ***** 38 | 39 | import sys 40 | import ConfigParser 41 | 42 | from source_exceptions import (CantHandleTypeException, ValueException, 43 | NotEnoughInformationException) 44 | from ..config_exceptions import NotAnOptionError 45 | 46 | from .. import namespace 47 | from .. import option 48 | from .. import converters as conv 49 | 50 | file_name_extension = 'ini' 51 | 52 | can_handle = (ConfigParser, 53 | ConfigParser.RawConfigParser, # just the base class, subclasses 54 | # will be detected too 55 | basestring, 56 | ) 57 | 58 | 59 | class LoadingIniFileFailsException(ValueException): 60 | pass 61 | 62 | 63 | class ValueSource(object): 64 | 65 | def __init__(self, source, 66 | config_manager=None, 67 | top_level_section_name='top_level'): 68 | self.delayed_parser_instantiation = False 69 | self.top_level_section_name = top_level_section_name 70 | if source is ConfigParser: 71 | try: 72 | app = config_manager._get_option('admin.application') 73 | source = "%s.%s" % (app.value.app_name, file_name_extension) 74 | except (AttributeError, NotAnOptionError): 75 | # we likely don't have the admin.application object set up yet. 76 | # we need to delay the instantiation of the ConfigParser 77 | # until later. 78 | if source is None: 79 | raise NotEnoughInformationException( 80 | "Can't setup an %s file without knowing the file name" 81 | % file_name_extension) 82 | self.delayed_parser_instantiation = True 83 | return 84 | if (isinstance(source, basestring) and 85 | source.endswith(file_name_extension)): 86 | try: 87 | self.configparser = self._create_parser(source) 88 | except Exception, x: 89 | # FIXME: this doesn't give you a clue why it fail. 90 | # Was it because the file didn't exist (IOError) or because it 91 | # was badly formatted?? 92 | raise LoadingIniFileFailsException( 93 | "ConfigParser cannot load file: %s" % str(x)) 94 | elif isinstance(source, ConfigParser.RawConfigParser): 95 | self.configparser = source 96 | else: 97 | raise CantHandleTypeException( 98 | "ConfigParser doesn't know how to handle %s." % source) 99 | 100 | @staticmethod 101 | def _create_parser(source): 102 | parser = ConfigParser.ConfigParser() 103 | parser.optionxform = str 104 | parser.read(source) 105 | return parser 106 | 107 | def get_values(self, config_manager, ignore_mismatches): 108 | """Return a nested dictionary representing the values in the ini file. 109 | In the case of this ValueSource implementation, both parameters are 110 | dummies.""" 111 | if self.delayed_parser_instantiation: 112 | try: 113 | app = config_manager._get_option('admin.application') 114 | source = "%s%s" % (app.value.app_name, file_name_extension) 115 | self.configparser = self._create_parser(source) 116 | self.delayed_parser_instantiation = False 117 | except (AttributeError, NotAnOptionError): 118 | # we don't have enough information to get the ini file 119 | # yet. we'll ignore the error for now 120 | return {} 121 | options = {} 122 | for a_section in self.configparser.sections(): 123 | if a_section == self.top_level_section_name: 124 | prefix = '' 125 | else: 126 | prefix = "%s." % a_section 127 | for an_option in self.configparser.options(a_section): 128 | name = '%s%s' % (prefix, an_option) 129 | options[name] = self.configparser.get(a_section, an_option) 130 | return options 131 | 132 | @staticmethod 133 | def write(option_iter, output_stream=sys.stdout): 134 | print >> output_stream, '[top_level]' 135 | for qkey, key, val in option_iter(): 136 | if isinstance(val, namespace.Namespace): 137 | print >> output_stream, '[%s]' % qkey 138 | print >> output_stream, '# %s\n' % val._doc 139 | elif isinstance(val, option.Option): 140 | print >> output_stream, '# name:', qkey 141 | print >> output_stream, '# doc:', val.doc 142 | print >> output_stream, '# converter:', \ 143 | conv.py_obj_to_str(val.from_string_converter) 144 | val_str = conv.option_value_str(val) 145 | print >> output_stream, '%s=%s\n' % (key, val_str) 146 | elif isinstance(val, option.Aggregation): 147 | # there is nothing to do for Aggregations at this time 148 | # it appears here anyway as a marker for future enhancements 149 | pass 150 | -------------------------------------------------------------------------------- /configman/tests/test_val_for_configobj.py: -------------------------------------------------------------------------------- 1 | # ***** BEGIN LICENSE BLOCK ***** 2 | # Version: MPL 1.1/GPL 2.0/LGPL 2.1 3 | # 4 | # The contents of this file are subject to the Mozilla Public License Version 5 | # 1.1 (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # http://www.mozilla.org/MPL/ 8 | # 9 | # Software distributed under the License is distributed on an "AS IS" basis, 10 | # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | # for the specific language governing rights and limitations under the 12 | # License. 13 | # 14 | # The Original Code is configman 15 | # 16 | # The Initial Developer of the Original Code is 17 | # Mozilla Foundation 18 | # Portions created by the Initial Developer are Copyright (C) 2011 19 | # the Initial Developer. All Rights Reserved. 20 | # 21 | # Contributor(s): 22 | # K Lars Lohn, lars@mozilla.com 23 | # Peter Bengtsson, peterbe@mozilla.com 24 | # 25 | # Alternatively, the contents of this file may be used under the terms of 26 | # either the GNU General Public License Version 2 or later (the "GPL"), or 27 | # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 28 | # in which case the provisions of the GPL or the LGPL are applicable instead 29 | # of those above. If you wish to allow use of your version of this file only 30 | # under the terms of either the GPL or the LGPL, and not to allow others to 31 | # use your version of this file under the terms of the MPL, indicate your 32 | # decision by deleting the provisions above and replace them with the notice 33 | # and other provisions required by the GPL or the LGPL. If you do not delete 34 | # the provisions above, a recipient may use your version of this file under 35 | # the terms of any one of the MPL, the GPL or the LGPL. 36 | # 37 | # ***** END LICENSE BLOCK ***** 38 | 39 | import unittest 40 | import os 41 | import tempfile 42 | from cStringIO import StringIO 43 | import contextlib 44 | 45 | import configman.datetime_util as dtu 46 | import configman.config_manager as config_manager 47 | try: 48 | #from ..value_sources.for_configobj import ValueSource 49 | from ..value_sources import for_configobj 50 | except ImportError: 51 | # this module is optional. If it doesn't exsit, that's ok, we'll just 52 | # igrore the tests 53 | pass 54 | else: 55 | 56 | def stringIO_context_wrapper(a_stringIO_instance): 57 | @contextlib.contextmanager 58 | def stringIS_context_manager(): 59 | yield a_stringIO_instance 60 | return stringIS_context_manager 61 | 62 | class TestCase(unittest.TestCase): 63 | def _some_namespaces(self): 64 | """set up some namespaces""" 65 | n = config_manager.Namespace(doc='top') 66 | n.add_option('aaa', '2011-05-04T15:10:00', 'the a', 67 | short_form='a', 68 | from_string_converter=dtu.datetime_from_ISO_string 69 | ) 70 | n.c = config_manager.Namespace(doc='c space') 71 | n.c.add_option('fred', 'stupid', 'husband from Flintstones') 72 | n.c.add_option('wilma', 'waspish', 'wife from Flintstones') 73 | n.d = config_manager.Namespace(doc='d space') 74 | n.d.add_option('fred', 'crabby', 'male neighbor from I Love Lucy') 75 | n.d.add_option('ethel', 'silly', 76 | 'female neighbor from I Love Lucy') 77 | n.x = config_manager.Namespace(doc='x space') 78 | n.x.add_option('size', 100, 'how big in tons', short_form='s') 79 | n.x.add_option('password', 'secret', 'the password') 80 | return n 81 | 82 | def test_for_configobj_basics(self): 83 | """test basic use of for_configobj""" 84 | tmp_filename = os.path.join(tempfile.gettempdir(), 'test.ini') 85 | open(tmp_filename, 'w').write(""" 86 | # comment 87 | name=Peter 88 | awesome= 89 | # comment 90 | [othersection] 91 | foo=bar # other comment 92 | """) 93 | 94 | try: 95 | o = for_configobj.ValueSource(tmp_filename) 96 | r = {'othersection': {'foo': 'bar'}, 97 | 'name': 'Peter', 98 | 'awesome': ''} 99 | print o.get_values(None, None) 100 | assert o.get_values(None, None) == r 101 | # in the case of this implementation of a ValueSource, 102 | # the two parameters to get_values are dummies. That may 103 | # not be true for all ValueSource implementations 104 | self.assertEqual(o.get_values(0, False), r) 105 | self.assertEqual(o.get_values(1, True), r) 106 | self.assertEqual(o.get_values(2, False), r) 107 | self.assertEqual(o.get_values(3, True), r) 108 | 109 | finally: 110 | if os.path.isfile(tmp_filename): 111 | os.remove(tmp_filename) 112 | 113 | def test_for_configobj_basics_2(self): 114 | tmp_filename = os.path.join(tempfile.gettempdir(), 'test.ini') 115 | open(tmp_filename, 'w').write(""" 116 | # comment 117 | name=Peter 118 | awesome= 119 | # comment 120 | [othersection] 121 | foo=bar # other comment 122 | """) 123 | 124 | try: 125 | o = for_configobj.ValueSource(tmp_filename) 126 | c = config_manager.ConfigurationManager([], 127 | use_admin_controls=True, 128 | #use_config_files=False, 129 | use_auto_help=False, 130 | argv_source=[]) 131 | 132 | self.assertEqual(o.get_values(c, False), 133 | {'othersection': {'foo': 'bar'}, 134 | 'name': 'Peter', 135 | 'awesome': ''}) 136 | self.assertEqual(o.get_values(c, True), 137 | {'othersection': {'foo': 'bar'}, 138 | 'name': 'Peter', 139 | 'awesome': ''}) 140 | finally: 141 | if os.path.isfile(tmp_filename): 142 | os.remove(tmp_filename) 143 | 144 | def test_write_ini(self): 145 | n = self._some_namespaces() 146 | c = config_manager.ConfigurationManager( 147 | [n], 148 | use_admin_controls=True, 149 | #use_config_files=False, 150 | use_auto_help=False, 151 | argv_source=[] 152 | ) 153 | expected = """aaa = 2011-05-04T15:10:00 154 | [x] 155 | password = secret 156 | size = 100 157 | [c] 158 | wilma = waspish 159 | fred = stupid 160 | [d] 161 | ethel = silly 162 | fred = crabby 163 | """ 164 | out = StringIO() 165 | c.write_conf(for_configobj, opener=stringIO_context_wrapper(out)) 166 | received = out.getvalue() 167 | out.close() 168 | self.assertEqual(expected.strip(), received.strip()) 169 | -------------------------------------------------------------------------------- /configman/tests/test_val_for_getopt.py: -------------------------------------------------------------------------------- 1 | # ***** BEGIN LICENSE BLOCK ***** 2 | # Version: MPL 1.1/GPL 2.0/LGPL 2.1 3 | # 4 | # The contents of this file are subject to the Mozilla Public License Version 5 | # 1.1 (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # http://www.mozilla.org/MPL/ 8 | # 9 | # Software distributed under the License is distributed on an "AS IS" basis, 10 | # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | # for the specific language governing rights and limitations under the 12 | # License. 13 | # 14 | # The Original Code is configman 15 | # 16 | # The Initial Developer of the Original Code is 17 | # Mozilla Foundation 18 | # Portions created by the Initial Developer are Copyright (C) 2011 19 | # the Initial Developer. All Rights Reserved. 20 | # 21 | # Contributor(s): 22 | # K Lars Lohn, lars@mozilla.com 23 | # Peter Bengtsson, peterbe@mozilla.com 24 | # 25 | # Alternatively, the contents of this file may be used under the terms of 26 | # either the GNU General Public License Version 2 or later (the "GPL"), or 27 | # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 28 | # in which case the provisions of the GPL or the LGPL are applicable instead 29 | # of those above. If you wish to allow use of your version of this file only 30 | # under the terms of either the GPL or the LGPL, and not to allow others to 31 | # use your version of this file under the terms of the MPL, indicate your 32 | # decision by deleting the provisions above and replace them with the notice 33 | # and other provisions required by the GPL or the LGPL. If you do not delete 34 | # the provisions above, a recipient may use your version of this file under 35 | # the terms of any one of the MPL, the GPL or the LGPL. 36 | # 37 | # ***** END LICENSE BLOCK ***** 38 | 39 | import unittest 40 | import getopt 41 | 42 | import configman.config_manager as config_manager 43 | from configman.config_exceptions import NotAnOptionError 44 | from ..value_sources.for_getopt import ValueSource 45 | 46 | 47 | class TestCase(unittest.TestCase): 48 | 49 | def test_for_getopt_basics(self): 50 | source = ['a', 'b', 'c'] 51 | o = ValueSource(source) 52 | self.assertEqual(o.argv_source, source) 53 | 54 | def test_for_getopt_get_values(self): 55 | c = config_manager.ConfigurationManager( 56 | use_admin_controls=True, 57 | #use_config_files=False, 58 | use_auto_help=False, 59 | argv_source=[] 60 | ) 61 | 62 | source = ['--limit', '10'] 63 | o = ValueSource(source) 64 | self.assertEqual(o.get_values(c, True), {}) 65 | self.assertRaises(NotAnOptionError, 66 | o.get_values, c, False) 67 | 68 | c.option_definitions.add_option('limit', default=0) 69 | self.assertEqual(o.get_values(c, False), {'limit': '10'}) 70 | self.assertEqual(o.get_values(c, True), {'limit': '10'}) 71 | 72 | def test_for_getopt_with_ignore(self): 73 | function = ValueSource.getopt_with_ignore 74 | args = ['a', 'b', 'c'] 75 | o, a = function(args, '', []) 76 | self.assertEqual(o, []) 77 | self.assertEqual(a, args) 78 | args = ['-a', '14', '--fred', 'sally', 'ethel', 'dwight'] 79 | o, a = function(args, '', []) 80 | self.assertEqual([], o) 81 | self.assertEqual(a, args) 82 | args = ['-a', '14', '--fred', 'sally', 'ethel', 'dwight'] 83 | o, a = function(args, 'a:', []) 84 | self.assertEqual(o, [('-a', '14')]) 85 | self.assertEqual(a, ['--fred', 'sally', 'ethel', 'dwight']) 86 | args = ['-a', '14', '--fred', 'sally', 'ethel', 'dwight'] 87 | o, a = function(args, 'a', ['fred=']) 88 | self.assertEqual(o, [('-a', ''), ('--fred', 'sally')]) 89 | self.assertEqual(a, ['14', 'ethel', 'dwight']) 90 | 91 | def test_overlay_config_5(self): 92 | """test namespace definition w/getopt""" 93 | n = config_manager.Namespace() 94 | n.add_option('a', 1, doc='the a') 95 | n.b = 17 96 | n.add_option('c', False, doc='the c') 97 | c = config_manager.ConfigurationManager([n], [['--a', '2', '--c']], 98 | use_admin_controls=True, 99 | use_auto_help=False, 100 | argv_source=[]) 101 | self.assertEqual(c.option_definitions.a, n.a) 102 | self.assertTrue(isinstance(c.option_definitions.b, 103 | config_manager.Option)) 104 | self.assertEqual(c.option_definitions.a.value, 2) 105 | self.assertEqual(c.option_definitions.b.value, 17) 106 | self.assertEqual(c.option_definitions.b.default, 17) 107 | self.assertEqual(c.option_definitions.b.name, 'b') 108 | self.assertEqual(c.option_definitions.c.name, 'c') 109 | self.assertEqual(c.option_definitions.c.value, True) 110 | 111 | def test_overlay_config_6(self): 112 | """test namespace definition w/getopt""" 113 | n = config_manager.Namespace() 114 | n.add_option('a', doc='the a', default=1) 115 | n.b = 17 116 | n.c = config_manager.Namespace() 117 | n.c.add_option('extra', doc='the x', default=3.14159, short_form='e') 118 | c = config_manager.ConfigurationManager([n], 119 | [['--a', '2', '--c.extra', 120 | '11.0']], 121 | use_admin_controls=True, 122 | use_auto_help=False) 123 | self.assertEqual(c.option_definitions.a, n.a) 124 | self.assertEqual(type(c.option_definitions.b), config_manager.Option) 125 | self.assertEqual(c.option_definitions.a.value, 2) 126 | self.assertEqual(c.option_definitions.b.value, 17) 127 | self.assertEqual(c.option_definitions.b.default, 17) 128 | self.assertEqual(c.option_definitions.b.name, 'b') 129 | self.assertEqual(c.option_definitions.c.extra.name, 'extra') 130 | self.assertEqual(c.option_definitions.c.extra.doc, 'the x') 131 | self.assertEqual(c.option_definitions.c.extra.default, 3.14159) 132 | self.assertEqual(c.option_definitions.c.extra.value, 11.0) 133 | 134 | def test_overlay_config_6a(self): 135 | """test namespace w/getopt w/short form""" 136 | n = config_manager.Namespace() 137 | n.add_option('a', 1, doc='the a') 138 | n.b = 17 139 | n.c = config_manager.Namespace() 140 | n.c.add_option('extra', 3.14159, 'the x', short_form='e') 141 | c = config_manager.ConfigurationManager([n], [getopt], 142 | use_admin_controls=True, 143 | argv_source=['--a', '2', '-e', '11.0'], 144 | use_auto_help=False) 145 | self.assertEqual(c.option_definitions.a, n.a) 146 | self.assertEqual(type(c.option_definitions.b), config_manager.Option) 147 | self.assertEqual(c.option_definitions.a.value, 2) 148 | self.assertEqual(c.option_definitions.b.value, 17) 149 | self.assertEqual(c.option_definitions.b.default, 17) 150 | self.assertEqual(c.option_definitions.b.name, 'b') 151 | self.assertEqual(c.option_definitions.c.extra.name, 'extra') 152 | self.assertEqual(c.option_definitions.c.extra.doc, 'the x') 153 | self.assertEqual(c.option_definitions.c.extra.default, 3.14159) 154 | self.assertEqual(c.option_definitions.c.extra.value, 11.0) 155 | -------------------------------------------------------------------------------- /configman/value_sources/__init__.py: -------------------------------------------------------------------------------- 1 | # ***** BEGIN LICENSE BLOCK ***** 2 | # Version: MPL 1.1/GPL 2.0/LGPL 2.1 3 | # 4 | # The contents of this file are subject to the Mozilla Public License Version 5 | # 1.1 (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # http://www.mozilla.org/MPL/ 8 | # 9 | # Software distributed under the License is distributed on an "AS IS" basis, 10 | # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | # for the specific language governing rights and limitations under the 12 | # License. 13 | # 14 | # The Original Code is configman 15 | # 16 | # The Initial Developer of the Original Code is 17 | # Mozilla Foundation 18 | # Portions created by the Initial Developer are Copyright (C) 2011 19 | # the Initial Developer. All Rights Reserved. 20 | # 21 | # Contributor(s): 22 | # K Lars Lohn, lars@mozilla.com 23 | # Peter Bengtsson, peterbe@mozilla.com 24 | # 25 | # Alternatively, the contents of this file may be used under the terms of 26 | # either the GNU General Public License Version 2 or later (the "GPL"), or 27 | # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 28 | # in which case the provisions of the GPL or the LGPL are applicable instead 29 | # of those above. If you wish to allow use of your version of this file only 30 | # under the terms of either the GPL or the LGPL, and not to allow others to 31 | # use your version of this file under the terms of the MPL, indicate your 32 | # decision by deleting the provisions above and replace them with the notice 33 | # and other provisions required by the GPL or the LGPL. If you do not delete 34 | # the provisions above, a recipient may use your version of this file under 35 | # the terms of any one of the MPL, the GPL or the LGPL. 36 | # 37 | # ***** END LICENSE BLOCK ***** 38 | 39 | import collections 40 | import inspect 41 | import sys 42 | 43 | from source_exceptions import (NoHandlerForType, ModuleHandlesNothingException, 44 | AllHandlersFailedException, 45 | UnknownFileExtensionException, 46 | ValueException) 47 | 48 | from ..config_file_future_proxy import ConfigFileFutureProxy 49 | 50 | # replace with dynamic discovery and loading 51 | #import for_argparse 52 | #import for_xml 53 | import for_getopt 54 | import for_json 55 | import for_conf 56 | import for_mapping 57 | import for_configparse 58 | 59 | # please replace with dynamic discovery 60 | for_handlers = [for_mapping, 61 | for_getopt, 62 | for_json, 63 | for_conf, 64 | for_configparse, 65 | ] 66 | try: 67 | import for_configobj 68 | for_handlers.append(for_configobj) 69 | except ImportError: 70 | # the module configobj is not present 71 | pass 72 | 73 | 74 | # create a dispatch table of types/objects to modules. Each type should have 75 | # a list of modules that can handle that type. 76 | class DispatchByType(collections.defaultdict): 77 | def get_handlers(self, candidate): 78 | handlers_set = set() 79 | for key, handler_list in self.iteritems(): 80 | if (self._is_instance_of(candidate, key) or (candidate is key) or 81 | (inspect.ismodule(key) and candidate is key)): 82 | handlers_set.update(handler_list) 83 | if not handlers_set: 84 | raise NoHandlerForType("no hander for %s is available" % 85 | candidate) 86 | return handlers_set 87 | 88 | @staticmethod 89 | def _is_instance_of(candidate, some_type): 90 | try: 91 | return isinstance(candidate, some_type) 92 | except TypeError: 93 | return False 94 | 95 | 96 | type_handler_dispatch = DispatchByType(list) 97 | for a_handler in for_handlers: 98 | try: 99 | for a_type_supported in a_handler.can_handle: 100 | try: 101 | type_handler_dispatch[a_type_supported].append(a_handler) 102 | except TypeError: 103 | # likely this is an instance of a handleable type that is not 104 | # hashable. Replace it with its base type and try to continue. 105 | type_handler_dispatch[type(a_type_supported)].append(a_handler) 106 | except AttributeError: 107 | # this module has no can_handle attribute, therefore cannot really 108 | # be a handler and an error should be raised 109 | raise ModuleHandlesNothingException( 110 | "%s has no 'can_handle' attribute" 111 | % str(a_handler)) 112 | 113 | file_extension_dispatch = {} 114 | for a_handler in for_handlers: 115 | try: 116 | file_extension_dispatch[a_handler.file_name_extension] = \ 117 | a_handler.ValueSource.write 118 | except AttributeError: 119 | # this handler doesn't have a 'file_name_extension' or ValueSource 120 | # therefore it is not eligibe for the write file dispatcher 121 | pass 122 | 123 | 124 | def wrap(value_source_list, a_config_manager): 125 | wrapped_sources = [] 126 | for a_source in value_source_list: 127 | if a_source is ConfigFileFutureProxy: 128 | a_source = a_config_manager._get_option('admin.conf').value 129 | handlers = type_handler_dispatch.get_handlers(a_source) 130 | wrapped_source = None 131 | error_history = [] 132 | for a_handler in handlers: 133 | try: 134 | #print "the source:", a_source 135 | #print "the handler:", a_handler 136 | wrapped_source = a_handler.ValueSource(a_source, 137 | a_config_manager) 138 | break 139 | except ValueException, x: 140 | # a failure is not necessarily fatal, we need to try all of 141 | # the handlers. It's only fatal when they've all failed 142 | error_history.append(str(x)) 143 | if wrapped_source is None: 144 | errors = '; '.join(error_history) 145 | raise AllHandlersFailedException(errors) 146 | wrapped_sources.append(wrapped_source) 147 | return wrapped_sources 148 | 149 | 150 | def write(config_file_type, 151 | option_iterator, 152 | output_stream=sys.stdout): 153 | if isinstance(config_file_type, basestring): 154 | try: 155 | file_extension_dispatch[config_file_type](option_iterator, 156 | output_stream) 157 | except KeyError: 158 | raise UnknownFileExtensionException("%s isn't a registered file" 159 | " name extension" % 160 | config_file_type) 161 | else: 162 | # this is the case where we've not gotten a file extension, but a 163 | # for_handler module. Use the module's ValueSource's write method 164 | config_file_type.ValueSource.write(option_iterator, output_stream) 165 | 166 | 167 | def get_admin_options_from_command_line(config_manager): 168 | command_line_value_source = for_getopt.ValueSource(for_getopt.getopt, 169 | config_manager) 170 | values = command_line_value_source.get_values(config_manager, 171 | ignore_mismatches=True) 172 | return dict([(key, val) for key, val in values.iteritems() 173 | if key.startswith('admin.')]) 174 | -------------------------------------------------------------------------------- /configman/dotdict.py: -------------------------------------------------------------------------------- 1 | # ***** BEGIN LICENSE BLOCK ***** 2 | # Version: MPL 1.1/GPL 2.0/LGPL 2.1 3 | # 4 | # The contents of this file are subject to the Mozilla Public License Version 5 | # 1.1 (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # http://www.mozilla.org/MPL/ 8 | # 9 | # Software distributed under the License is distributed on an "AS IS" basis, 10 | # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | # for the specific language governing rights and limitations under the 12 | # License. 13 | # 14 | # The Original Code is configman 15 | # 16 | # The Initial Developer of the Original Code is 17 | # Mozilla Foundation 18 | # Portions created by the Initial Developer are Copyright (C) 2011 19 | # the Initial Developer. All Rights Reserved. 20 | # 21 | # Contributor(s): 22 | # K Lars Lohn, lars@mozilla.com 23 | # Peter Bengtsson, peterbe@mozilla.com 24 | # 25 | # Alternatively, the contents of this file may be used under the terms of 26 | # either the GNU General Public License Version 2 or later (the "GPL"), or 27 | # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 28 | # in which case the provisions of the GPL or the LGPL are applicable instead 29 | # of those above. If you wish to allow use of your version of this file only 30 | # under the terms of either the GPL or the LGPL, and not to allow others to 31 | # use your version of this file under the terms of the MPL, indicate your 32 | # decision by deleting the provisions above and replace them with the notice 33 | # and other provisions required by the GPL or the LGPL. If you do not delete 34 | # the provisions above, a recipient may use your version of this file under 35 | # the terms of any one of the MPL, the GPL or the LGPL. 36 | # 37 | # ***** END LICENSE BLOCK ***** 38 | 39 | 40 | import collections 41 | import weakref 42 | 43 | 44 | 45 | class DotDict(collections.MutableMapping): 46 | """This class is a mapping that stores its items within the __dict__ 47 | of a regular class. This means that items can be access with either 48 | the regular square bracket notation or dot notation of class instance 49 | attributes: 50 | 51 | d = DotDict() 52 | d['a'] = 23 53 | assert d['a'] == 23 54 | assert d.a == 23 55 | d.b = 17 56 | assert d['b'] == 17 57 | assert d.b == 17 58 | 59 | Because it is a Mapping and key lookup for mappings requires the raising of 60 | a KeyError when a Key is missing, KeyError is used when an AttributeError 61 | might normally be expected: 62 | 63 | d = DotDict() 64 | try: 65 | d.a 66 | except KeyError: 67 | print 'yep, we got a KeyError' 68 | except AttributeError: 69 | print 'nope, this will never happen' 70 | """ 71 | 72 | def __init__(self, initializer=None): 73 | """the constructor allows for initialization from another mapping. 74 | 75 | parameters: 76 | initializer - a mapping of keys and values to be added to this 77 | mapping.""" 78 | if isinstance(initializer, collections.Mapping): 79 | self.__dict__.update(initializer) 80 | elif initializer is not None: 81 | raise TypeError('can only initialize with a Mapping') 82 | 83 | def __setattr__(self, key, value): 84 | """this function saves keys into the mapping's __dict__.""" 85 | self.__dict__[key] = value 86 | 87 | def __getattr__(self, key): 88 | """this function is called when the key wasn't found in self.__dict__. 89 | all that is left to do is raise the KeyError.""" 90 | # the copy.deepcopy function will try to probe this class for an 91 | # instance of __deepcopy__. If an AttributeError is raised, then 92 | # copy.deepcopy goes on with out it. However, this class raises 93 | # a KeyError instead and copy.deepcopy can't handle it. So we 94 | # make sure that any missing attribute that begins with '__' 95 | # raises an AttributeError instead of KeyError. 96 | if key.startswith('__'): 97 | raise AttributeError(key) 98 | raise KeyError(key) 99 | 100 | def __getitem__(self, key): 101 | """define the square bracket operator to refer to the object's __dict__ 102 | for fetching values.""" 103 | return getattr(self, key) 104 | 105 | def __setitem__(self, key, value): 106 | """define the square bracket operator to refer to the object's __dict__ 107 | for setting values.""" 108 | setattr(self, key, value) 109 | 110 | def __delitem__(self, key): 111 | """define the square bracket operator to refer to the object's __dict__ 112 | for deleting values.""" 113 | del self.__dict__[key] 114 | 115 | def __iter__(self): 116 | """redirect the default iterator to iterate over the object's __dict__ 117 | making sure that it ignores the special '_' keys. We want those items 118 | ignored or we risk infinite recursion, not with this function, but 119 | with the clients of this class deep within configman""" 120 | return (k for k in self.__dict__ 121 | if not k.startswith('_')) 122 | 123 | def __len__(self): 124 | """makes the len function also ignore the '_' keys""" 125 | return len([x for x in self.__iter__()]) 126 | 127 | 128 | class DotDictWithAcquisition(DotDict): 129 | """This mapping, a derivative of DotDict, has special semantics when 130 | nested with mappings of the same type. 131 | 132 | d = DotDict() 133 | d.a = 23 134 | d.dd = DotDict() 135 | assert d.dd.a == 23 136 | 137 | Nested instances of DotDict, when faced with a key not within the local 138 | mapping, will defer to the parent DotDict to find a key. Only if the key 139 | is not found in the root of the nested mappings will the KeyError be 140 | raised. This is similar to the "acquisition" system found in Zope. 141 | 142 | d = DotDict() 143 | d.dd = DotDict() 144 | try: 145 | d.dd.x 146 | except KeyError: 147 | print "'x' was not found in d.dd or in d" 148 | """ 149 | 150 | def __setattr__(self, key, value): 151 | """this function saves keys into the mapping's __dict__. If the 152 | item being added is another instance of DotDict, it makes a weakref 153 | proxy object of itself and assigns it to '_parent' in the incoming 154 | DotDict.""" 155 | if isinstance(value, DotDict) and key != '_parent': 156 | value._parent = weakref.proxy(self) 157 | self.__dict__[key] = value 158 | 159 | def __getattr__(self, key): 160 | """if a key is not found in the __dict__ using the regular python 161 | attribute reference algorithm, this function will try to get it from 162 | parent class.""" 163 | if key == '_parent': 164 | raise AttributeError('_parent') 165 | try: 166 | return getattr(self._parent, key) 167 | except AttributeError: # no parent attribute 168 | # the copy.deepcopy function will try to probe this class for an 169 | # instance of __deepcopy__. If an AttributeError is raised, then 170 | # copy.deepcopy goes on with out it. However, this class raises 171 | # a KeyError instead and copy.deepcopy can't handle it. So we 172 | # make sure that any missing attribute that begins with '__' 173 | # raises an AttributeError instead of KeyError. 174 | if key.startswith('__'): 175 | raise 176 | raise KeyError(key) 177 | -------------------------------------------------------------------------------- /configman/tests/test_val_for_conf.py: -------------------------------------------------------------------------------- 1 | # ***** BEGIN LICENSE BLOCK ***** 2 | # Version: MPL 1.1/GPL 2.0/LGPL 2.1 3 | # 4 | # The contents of this file are subject to the Mozilla Public License Version 5 | # 1.1 (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # http://www.mozilla.org/MPL/ 8 | # 9 | # Software distributed under the License is distributed on an "AS IS" basis, 10 | # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | # for the specific language governing rights and limitations under the 12 | # License. 13 | # 14 | # The Original Code is configman 15 | # 16 | # The Initial Developer of the Original Code is 17 | # Mozilla Foundation 18 | # Portions created by the Initial Developer are Copyright (C) 2011 19 | # the Initial Developer. All Rights Reserved. 20 | # 21 | # Contributor(s): 22 | # K Lars Lohn, lars@mozilla.com 23 | # Peter Bengtsson, peterbe@mozilla.com 24 | # 25 | # Alternatively, the contents of this file may be used under the terms of 26 | # either the GNU General Public License Version 2 or later (the "GPL"), or 27 | # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 28 | # in which case the provisions of the GPL or the LGPL are applicable instead 29 | # of those above. If you wish to allow use of your version of this file only 30 | # under the terms of either the GPL or the LGPL, and not to allow others to 31 | # use your version of this file under the terms of the MPL, indicate your 32 | # decision by deleting the provisions above and replace them with the notice 33 | # and other provisions required by the GPL or the LGPL. If you do not delete 34 | # the provisions above, a recipient may use your version of this file under 35 | # the terms of any one of the MPL, the GPL or the LGPL. 36 | # 37 | # ***** END LICENSE BLOCK ***** 38 | 39 | import unittest 40 | import os 41 | import tempfile 42 | import contextlib 43 | from cStringIO import StringIO 44 | 45 | from ..value_sources import for_conf 46 | from ..option import Option 47 | from configman import Namespace, ConfigurationManager 48 | import configman.datetime_util as dtu 49 | 50 | def stringIO_context_wrapper(a_stringIO_instance): 51 | @contextlib.contextmanager 52 | def stringIS_context_manager(): 53 | yield a_stringIO_instance 54 | return stringIS_context_manager 55 | 56 | 57 | class TestCase(unittest.TestCase): 58 | 59 | def _some_namespaces(self): 60 | """set up some namespaces""" 61 | n = Namespace(doc='top') 62 | n.add_option('aaa', '2011-05-04T15:10:00', 'the a', 63 | short_form='a', 64 | from_string_converter=dtu.datetime_from_ISO_string 65 | ) 66 | n.c = Namespace(doc='c space') 67 | n.c.add_option('fred', 'stupid', 'husband from Flintstones') 68 | n.c.add_option('wilma', 'waspish', 'wife from Flintstones') 69 | n.c.e = Namespace(doc='e space') 70 | n.c.e.add_option('dwight', 71 | default=97, 72 | doc='my uncle') 73 | n.c.add_option('dwight', 74 | default=98, 75 | doc='your uncle') 76 | n.d = Namespace(doc='d space') 77 | n.d.add_option('fred', 'crabby', 'male neighbor from I Love Lucy') 78 | n.d.add_option('ethel', 'silly', 79 | 'female neighbor from I Love Lucy') 80 | n.x = Namespace(doc='x space') 81 | n.x.add_option('size', 100, 'how big in tons', short_form='s') 82 | n.x.add_option('password', 'secret', 'the password') 83 | return n 84 | 85 | def test_for_conf_basics(self): 86 | tmp_filename = os.path.join(tempfile.gettempdir(), 'test.conf') 87 | with open(tmp_filename, 'w') as f: 88 | f.write('# comment\n') 89 | f.write('limit=20\n') 90 | f.write('\n') 91 | try: 92 | o = for_conf.ValueSource(tmp_filename) 93 | assert o.values == {'limit': '20'}, o.values 94 | # in the case of this implementation of a ValueSource, 95 | # the two parameters to get_values are dummies. That may 96 | # not be true for all ValueSource implementations 97 | self.assertEqual(o.get_values(1, False), {'limit': '20'}) 98 | self.assertEqual(o.get_values(2, True), {'limit': '20'}) 99 | finally: 100 | if os.path.isfile(tmp_filename): 101 | os.remove(tmp_filename) 102 | 103 | def test_for_conf_nested_namespaces(self): 104 | n = self._some_namespaces() 105 | cm = ConfigurationManager(n, 106 | values_source_list=[], 107 | ) 108 | out = StringIO() 109 | cm.write_conf(for_conf, opener=stringIO_context_wrapper(out)) 110 | received = out.getvalue() 111 | out.close() 112 | expected = """# name: aaa 113 | # doc: the a 114 | # converter: configman.datetime_util.datetime_from_ISO_string 115 | aaa=2011-05-04T15:10:00 116 | 117 | #------------------------------------------------------------------------------- 118 | # c - c space 119 | 120 | # name: c.dwight 121 | # doc: your uncle 122 | # converter: int 123 | c.dwight=98 124 | 125 | # name: c.fred 126 | # doc: husband from Flintstones 127 | # converter: str 128 | c.fred=stupid 129 | 130 | # name: c.wilma 131 | # doc: wife from Flintstones 132 | # converter: str 133 | c.wilma=waspish 134 | 135 | #------------------------------------------------------------------------------- 136 | # e - e space 137 | 138 | # name: c.e.dwight 139 | # doc: my uncle 140 | # converter: int 141 | c.e.dwight=97 142 | 143 | #------------------------------------------------------------------------------- 144 | # d - d space 145 | 146 | # name: d.ethel 147 | # doc: female neighbor from I Love Lucy 148 | # converter: str 149 | d.ethel=silly 150 | 151 | # name: d.fred 152 | # doc: male neighbor from I Love Lucy 153 | # converter: str 154 | d.fred=crabby 155 | 156 | #------------------------------------------------------------------------------- 157 | # x - x space 158 | 159 | # name: x.password 160 | # doc: the password 161 | # converter: str 162 | x.password=secret 163 | 164 | # name: x.size 165 | # doc: how big in tons 166 | # converter: int 167 | x.size=100""" 168 | self.assertEqual(received.strip(), expected) 169 | 170 | strio = StringIO(expected) 171 | n.c.dwight.default = 3823 172 | n.c.e.dwight = 'fred' 173 | cm2 = ConfigurationManager(n, 174 | [stringIO_context_wrapper(strio)], 175 | use_admin_controls=False, 176 | use_auto_help=False) 177 | result = cm2.get_config() 178 | self.assertEqual(len(result), 4) 179 | self.assertEqual(sorted(result.keys()), ['aaa', 'c', 'd', 'x']) 180 | self.assertEqual(len(result.c), 4) 181 | self.assertEqual(sorted(result.c.keys()), ['dwight', 182 | 'e', 183 | 'fred', 184 | 'wilma' 185 | ]) 186 | self.assertEqual(result.c.dwight, 98) 187 | self.assertEqual(len(result.c.e), 1) 188 | self.assertEqual(result.c.e.dwight, '97') 189 | 190 | 191 | def test_write_flat_1(self): 192 | def iter_source(): 193 | yield 'x', 'x', Option('x', default=13, doc='the x') 194 | yield 'y', 'y', Option('y', default=-1, doc='the y') 195 | yield 'z', 's', Option('z', default='fred', doc='the z') 196 | out = StringIO() 197 | for_conf.ValueSource.write(iter_source, out) 198 | result = out.getvalue() 199 | expected = ("# name: x\n" 200 | "# doc: the x\n" 201 | "# converter: int\n" 202 | "x=13\n\n" 203 | "# name: y\n" 204 | "# doc: the y\n" 205 | "# converter: int\n" 206 | "y=-1\n\n" 207 | "# name: z\n" 208 | "# doc: the z\n" 209 | "# converter: str\n" 210 | "z=fred\n\n" 211 | ) 212 | self.assertEqual(expected, result, 213 | "exepected %s\nbut got\n%s" % (expected, result)) 214 | #config.write_conf(output_stream=out) 215 | -------------------------------------------------------------------------------- /configman/tests/test_val_for_configparse.py: -------------------------------------------------------------------------------- 1 | # ***** BEGIN LICENSE BLOCK ***** 2 | # Version: MPL 1.1/GPL 2.0/LGPL 2.1 3 | # 4 | # The contents of this file are subject to the Mozilla Public License Version 5 | # 1.1 (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # http://www.mozilla.org/MPL/ 8 | # 9 | # Software distributed under the License is distributed on an "AS IS" basis, 10 | # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | # for the specific language governing rights and limitations under the 12 | # License. 13 | # 14 | # The Original Code is configman 15 | # 16 | # The Initial Developer of the Original Code is 17 | # Mozilla Foundation 18 | # Portions created by the Initial Developer are Copyright (C) 2011 19 | # the Initial Developer. All Rights Reserved. 20 | # 21 | # Contributor(s): 22 | # K Lars Lohn, lars@mozilla.com 23 | # Peter Bengtsson, peterbe@mozilla.com 24 | # 25 | # Alternatively, the contents of this file may be used under the terms of 26 | # either the GNU General Public License Version 2 or later (the "GPL"), or 27 | # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 28 | # in which case the provisions of the GPL or the LGPL are applicable instead 29 | # of those above. If you wish to allow use of your version of this file only 30 | # under the terms of either the GPL or the LGPL, and not to allow others to 31 | # use your version of this file under the terms of the MPL, indicate your 32 | # decision by deleting the provisions above and replace them with the notice 33 | # and other provisions required by the GPL or the LGPL. If you do not delete 34 | # the provisions above, a recipient may use your version of this file under 35 | # the terms of any one of the MPL, the GPL or the LGPL. 36 | # 37 | # ***** END LICENSE BLOCK ***** 38 | 39 | import unittest 40 | import os 41 | import tempfile 42 | from cStringIO import StringIO 43 | import contextlib 44 | import ConfigParser 45 | 46 | import configman.datetime_util as dtu 47 | import configman.config_manager as config_manager 48 | 49 | from ..value_sources import for_configparse 50 | from ..value_sources.for_configparse import ValueSource 51 | 52 | 53 | def stringIO_context_wrapper(a_stringIO_instance): 54 | @contextlib.contextmanager 55 | def stringIS_context_manager(): 56 | yield a_stringIO_instance 57 | return stringIS_context_manager 58 | 59 | 60 | class TestCase(unittest.TestCase): 61 | def _some_namespaces(self): 62 | """set up some namespaces""" 63 | n = config_manager.Namespace(doc='top') 64 | n.add_option('aaa', '2011-05-04T15:10:00', 'the a', 65 | short_form='a', 66 | from_string_converter=dtu.datetime_from_ISO_string 67 | ) 68 | n.c = config_manager.Namespace(doc='c space') 69 | n.c.add_option('fred', 'stupid', 'husband from Flintstones') 70 | n.c.add_option('wilma', 'waspish', 'wife from Flintstones') 71 | n.d = config_manager.Namespace(doc='d space') 72 | n.d.add_option('fred', 'crabby', 'male neighbor from I Love Lucy') 73 | n.d.add_option('ethel', 'silly', 'female neighbor from I Love Lucy') 74 | n.x = config_manager.Namespace(doc='x space') 75 | n.x.add_option('size', 100, 'how big in tons', short_form='s') 76 | n.x.add_option('password', 'secret', 'the password') 77 | return n 78 | 79 | def test_for_configparse_basics(self): 80 | """test basic use of for_configparse""" 81 | tmp_filename = os.path.join( 82 | tempfile.gettempdir(), 83 | 'test.%s' % for_configparse.file_name_extension 84 | ) 85 | open(tmp_filename, 'w').write(""" 86 | ; comment 87 | [top_level] 88 | name=Peter 89 | awesome: 90 | ; comment 91 | [othersection] 92 | foo=bar ; other comment 93 | """) 94 | 95 | try: 96 | o = ValueSource(tmp_filename) 97 | r = {'othersection.foo': 'bar', 98 | 'name': 'Peter', 99 | 'awesome': ''} 100 | assert o.get_values(None, None) == r 101 | # in the case of this implementation of a ValueSource, 102 | # the two parameters to get_values are dummies. That may 103 | # not be true for all ValueSource implementations 104 | self.assertEqual(o.get_values(0, False), r) 105 | self.assertEqual(o.get_values(1, True), r) 106 | self.assertEqual(o.get_values(2, False), r) 107 | self.assertEqual(o.get_values(3, True), r) 108 | 109 | # XXX (peterbe): commented out because I'm not sure if 110 | # OptionsByIniFile get_values() should depend on the configuration 111 | # manager it is given as first argument or not. 112 | #c = config_manager.ConfigurationManager([], 113 | #use_admin_controls=True, 114 | ##use_config_files=False, 115 | #auto_help=False, 116 | #argv_source=[]) 117 | #self.assertEqual(o.get_values(c, True), {}) 118 | #self.assertRaises(config_manager.NotAnOptionError, 119 | # o.get_values, c, False) 120 | 121 | #c.option_definitions.add_option('limit', default=0) 122 | #self.assertEqual(o.get_values(c, False), {'limit': '20'}) 123 | #self.assertEqual(o.get_values(c, True), {'limit': '20'}) 124 | finally: 125 | if os.path.isfile(tmp_filename): 126 | os.remove(tmp_filename) 127 | 128 | def test_for_configparse_basics_2(self): 129 | tmp_filename = os.path.join( 130 | tempfile.gettempdir(), 131 | 'test.%s' % for_configparse.file_name_extension 132 | ) 133 | open(tmp_filename, 'w').write(""" 134 | ; comment 135 | [top_level] 136 | name=Peter 137 | awesome: 138 | ; comment 139 | [othersection] 140 | foo=bar ; other comment 141 | """) 142 | 143 | try: 144 | o = ValueSource(tmp_filename) 145 | c = config_manager.ConfigurationManager([], 146 | use_admin_controls=True, 147 | #use_config_files=False, 148 | use_auto_help=False, 149 | argv_source=[]) 150 | 151 | self.assertEqual(o.get_values(c, False), 152 | {'othersection.foo': 'bar', 153 | 'name': 'Peter', 154 | 'awesome': ''}) 155 | self.assertEqual(o.get_values(c, True), 156 | {'othersection.foo': 'bar', 157 | 'name': 'Peter', 158 | 'awesome': ''}) 159 | finally: 160 | if os.path.isfile(tmp_filename): 161 | os.remove(tmp_filename) 162 | 163 | def test_write_ini(self): 164 | n = self._some_namespaces() 165 | c = config_manager.ConfigurationManager( 166 | [n], 167 | [ConfigParser], 168 | use_admin_controls=False, 169 | #use_config_files=False, 170 | use_auto_help=False, 171 | argv_source=[] 172 | ) 173 | out = StringIO() 174 | c.write_conf(for_configparse, 175 | opener=stringIO_context_wrapper(out)) 176 | received = out.getvalue() 177 | out.close() 178 | expected = """[top_level] 179 | # name: aaa 180 | # doc: the a 181 | # converter: configman.datetime_util.datetime_from_ISO_string 182 | aaa=2011-05-04T15:10:00 183 | 184 | [c] 185 | # c space 186 | 187 | # name: c.fred 188 | # doc: husband from Flintstones 189 | # converter: str 190 | fred=stupid 191 | 192 | # name: c.wilma 193 | # doc: wife from Flintstones 194 | # converter: str 195 | wilma=waspish 196 | 197 | [d] 198 | # d space 199 | 200 | # name: d.ethel 201 | # doc: female neighbor from I Love Lucy 202 | # converter: str 203 | ethel=silly 204 | 205 | # name: d.fred 206 | # doc: male neighbor from I Love Lucy 207 | # converter: str 208 | fred=crabby 209 | 210 | [x] 211 | # x space 212 | 213 | # name: x.password 214 | # doc: the password 215 | # converter: str 216 | password=secret 217 | 218 | # name: x.size 219 | # doc: how big in tons 220 | # converter: int 221 | size=100 222 | """ 223 | self.assertEqual(expected.strip(), received.strip()) 224 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # configman documentation build configuration file, created by 4 | # sphinx-quickstart on Thu Nov 17 09:40:50 2011. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import sys, os 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | #sys.path.insert(0, os.path.abspath('.')) 20 | 21 | # -- General configuration ----------------------------------------------------- 22 | 23 | # If your documentation needs a minimal Sphinx version, state it here. 24 | #needs_sphinx = '1.0' 25 | 26 | # Add any Sphinx extension module names here, as strings. They can be extensions 27 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 28 | extensions = ['sphinx.ext.todo'] 29 | 30 | # Add any paths that contain templates here, relative to this directory. 31 | templates_path = ['_templates'] 32 | 33 | # The suffix of source filenames. 34 | source_suffix = '.rst' 35 | 36 | # The encoding of source files. 37 | #source_encoding = 'utf-8-sig' 38 | 39 | # The master toctree document. 40 | master_doc = 'index' 41 | 42 | # General information about the project. 43 | project = u'configman' 44 | copyright = u'2011, K Lars Lohn, Peter Bengtsson' 45 | 46 | # The version info for the project you're documenting, acts as replacement for 47 | # |version| and |release|, also used in various other places throughout the 48 | # built documents. 49 | # 50 | # The short X.Y version. 51 | version = '1.0' 52 | # The full version, including alpha/beta/rc tags. 53 | release = '1.0' 54 | 55 | # The language for content autogenerated by Sphinx. Refer to documentation 56 | # for a list of supported languages. 57 | #language = None 58 | 59 | # There are two options for replacing |today|: either, you set today to some 60 | # non-false value, then it is used: 61 | #today = '' 62 | # Else, today_fmt is used as the format for a strftime call. 63 | #today_fmt = '%B %d, %Y' 64 | 65 | # List of patterns, relative to source directory, that match files and 66 | # directories to ignore when looking for source files. 67 | exclude_patterns = ['_build'] 68 | 69 | # The reST default role (used for this markup: `text`) to use for all documents. 70 | #default_role = None 71 | 72 | # If true, '()' will be appended to :func: etc. cross-reference text. 73 | #add_function_parentheses = True 74 | 75 | # If true, the current module name will be prepended to all description 76 | # unit titles (such as .. function::). 77 | #add_module_names = True 78 | 79 | # If true, sectionauthor and moduleauthor directives will be shown in the 80 | # output. They are ignored by default. 81 | #show_authors = False 82 | 83 | # The name of the Pygments (syntax highlighting) style to use. 84 | pygments_style = 'sphinx' 85 | 86 | # A list of ignored prefixes for module index sorting. 87 | #modindex_common_prefix = [] 88 | 89 | 90 | # -- Options for HTML output --------------------------------------------------- 91 | 92 | # The theme to use for HTML and HTML Help pages. See the documentation for 93 | # a list of builtin themes. 94 | html_theme = 'default' 95 | 96 | # Theme options are theme-specific and customize the look and feel of a theme 97 | # further. For a list of options available for each theme, see the 98 | # documentation. 99 | #html_theme_options = {} 100 | 101 | # Add any paths that contain custom themes here, relative to this directory. 102 | #html_theme_path = [] 103 | 104 | # The name for this set of Sphinx documents. If None, it defaults to 105 | # " v documentation". 106 | #html_title = None 107 | 108 | # A shorter title for the navigation bar. Default is the same as html_title. 109 | #html_short_title = None 110 | 111 | # The name of an image file (relative to this directory) to place at the top 112 | # of the sidebar. 113 | #html_logo = None 114 | 115 | # The name of an image file (within the static path) to use as favicon of the 116 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 117 | # pixels large. 118 | #html_favicon = None 119 | 120 | # Add any paths that contain custom static files (such as style sheets) here, 121 | # relative to this directory. They are copied after the builtin static files, 122 | # so a file named "default.css" will overwrite the builtin "default.css". 123 | html_static_path = ['_static'] 124 | 125 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 126 | # using the given strftime format. 127 | #html_last_updated_fmt = '%b %d, %Y' 128 | 129 | # If true, SmartyPants will be used to convert quotes and dashes to 130 | # typographically correct entities. 131 | #html_use_smartypants = True 132 | 133 | # Custom sidebar templates, maps document names to template names. 134 | #html_sidebars = {} 135 | 136 | # Additional templates that should be rendered to pages, maps page names to 137 | # template names. 138 | #html_additional_pages = {} 139 | 140 | # If false, no module index is generated. 141 | #html_domain_indices = True 142 | 143 | # If false, no index is generated. 144 | #html_use_index = True 145 | 146 | # If true, the index is split into individual pages for each letter. 147 | #html_split_index = False 148 | 149 | # If true, links to the reST sources are added to the pages. 150 | #html_show_sourcelink = True 151 | 152 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 153 | #html_show_sphinx = True 154 | 155 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 156 | #html_show_copyright = True 157 | 158 | # If true, an OpenSearch description file will be output, and all pages will 159 | # contain a tag referring to it. The value of this option must be the 160 | # base URL from which the finished HTML is served. 161 | #html_use_opensearch = '' 162 | 163 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 164 | #html_file_suffix = None 165 | 166 | # Output file base name for HTML help builder. 167 | htmlhelp_basename = 'configmandoc' 168 | 169 | 170 | # -- Options for LaTeX output -------------------------------------------------- 171 | 172 | latex_elements = { 173 | # The paper size ('letterpaper' or 'a4paper'). 174 | #'papersize': 'letterpaper', 175 | 176 | # The font size ('10pt', '11pt' or '12pt'). 177 | #'pointsize': '10pt', 178 | 179 | # Additional stuff for the LaTeX preamble. 180 | #'preamble': '', 181 | } 182 | 183 | # Grouping the document tree into LaTeX files. List of tuples 184 | # (source start file, target name, title, author, documentclass [howto/manual]). 185 | latex_documents = [ 186 | ('index', 'configman.tex', u'configman Documentation', 187 | u'K Lars Lohn, Peter Bengtsson', 'manual'), 188 | ] 189 | 190 | # The name of an image file (relative to this directory) to place at the top of 191 | # the title page. 192 | #latex_logo = None 193 | 194 | # For "manual" documents, if this is true, then toplevel headings are parts, 195 | # not chapters. 196 | #latex_use_parts = False 197 | 198 | # If true, show page references after internal links. 199 | #latex_show_pagerefs = False 200 | 201 | # If true, show URL addresses after external links. 202 | #latex_show_urls = False 203 | 204 | # Documents to append as an appendix to all manuals. 205 | #latex_appendices = [] 206 | 207 | # If false, no module index is generated. 208 | #latex_domain_indices = True 209 | 210 | 211 | # -- Options for manual page output -------------------------------------------- 212 | 213 | # One entry per manual page. List of tuples 214 | # (source start file, name, description, authors, manual section). 215 | man_pages = [ 216 | ('index', 'configman', u'configman Documentation', 217 | [u'K Lars Lohn, Peter Bengtsson'], 1) 218 | ] 219 | 220 | # If true, show URL addresses after external links. 221 | #man_show_urls = False 222 | 223 | 224 | # -- Options for Texinfo output ------------------------------------------------ 225 | 226 | # Grouping the document tree into Texinfo files. List of tuples 227 | # (source start file, target name, title, author, 228 | # dir menu entry, description, category) 229 | texinfo_documents = [ 230 | ('index', 'configman', u'configman Documentation', 231 | u'K Lars Lohn, Peter Bengtsson', 'configman', 'One line description of project.', 232 | 'Miscellaneous'), 233 | ] 234 | 235 | # Documents to append as an appendix to all manuals. 236 | #texinfo_appendices = [] 237 | 238 | # If false, no module index is generated. 239 | #texinfo_domain_indices = True 240 | 241 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 242 | #texinfo_show_urls = 'footnote' 243 | -------------------------------------------------------------------------------- /configman/tests/test_namespace.py: -------------------------------------------------------------------------------- 1 | # ***** BEGIN LICENSE BLOCK ***** 2 | # Version: MPL 1.1/GPL 2.0/LGPL 2.1 3 | # 4 | # The contents of this file are subject to the Mozilla Public License Version 5 | # 1.1 (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # http://www.mozilla.org/MPL/ 8 | # 9 | # Software distributed under the License is distributed on an "AS IS" basis, 10 | # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | # for the specific language governing rights and limitations under the 12 | # License. 13 | # 14 | # The Original Code is configman 15 | # 16 | # The Initial Developer of the Original Code is 17 | # Mozilla Foundation 18 | # Portions created by the Initial Developer are Copyright (C) 2011 19 | # the Initial Developer. All Rights Reserved. 20 | # 21 | # Contributor(s): 22 | # K Lars Lohn, lars@mozilla.com 23 | # Peter Bengtsson, peterbe@mozilla.com 24 | # 25 | # Alternatively, the contents of this file may be used under the terms of 26 | # either the GNU General Public License Version 2 or later (the "GPL"), or 27 | # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 28 | # in which case the provisions of the GPL or the LGPL are applicable instead 29 | # of those above. If you wish to allow use of your version of this file only 30 | # under the terms of either the GPL or the LGPL, and not to allow others to 31 | # use your version of this file under the terms of the MPL, indicate your 32 | # decision by deleting the provisions above and replace them with the notice 33 | # and other provisions required by the GPL or the LGPL. If you do not delete 34 | # the provisions above, a recipient may use your version of this file under 35 | # the terms of any one of the MPL, the GPL or the LGPL. 36 | # 37 | # ***** END LICENSE BLOCK ***** 38 | 39 | import unittest 40 | import datetime 41 | import functools 42 | 43 | import configman.config_manager as config_manager 44 | import configman.datetime_util as dtu 45 | 46 | 47 | class TestCase(unittest.TestCase): 48 | 49 | def test_Namespace_basics(self): 50 | namespace = config_manager.Namespace('doc string') 51 | namespace.alpha = 1 52 | my_birthday = datetime.datetime(1960, 5, 4, 15, 10) 53 | namespace.beta = my_birthday 54 | self.assertEqual(namespace.alpha.name, 'alpha') 55 | self.assertEqual(namespace.alpha.doc, None) 56 | self.assertEqual(namespace.alpha.default, 1) 57 | self.assertEqual(namespace.alpha.from_string_converter, int) 58 | self.assertEqual(namespace.alpha.value, 1) 59 | self.assertEqual(namespace.beta.name, 'beta') 60 | self.assertEqual(namespace.beta.doc, None) 61 | self.assertEqual(namespace.beta.default, my_birthday) 62 | self.assertEqual(namespace.beta.from_string_converter, 63 | dtu.datetime_from_ISO_string) 64 | self.assertEqual(namespace.beta.value, my_birthday) 65 | 66 | def test_configuration_with_namespace(self): 67 | namespace = config_manager.Namespace() 68 | namespace.add_option('a') 69 | namespace.a.default = 1 70 | namespace.a.doc = 'the a' 71 | namespace.b = 17 72 | config = config_manager.ConfigurationManager( 73 | [namespace], 74 | #use_config_files=False, 75 | argv_source=[] 76 | ) 77 | self.assertEqual(config.option_definitions.a, namespace.a) 78 | self.assertTrue(isinstance(config.option_definitions.b, 79 | config_manager.Option)) 80 | self.assertEqual(config.option_definitions.b.value, 17) 81 | self.assertEqual(config.option_definitions.b.default, 17) 82 | self.assertEqual(config.option_definitions.b.name, 'b') 83 | 84 | def test_namespace_constructor_3(self): 85 | """test json definition""" 86 | 87 | j = '{ "a": {"name": "a", "default": 1, "doc": "the a"}, "b": 17}' 88 | config = config_manager.ConfigurationManager( 89 | [j], 90 | #use_config_files=False, 91 | argv_source=[] 92 | ) 93 | self.assertTrue(isinstance(config.option_definitions.a, 94 | config_manager.Option)) 95 | self.assertEqual(config.option_definitions.a.value, 1) 96 | self.assertEqual(config.option_definitions.a.default, 1) 97 | self.assertEqual(config.option_definitions.a.name, 'a') 98 | self.assertTrue(isinstance(config.option_definitions.b, 99 | config_manager.Option)) 100 | self.assertEqual(config.option_definitions.b.value, 17) 101 | self.assertEqual(config.option_definitions.b.default, 17) 102 | self.assertEqual(config.option_definitions.b.name, 'b') 103 | 104 | def test_namespace_from_json_with_default_datetime_date(self): 105 | """fix that verifies this bug 106 | https://github.com/twobraids/configman/issues/7 107 | """ 108 | j = ( 109 | u'{"bday": {"default": "1979-12-13", "name": "bday",' 110 | u' "from_string_converter": "configman.datetime_util.date_from_ISO' 111 | u'_string", "doc": null, "value": "1979-12-13", ' 112 | u'"short_form": null}}') 113 | config = config_manager.ConfigurationManager( 114 | [j], 115 | #use_config_files=False, 116 | use_auto_help=False, 117 | use_admin_controls=True, 118 | argv_source=[] 119 | ) 120 | 121 | option = config_manager.Option( 122 | 'bday', 123 | default=datetime.date(1979, 12, 13), 124 | ) 125 | assert option.value == config.option_definitions.bday.value 126 | self.assertEqual( 127 | config.option_definitions.bday.default, 128 | option.default 129 | ) 130 | 131 | def test_walk_expanding_class_options(self): 132 | class A(config_manager.RequiredConfig): 133 | required_config = { 134 | 'a': config_manager.Option('a', 1, 'the a'), 135 | 'b': 17, 136 | } 137 | n = config_manager.Namespace() 138 | n.source = config_manager.Namespace() 139 | n.source.add_option('c', A, 'the A class') 140 | assert n.source.c.doc == 'the A class' 141 | 142 | n.dest = config_manager.Namespace() 143 | n.dest.add_option('c', A, doc='the A class') 144 | assert n.dest.c.doc == 'the A class' 145 | c = config_manager.ConfigurationManager([n], 146 | use_admin_controls=True, 147 | #use_config_files=False, 148 | use_auto_help=False, 149 | argv_source=[]) 150 | e = config_manager.Namespace() 151 | e.s = config_manager.Namespace() 152 | e.s.add_option('c', A, doc='the A class') 153 | e.s.add_option('a', 1, 'the a') 154 | e.s.add_option('b', default=17) 155 | e.d = config_manager.Namespace() 156 | e.d.add_option('c', A, doc='the A class') 157 | e.d.add_option('a', 1, 'the a') 158 | e.d.add_option('b', default=17) 159 | 160 | def namespace_test(val): 161 | self.assertEqual(type(val), config_manager.Namespace) 162 | 163 | def option_test(val, expected=None): 164 | self.assertEqual(val.name, expected.name) 165 | self.assertEqual(val.default, expected.default) 166 | self.assertEqual(val.doc, expected.doc) 167 | 168 | e = [ 169 | ('dest', 'dest', namespace_test), 170 | ('dest.a', 'a', functools.partial(option_test, expected=e.d.a)), 171 | ('dest.b', 'b', functools.partial(option_test, expected=e.d.b)), 172 | ('dest.c', 'c', functools.partial(option_test, expected=e.d.c)), 173 | ('source', 'source', namespace_test), 174 | ('source.a', 'a', functools.partial(option_test, expected=e.s.a)), 175 | ('source.b', 'b', functools.partial(option_test, expected=e.s.b)), 176 | ('source.c', 'c', functools.partial(option_test, expected=e.s.c)), 177 | ] 178 | 179 | c_contents = [(qkey, key, val) for qkey, key, val in c._walk_config()] 180 | c_contents.sort() 181 | e.sort() 182 | for c_tuple, e_tuple in zip(c_contents, e): 183 | qkey, key, val = c_tuple 184 | e_qkey, e_key, e_fn = e_tuple 185 | self.assertEqual(qkey, e_qkey) 186 | self.assertEqual(key, e_key) 187 | e_fn(val) 188 | 189 | def test_setting_nested_namespaces(self): 190 | n = config_manager.Namespace() 191 | n.namespace('sub') 192 | sub_n = n.sub 193 | sub_n.add_option('name') 194 | self.assertTrue(n.sub) 195 | self.assertTrue(isinstance(n.sub.name, config_manager.Option)) 196 | 197 | def test_editing_values_on_namespace(self): 198 | n = config_manager.Namespace() 199 | self.assertRaises(KeyError, n.set_value, 'name', 'Peter') 200 | n.add_option('name', 'Lars') 201 | n.set_value('name', 'Peter') 202 | self.assertTrue(n.name) 203 | self.assertEqual(n.name.value, 'Peter') 204 | n.namespace('user') 205 | n.user.add_option('age', 100) 206 | n.set_value('user.age', 200) 207 | self.assertTrue(n.user.age) 208 | self.assertEqual(n.user.age.value, 200) 209 | 210 | # let's not be strict once 211 | n.set_value('user.gender', u'male', strict=False) 212 | self.assertEqual(n.user.gender.value, u'male') 213 | 214 | def test_comparing_namespace_instances(self): 215 | n = config_manager.Namespace() 216 | n2 = config_manager.Namespace() 217 | self.assertEqual(n, n2) 218 | 219 | n3 = config_manager.Namespace() 220 | n3.add_option('name', 'Peter') 221 | self.assertNotEqual(n, n3) 222 | 223 | n2.add_option('name', 'Peter', 'Name of a person') 224 | self.assertNotEqual(n, n2) 225 | self.assertNotEqual(n2, n3) 226 | 227 | n4 = config_manager.Namespace() 228 | n4.add_option('name', 'Peter') 229 | self.assertEqual(n4, n3) 230 | 231 | def test_deep_copy(self): 232 | from copy import deepcopy 233 | 234 | n = config_manager.Namespace() 235 | n2 = deepcopy(n) 236 | self.assertTrue(n is not n2) 237 | 238 | n = config_manager.Namespace() 239 | n.add_option('name', 'Peter') 240 | n3 = deepcopy(n) 241 | self.assertEqual(n, n3) 242 | self.assertTrue(n.name is not n3.name) 243 | 244 | def foo(all, local, args): 245 | return 17 246 | n = config_manager.Namespace() 247 | n.add_aggregation('a', foo) 248 | n4 = deepcopy(n) 249 | self.assertTrue(n.a is not n4.a) 250 | 251 | 252 | --------------------------------------------------------------------------------