Skip to content
Browse files

added support for configobj

  • Loading branch information...
1 parent 61925d4 commit 4989477c0c8d3b96216b0f85f45fa64ba6428044 @twobraids committed Mar 26, 2012
View
5 configman/config_manager.py
@@ -510,8 +510,9 @@ def _setup_admin_options(self, values_source_list):
base_namespace.admin = admin = Namespace()
admin.add_option(name='print_conf',
default=None,
- doc='write current config to stdout '
- '(conf, ini, json)',
+ doc='write current config to stdout (%s)'
+ % ', '.join(
+ value_sources.file_extension_dispatch.keys())
)
admin.add_option(name='dump_conf',
default='',
View
33 configman/tests/test_config_manager.py
@@ -40,17 +40,20 @@
import os
import unittest
from contextlib import contextmanager
-import ConfigParser
import io
from cStringIO import StringIO
import getopt
+import ConfigParser
+
import configman.config_manager as config_manager
from configman.dotdict import DotDict
import configman.datetime_util as dtu
from configman.config_exceptions import NotAnOptionError
from configman.value_sources.source_exceptions import \
AllHandlersFailedException
+#from configobj import ConfigObj
+
class TestCase(unittest.TestCase):
@@ -517,15 +520,16 @@ def test_overlay_config_9(self):
def test_overlay_config_10(self):
"""test namespace definition ini file"""
n = config_manager.Namespace()
- n.add_option('t', 'tee', 'the t')
+ n.other = config_manager.Namespace()
+ n.other.add_option('t', 'tee', 'the t')
n.d = config_manager.Namespace()
n.d.add_option('a', 1, 'the a')
n.d.b = 17
n.c = config_manager.Namespace()
n.c.add_option('extra', 3.14159, 'the x')
n.c.add_option('string', 'fred', doc='str')
ini_data = """
-[top_level]
+[other]
t=tea
[d]
# blank line to be ignored
@@ -538,7 +542,8 @@ def test_overlay_config_10(self):
config.readfp(io.BytesIO(ini_data))
#g = config_manager.IniValueSource(config)
e = DotDict()
- e.t = 'T'
+ e.other = DotDict()
+ e.other.t = 'T'
e.d = DotDict()
e.d.a = 16
e.c = DotDict()
@@ -552,8 +557,8 @@ def test_overlay_config_10(self):
argv_source=['--c.extra', '11.0'],
#use_config_files=False,
use_auto_help=False)
- self.assertEqual(c.option_definitions.t.name, 't')
- self.assertEqual(c.option_definitions.t.value, 'tea')
+ self.assertEqual(c.option_definitions.other.t.name, 't')
+ self.assertEqual(c.option_definitions.other.t.value, 'tea')
self.assertEqual(c.option_definitions.d.a, n.d.a)
self.assertEqual(type(c.option_definitions.d.b), config_manager.Option)
self.assertEqual(c.option_definitions.d.a.value, 22)
@@ -1109,8 +1114,8 @@ def test_print_conf_some_options_excluded(self):
sys.stdout = old_stdout
printed = temp_output.getvalue()
- self.assertTrue('name: gender' in printed)
- self.assertTrue('name: salary' not in printed)
+ self.assertTrue('gender' in printed)
+ self.assertTrue('salary' not in printed)
def test_dump_conf_some_options_excluded(self):
n = config_manager.Namespace()
@@ -1131,17 +1136,17 @@ def test_dump_conf_some_options_excluded(self):
use_admin_controls=True,
use_auto_help=False,
quit_after_admin=False,
- argv_source=['--admin.dump_conf=foo.ini'],
+ argv_source=['--admin.dump_conf=foo.conf'],
config_pathname='fred'
)
- printed = open('foo.ini').read()
- self.assertTrue('name: gender' in printed)
- self.assertTrue('name: salary' not in printed)
+ printed = open('foo.conf').read()
+ self.assertTrue('gender' in printed)
+ self.assertTrue('salary' not in printed)
finally:
- if os.path.isfile('foo.ini'):
- os.remove('foo.ini')
+ if os.path.isfile('foo.conf'):
+ os.remove('foo.conf')
def test_config_pathname_set(self):
View
184 configman/tests/test_val_for_configobj.py
@@ -0,0 +1,184 @@
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is configman
+#
+# The Initial Developer of the Original Code is
+# Mozilla Foundation
+# Portions created by the Initial Developer are Copyright (C) 2011
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# K Lars Lohn, lars@mozilla.com
+# Peter Bengtsson, peterbe@mozilla.com
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+import unittest
+import os
+import tempfile
+from cStringIO import StringIO
+import contextlib
+
+import configman.datetime_util as dtu
+import configman.config_manager as config_manager
+try:
+ from ..value_sources.for_configobj import ValueSource
+except ImportError:
+ # this module is optional. If it doesn't exsit, that's ok, we'll just
+ # igrore the tests
+ pass
+else:
+
+ def stringIO_context_wrapper(a_stringIO_instance):
+ @contextlib.contextmanager
+ def stringIS_context_manager():
+ yield a_stringIO_instance
+ return stringIS_context_manager
+
+
+ class TestCase(unittest.TestCase):
+ def _some_namespaces(self):
+ """set up some namespaces"""
+ n = config_manager.Namespace(doc='top')
+ n.add_option('aaa', '2011-05-04T15:10:00', 'the a',
+ short_form='a',
+ from_string_converter=dtu.datetime_from_ISO_string
+ )
+ n.c = config_manager.Namespace(doc='c space')
+ n.c.add_option('fred', 'stupid', 'husband from Flintstones')
+ n.c.add_option('wilma', 'waspish', 'wife from Flintstones')
+ n.d = config_manager.Namespace(doc='d space')
+ n.d.add_option('fred', 'crabby', 'male neighbor from I Love Lucy')
+ n.d.add_option('ethel', 'silly', 'female neighbor from I Love Lucy')
+ n.x = config_manager.Namespace(doc='x space')
+ n.x.add_option('size', 100, 'how big in tons', short_form='s')
+ n.x.add_option('password', 'secret', 'the password')
+ return n
+
+ def test_for_configobj_basics(self):
+ """test basic use of for_configobj"""
+ tmp_filename = os.path.join(tempfile.gettempdir(), 'test.ini')
+ open(tmp_filename, 'w').write("""
+# comment
+name=Peter
+awesome=
+# comment
+[othersection]
+foo=bar # other comment
+ """)
+
+ try:
+ o = ValueSource(tmp_filename)
+ r = {'othersection': {'foo': 'bar'},
+ 'name': 'Peter',
+ 'awesome': ''}
+ print o.get_values(None, None)
+ assert o.get_values(None, None) == r
+ # in the case of this implementation of a ValueSource,
+ # the two parameters to get_values are dummies. That may
+ # not be true for all ValueSource implementations
+ self.assertEqual(o.get_values(0, False), r)
+ self.assertEqual(o.get_values(1, True), r)
+ self.assertEqual(o.get_values(2, False), r)
+ self.assertEqual(o.get_values(3, True), r)
+
+ # XXX (peterbe): commented out because I'm not sure if
+ # OptionsByIniFile get_values() should depend on the configuration
+ # manager it is given as first argument or not.
+ #c = config_manager.ConfigurationManager([],
+ #use_admin_controls=True,
+ ##use_config_files=False,
+ #auto_help=False,
+ #argv_source=[])
+ #self.assertEqual(o.get_values(c, True), {})
+ #self.assertRaises(config_manager.NotAnOptionError,
+ # o.get_values, c, False)
+
+ #c.option_definitions.add_option('limit', default=0)
+ #self.assertEqual(o.get_values(c, False), {'limit': '20'})
+ #self.assertEqual(o.get_values(c, True), {'limit': '20'})
+ finally:
+ if os.path.isfile(tmp_filename):
+ os.remove(tmp_filename)
+
+ def test_for_configobj_basics_2(self):
+ tmp_filename = os.path.join(tempfile.gettempdir(), 'test.ini')
+ open(tmp_filename, 'w').write("""
+# comment
+name=Peter
+awesome=
+# comment
+[othersection]
+foo=bar # other comment
+ """)
+
+ try:
+ o = ValueSource(tmp_filename)
+ c = config_manager.ConfigurationManager([],
+ use_admin_controls=True,
+ #use_config_files=False,
+ use_auto_help=False,
+ argv_source=[])
+
+ self.assertEqual(o.get_values(c, False),
+ {'othersection': {'foo': 'bar'},
+ 'name': 'Peter',
+ 'awesome': ''})
+ self.assertEqual(o.get_values(c, True),
+ {'othersection': {'foo': 'bar'},
+ 'name': 'Peter',
+ 'awesome': ''})
+ finally:
+ if os.path.isfile(tmp_filename):
+ os.remove(tmp_filename)
+
+ def test_write_ini(self):
+ n = self._some_namespaces()
+ c = config_manager.ConfigurationManager(
+ [n],
+ use_admin_controls=True,
+ #use_config_files=False,
+ use_auto_help=False,
+ argv_source=[]
+ )
+ expected = """aaa = 2011-05-04T15:10:00
+[x]
+ password = secret
+ size = 100
+[c]
+ wilma = waspish
+ fred = stupid
+[d]
+ ethel = silly
+ fred = crabby
+"""
+ out = StringIO()
+ c.write_conf('ini', opener=stringIO_context_wrapper(out))
+ received = out.getvalue()
+ out.close()
+ print received.strip()
+ print expected.strip()
+ self.assertEqual(expected.strip(), received.strip())
View
25 configman/tests/test_val_for_configparse.py
@@ -41,9 +41,12 @@
import tempfile
from cStringIO import StringIO
import contextlib
+import ConfigParser
import configman.datetime_util as dtu
import configman.config_manager as config_manager
+
+from ..value_sources import for_configparse
from ..value_sources.for_configparse import ValueSource
@@ -75,7 +78,10 @@ def _some_namespaces(self):
def test_for_configparse_basics(self):
"""test basic use of for_configparse"""
- tmp_filename = os.path.join(tempfile.gettempdir(), 'test.ini')
+ tmp_filename = os.path.join(
+ tempfile.gettempdir(),
+ 'test.%s' % for_configparse.file_name_extension
+ )
open(tmp_filename, 'w').write("""
; comment
[top_level]
@@ -120,7 +126,10 @@ def test_for_configparse_basics(self):
os.remove(tmp_filename)
def test_for_configparse_basics_2(self):
- tmp_filename = os.path.join(tempfile.gettempdir(), 'test.ini')
+ tmp_filename = os.path.join(
+ tempfile.gettempdir(),
+ 'test.%s' % for_configparse.file_name_extension
+ )
open(tmp_filename, 'w').write("""
; comment
[top_level]
@@ -155,11 +164,17 @@ def test_write_ini(self):
n = self._some_namespaces()
c = config_manager.ConfigurationManager(
[n],
- use_admin_controls=True,
+ [ConfigParser],
+ use_admin_controls=False,
#use_config_files=False,
use_auto_help=False,
argv_source=[]
)
+ out = StringIO()
+ c.write_conf(for_configparse.file_name_extension,
+ opener=stringIO_context_wrapper(out))
+ received = out.getvalue()
+ out.close()
expected = """[top_level]
# name: aaa
# doc: the a
@@ -205,8 +220,4 @@ def test_write_ini(self):
# converter: int
size=100
"""
- out = StringIO()
- c.write_conf('ini', opener=stringIO_context_wrapper(out))
- received = out.getvalue()
- out.close()
self.assertEqual(expected.strip(), received.strip())
View
13 configman/value_sources/__init__.py
@@ -52,18 +52,23 @@
#import for_xml
import for_getopt
import for_json
-import for_configparse
import for_conf
import for_mapping
+import for_configparse
# please replace with dynamic discovery
for_handlers = [for_mapping,
for_getopt,
for_json,
- for_configparse,
- for_conf
+ for_conf,
+ for_configparse
]
-
+try:
+ import for_configobj
+ for_handlers.append(for_configobj)
+except ImportError:
+ # the module configobj is not loaded, ignore the error
+ pass
# create a dispatch table of types/objects to modules. Each type should have
# a list of modules that can handle that type.
View
4 configman/value_sources/for_conf.py
@@ -83,7 +83,7 @@ def __init__(self, candidate, the_config_manager=None):
# will return a Context Manager Type.
opener = candidate
else:
- raise CantHandleTypeException("don't know how to handle"
+ raise CantHandleTypeException("Conf doesn't know how to handle"
" %s." % str(candidate))
self.values = {}
try:
@@ -105,7 +105,7 @@ def __init__(self, candidate, the_config_manager=None):
except ValueError:
self.values[line] = ''
except Exception, x:
- raise NotAConfigFileError("couldn't interpret %s as a context "
+ raise NotAConfigFileError("Conf couldn't interpret %s as a config "
"file: %s" % (candidate, str(x)))
def get_values(self, config_manager, ignore_mismatches):
View
130 configman/value_sources/for_configobj.py
@@ -0,0 +1,130 @@
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is configman
+#
+# The Initial Developer of the Original Code is
+# Mozilla Foundation
+# Portions created by the Initial Developer are Copyright (C) 2011
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# K Lars Lohn, lars@mozilla.com
+# Peter Bengtsson, peterbe@mozilla.com
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+import sys
+import collections
+
+import configobj
+from configobj import ConfigObj
+
+from source_exceptions import (CantHandleTypeException, ValueException,
+ NotEnoughInformationException)
+from ..namespace import Namespace
+from ..option import Option
+from .. import converters as conv
+
+file_name_extension = 'ini'
+
+can_handle = (configobj,
+ ConfigObj,
+ basestring,
+ )
+
+
+class LoadingIniFileFailsException(ValueException):
+ pass
+
+
+class ValueSource(object):
+
+ def __init__(self, source,
+ config_manager=None,
+ top_level_section_name=''):
+ self.delayed_parser_instantiation = False
+ self.top_level_section_name = top_level_section_name
+ if source is ConfigObj:
+ try:
+ app = config_manager._get_option('admin.application')
+ source = "%s.%s" % (app.value.app_name, file_name_extension)
+ except AttributeError:
+ # we likely don't have the admin.application object set up yet.
+ # we need to delay the instantiation of the ConfigParser
+ # until later.
+ if source is None:
+ raise NotEnoughInformationException("Can't setup an ini "
+ "file without knowing "
+ "the file name")
+ self.delayed_parser_instantiation = True
+ return
+ if (isinstance(source, basestring) and
+ source.endswith(file_name_extension)):
+ try:
+ self.config_obj = ConfigObj(source)
+ except Exception, x:
+ raise LoadingIniFileFailsException(
+ "ConfigObj cannot load ini: %s" % str(x))
+ else:
+ raise CantHandleTypeException(
+ "ConfigObj doesn't know how to handle %s." % source)
+
+ def get_values(self, config_manager, ignore_mismatches):
+ """Return a nested dictionary representing the values in the ini file.
+ In the case of this ValueSource implementation, both parameters are
+ dummies."""
+ if self.delayed_parser_instantiation:
+ try:
+ app = config_manager._get_option('admin.application')
+ source = "%s%s" % (app.value.app_name, file_name_extension)
+ self.config_obj = ConfigObj(source)
+ self.delayed_parser_instantiation = False
+ except AttributeError:
+ # we don't have enough information to get the ini file
+ # yet. we'll ignore the error for now
+ return {}
+ return self.config_obj
+
+ @staticmethod
+ def recursive_default_dict():
+ return collections.defaultdict(ValueSource.recursive_default_dict)
+
+ @staticmethod
+ def write(option_iter, output_stream=sys.stdout):
+ # must construct a dict from the iter
+ destination_dict = ValueSource.recursive_default_dict()
+ for qkey, key, val in option_iter():
+ if isinstance(val, Namespace):
+ continue
+ d = destination_dict
+ for x in qkey.split('.')[:-1]:
+ d = d[x]
+ if isinstance(val, Option):
+ v = val.value
+ v_str = conv.to_string_converters[type(v)](v)
+ d[key] = v_str
+ config = ConfigObj(destination_dict)
+ config.write(outfile=output_stream)
View
29 configman/value_sources/for_configparse.py
@@ -40,13 +40,19 @@
import ConfigParser
from source_exceptions import (CantHandleTypeException, ValueException,
- NotEnoughInformationException)
+ NotEnoughInformationException,
+ )
+from ..config_exceptions import NotAnOptionError
+
from .. import namespace
from .. import option
from .. import converters as conv
-
-file_name_extension = 'ini'
+try:
+ import configobj
+ file_name_extension = 'inix'
+except ImportError:
+ file_name_extension = 'ini'
can_handle = (ConfigParser,
ConfigParser.RawConfigParser, # just the base class, subclasses
@@ -70,14 +76,14 @@ def __init__(self, source,
try:
app = config_manager._get_option('admin.application')
source = "%s.%s" % (app.value.app_name, file_name_extension)
- except AttributeError:
+ except (AttributeError, NotAnOptionError):
# we likely don't have the admin.application object set up yet.
# we need to delay the instantiation of the ConfigParser
# until later.
if source is None:
- raise NotEnoughInformationException("Can't setup an ini "
- "file without knowing "
- "the file name")
+ raise NotEnoughInformationException(
+ "Can't setup an %s file without knowing the file name"
+ % file_name_extension)
self.delayed_parser_instantiation = True
return
if (isinstance(source, basestring) and
@@ -88,9 +94,8 @@ def __init__(self, source,
# FIXME: this doesn't give you a clue why it fail.
# Was it because the file didn't exist (IOError) or because it
# was badly formatted??
- raise LoadingIniFileFailsException("Cannot load ini: %s"
- % str(x))
-
+ raise LoadingIniFileFailsException(
+ "ConfigParser cannot load file: %s" % str(x))
elif isinstance(source, ConfigParser.RawConfigParser):
self.configparser = source
else:
@@ -114,7 +119,7 @@ def get_values(self, config_manager, ignore_mismatches):
source = "%s%s" % (app.value.app_name, file_name_extension)
self.configparser = self._create_parser(source)
self.delayed_parser_instantiation = False
- except AttributeError:
+ except (AttributeError, NotAnOptionError):
# we don't have enough information to get the ini file
# yet. we'll ignore the error for now
return {}
@@ -134,7 +139,7 @@ def write(option_iter, output_stream=sys.stdout):
print >> output_stream, '[top_level]'
for qkey, key, val in option_iter():
if isinstance(val, namespace.Namespace):
- print >> output_stream, '[%s]' % key
+ print >> output_stream, '[%s]' % qkey
print >> output_stream, '# %s\n' % val._doc
elif isinstance(val, option.Option):
print >> output_stream, '# name:', qkey

0 comments on commit 4989477

Please sign in to comment.
Something went wrong with that request. Please try again.