# configobj_test.py # doctests for ConfigObj # A config file reader/writer that supports nested sections in config files. # Copyright (C) 2005-2006 Michael Foord, Nicola Larosa # E-mail: fuzzyman AT voidspace DOT org DOT uk # nico AT tekNico DOT net # ConfigObj 4 # http://www.voidspace.org.uk/python/configobj.html # Released subject to the BSD License # Please see http://www.voidspace.org.uk/python/license.shtml # Scripts maintained at http://www.voidspace.org.uk/python/index.shtml # For information about bugfixes, updates and support, please join the # ConfigObj mailing list: # http://lists.sourceforge.net/lists/listinfo/configobj-develop # Comments, suggestions and bug reports welcome. from __future__ import generators import os, sys INTP_VER = sys.version_info[:2] if INTP_VER < (2, 2): raise RuntimeError("Python v.2.2 or later needed") try: from codecs import BOM_UTF8 except ImportError: # Python 2.2 does not have this # UTF-8 BOM_UTF8 = '\xef\xbb\xbf' from configobj import * """ >>> z = ConfigObj() >>> z['a'] = 'a' >>> z['sect'] = { ... 'subsect': { ... 'a': 'fish', ... 'b': 'wobble', ... }, ... 'member': 'value', ... } >>> x = ConfigObj(z.write()) >>> z == x 1 """ def _error_test(): """ Testing the error classes. >>> raise ConfigObjError Traceback (most recent call last): ConfigObjError >>> raise NestingError Traceback (most recent call last): NestingError >>> raise ParseError Traceback (most recent call last): ParseError >>> raise DuplicateError Traceback (most recent call last): DuplicateError >>> raise ConfigspecError Traceback (most recent call last): ConfigspecError >>> raise InterpolationLoopError('yoda') Traceback (most recent call last): InterpolationLoopError: interpolation loop detected in value "yoda". >>> raise RepeatSectionError Traceback (most recent call last): RepeatSectionError >>> raise MissingInterpolationOption('yoda') Traceback (most recent call last): MissingInterpolationOption: missing option "yoda" in interpolation. """ def _section_test(): """ Tests from Section methods. >>> n = a.dict() >>> n == a 1 >>> n is a 0 >>> a = '''[section1] ... option1 = True ... [[subsection]] ... more_options = False ... # end of file'''.splitlines() >>> b = '''# File is user.ini ... [section1] ... option1 = False ... # end of file'''.splitlines() >>> c1 = ConfigObj(b) >>> c2 = ConfigObj(a) >>> c2.merge(c1) >>> c2 ConfigObj({'section1': {'option1': 'False', 'subsection': {'more_options': 'False'}}}) >>> config = '''[XXXXsection] ... XXXXkey = XXXXvalue'''.splitlines() >>> cfg = ConfigObj(config) >>> cfg ConfigObj({'XXXXsection': {'XXXXkey': 'XXXXvalue'}}) >>> def transform(section, key): ... val = section[key] ... newkey = key.replace('XXXX', 'CLIENT1') ... section.rename(key, newkey) ... if isinstance(val, (tuple, list, dict)): ... pass ... else: ... val = val.replace('XXXX', 'CLIENT1') ... section[newkey] = val >>> cfg.walk(transform, call_on_sections=True) {'CLIENT1section': {'CLIENT1key': None}} >>> cfg ConfigObj({'CLIENT1section': {'CLIENT1key': 'CLIENT1value'}}) """ # FIXME: test error code for badly built multiline values # FIXME: test handling of StringIO # FIXME: test interpolation with writing def _doctest(): """ Dummy function to hold some of the doctests. >>> a.depth 0 >>> a == { ... 'key2': 'val', ... 'key1': 'val', ... 'lev1c': { ... 'lev2c': { ... 'lev3c': { ... 'key1': 'val', ... }, ... }, ... }, ... 'lev1b': { ... 'key2': 'val', ... 'key1': 'val', ... 'lev2ba': { ... 'key1': 'val', ... }, ... 'lev2bb': { ... 'key1': 'val', ... }, ... }, ... 'lev1a': { ... 'key2': 'val', ... 'key1': 'val', ... }, ... } 1 >>> b.depth 0 >>> b == { ... 'key3': 'val3', ... 'key2': 'val2', ... 'key1': 'val1', ... 'section 1': { ... 'keys11': 'val1', ... 'keys13': 'val3', ... 'keys12': 'val2', ... }, ... 'section 2': { ... 'section 2 sub 1': { ... 'fish': '3', ... }, ... 'keys21': 'val1', ... 'keys22': 'val2', ... 'keys23': 'val3', ... }, ... } 1 >>> t = ''' ... 'a' = b # !"$%^&*(),::;'@~#= 33 ... "b" = b #= 6, 33 ... ''' .split('\\n') >>> t2 = ConfigObj(t) >>> assert t2 == {'a': 'b', 'b': 'b'} >>> t2.inline_comments['b'] = '' >>> del t2['a'] >>> assert t2.write() == ['','b = b', ''] # Test ``list_values=False`` stuff >>> c = ''' ... key1 = no quotes ... key2 = 'single quotes' ... key3 = "double quotes" ... key4 = "list", 'with', several, "quotes" ... ''' >>> cfg = ConfigObj(c.splitlines(), list_values=False) >>> cfg == {'key1': 'no quotes', 'key2': "'single quotes'", ... 'key3': '"double quotes"', ... 'key4': '"list", \\'with\\', several, "quotes"' ... } 1 >>> cfg = ConfigObj(list_values=False) >>> cfg['key1'] = 'Multiline\\nValue' >>> cfg['key2'] = '''"Value" with 'quotes' !''' >>> cfg.write() ["key1 = '''Multiline\\nValue'''", 'key2 = "Value" with \\'quotes\\' !'] >>> cfg.list_values = True >>> cfg.write() == ["key1 = '''Multiline\\nValue'''", ... 'key2 = \\'\\'\\'"Value" with \\'quotes\\' !\\'\\'\\''] 1 Test flatten_errors: >>> from validate import Validator, VdtValueTooSmallError >>> config = ''' ... test1=40 ... test2=hello ... test3=3 ... test4=5.0 ... [section] ... test1=40 ... test2=hello ... test3=3 ... test4=5.0 ... [[sub section]] ... test1=40 ... test2=hello ... test3=3 ... test4=5.0 ... '''.split('\\n') >>> configspec = ''' ... test1= integer(30,50) ... test2= string ... test3=integer ... test4=float(6.0) ... [section ] ... test1=integer(30,50) ... test2=string ... test3=integer ... test4=float(6.0) ... [[sub section]] ... test1=integer(30,50) ... test2=string ... test3=integer ... test4=float(6.0) ... '''.split('\\n') >>> val = Validator() >>> c1 = ConfigObj(config, configspec=configspec) >>> res = c1.validate(val) >>> flatten_errors(c1, res) == [([], 'test4', False), (['section', ... 'sub section'], 'test4', False), (['section'], 'test4', False)] True >>> res = c1.validate(val, preserve_errors=True) >>> check = flatten_errors(c1, res) >>> check[0][:2] ([], 'test4') >>> check[1][:2] (['section', 'sub section'], 'test4') >>> check[2][:2] (['section'], 'test4') >>> for entry in check: ... isinstance(entry[2], VdtValueTooSmallError) ... print str(entry[2]) True the value "5.0" is too small. True the value "5.0" is too small. True the value "5.0" is too small. Test unicode handling, BOM, write witha file like object and line endings : >>> u_base = ''' ... # initial comment ... # inital comment 2 ... ... test1 = some value ... # comment ... test2 = another value # inline comment ... # section comment ... [section] # inline comment ... test = test # another inline comment ... test2 = test2 ... ... # final comment ... # final comment2 ... ''' >>> u = u_base.encode('utf_8').splitlines(True) >>> u[0] = BOM_UTF8 + u[0] >>> uc = ConfigObj(u) >>> uc.encoding = None >>> uc.BOM == True 1 >>> uc == {'test1': 'some value', 'test2': 'another value', ... 'section': {'test': 'test', 'test2': 'test2'}} 1 >>> uc = ConfigObj(u, encoding='utf_8', default_encoding='latin-1') >>> uc.BOM 1 >>> isinstance(uc['test1'], unicode) 1 >>> uc.encoding 'utf_8' >>> uc.newlines '\\n' >>> uc['latin1'] = "This costs lot's of " >>> a_list = uc.write() >>> len(a_list) 15 >>> isinstance(a_list[0], str) 1 >>> a_list[0].startswith(BOM_UTF8) 1 >>> u = u_base.replace('\\n', '\\r\\n').encode('utf_8').splitlines(True) >>> uc = ConfigObj(u) >>> uc.newlines '\\r\\n' >>> uc.newlines = '\\r' >>> from cStringIO import StringIO >>> file_like = StringIO() >>> uc.write(file_like) >>> file_like.seek(0) >>> uc2 = ConfigObj(file_like) >>> uc2 == uc 1 >>> uc2.filename == None 1 >>> uc2.newlines == '\\r' 1 Test validate in copy mode >>> a = ''' ... # Initial Comment ... ... key1 = string(default=Hello) # comment 1 ... ... # section comment ... [section] # inline comment ... # key1 comment ... key1 = integer(default=6) # an integer value ... # key2 comment ... key2 = boolean(default=True) # a boolean ... ... # subsection comment ... [[sub-section]] # inline comment ... # another key1 comment ... key1 = float(default=3.0) # a float'''.splitlines() >>> b = ConfigObj(configspec=a) >>> b.validate(val, copy=True) 1 >>> b.write() == ['', ... '# Initial Comment', ... '', ... 'key1 = Hello # comment 1', ... '', ... '# section comment', ... '[section] # inline comment', ... ' # key1 comment', ... ' key1 = 6 # an integer value', ... ' # key2 comment', ... ' key2 = True # a boolean', ... ' ', ... ' # subsection comment', ... ' [[sub-section]] # inline comment', ... ' # another key1 comment', ... ' key1 = 3.0 # a float'] 1 Test Writing Empty Values >>> a = ''' ... key1 = ... key2 =# a comment''' >>> b = ConfigObj(a.splitlines()) >>> b.write() ['', 'key1 = ""', 'key2 = "" # a comment'] >>> b.write_empty_values = True >>> b.write() ['', 'key1 = ', 'key2 = # a comment'] Test unrepr when reading >>> a = ''' ... key1 = (1, 2, 3) # comment ... key2 = True ... key3 = 'a string' ... key4 = [1, 2, 3, 'a mixed list'] ... '''.splitlines() >>> b = ConfigObj(a, unrepr=True) >>> b == {'key1': (1, 2, 3), ... 'key2': True, ... 'key3': 'a string', ... 'key4': [1, 2, 3, 'a mixed list']} 1 Test unrepr when writing >>> c = ConfigObj(b.write(), unrepr=True) >>> c == b 1 Test unrepr with multiline values >>> a = '''k = \"""{ ... 'k1': 3, ... 'k2': 6.0}\""" ... '''.splitlines() >>> c = ConfigObj(a, unrepr=True) >>> c == {'k': {'k1': 3, 'k2': 6.0}} 1 Test unrepr with a dictionary >>> a = 'k = {"a": 1}'.splitlines() >>> c = ConfigObj(a, unrepr=True) >>> type(c['k']) == dict 1 >>> a = ConfigObj() >>> a['a'] = 'fish' >>> a.as_bool('a') Traceback (most recent call last): ValueError: Value "fish" is neither True nor False >>> a['b'] = 'True' >>> a.as_bool('b') 1 >>> a['b'] = 'off' >>> a.as_bool('b') 0 >>> a = ConfigObj() >>> a['a'] = 'fish' >>> try: ... a.as_int('a') #doctest: +ELLIPSIS ... except ValueError, e: ... err_mess = str(e) >>> err_mess.startswith('invalid literal for int()') 1 >>> a['b'] = '1' >>> a.as_int('b') 1 >>> a['b'] = '3.2' >>> try: ... a.as_int('b') #doctest: +ELLIPSIS ... except ValueError, e: ... err_mess = str(e) >>> err_mess.startswith('invalid literal for int()') 1 >>> a = ConfigObj() >>> a['a'] = 'fish' >>> a.as_float('a') Traceback (most recent call last): ValueError: invalid literal for float(): fish >>> a['b'] = '1' >>> a.as_float('b') 1.0 >>> a['b'] = '3.2' >>> a.as_float('b') 3.2000000000000002 Test # with unrepr >>> a = ''' ... key1 = (1, 2, 3) # comment ... key2 = True ... key3 = 'a string' ... key4 = [1, 2, 3, 'a mixed list#'] ... '''.splitlines() >>> b = ConfigObj(a, unrepr=True) >>> b == {'key1': (1, 2, 3), ... 'key2': True, ... 'key3': 'a string', ... 'key4': [1, 2, 3, 'a mixed list#']} 1 """ def _test_configobj(): """ Testing ConfigObj Testing with duplicate keys and sections. >>> c = ''' ... [hello] ... member = value ... [hello again] ... member = value ... [ "hello" ] ... member = value ... ''' >>> ConfigObj(c.split('\\n'), raise_errors = True) Traceback (most recent call last): DuplicateError: Duplicate section name at line 6. >>> d = ''' ... [hello] ... member = value ... [hello again] ... member1 = value ... member2 = value ... 'member1' = value ... [ "and again" ] ... member = value ... ''' >>> ConfigObj(d.split('\\n'), raise_errors = True) Traceback (most recent call last): DuplicateError: Duplicate keyword name at line 7. Testing ConfigParser-style interpolation >>> c = ConfigObj() >>> c['DEFAULT'] = { ... 'b': 'goodbye', ... 'userdir': 'c:\\\\home', ... 'c': '%(d)s', ... 'd': '%(c)s' ... } >>> c['section'] = { ... 'a': '%(datadir)s\\\\some path\\\\file.py', ... 'b': '%(userdir)s\\\\some path\\\\file.py', ... 'c': 'Yo %(a)s', ... 'd': '%(not_here)s', ... 'e': '%(e)s', ... } >>> c['section']['DEFAULT'] = { ... 'datadir': 'c:\\\\silly_test', ... 'a': 'hello - %(b)s', ... } >>> c['section']['a'] == 'c:\\\\silly_test\\\\some path\\\\file.py' 1 >>> c['section']['b'] == 'c:\\\\home\\\\some path\\\\file.py' 1 >>> c['section']['c'] == 'Yo c:\\\\silly_test\\\\some path\\\\file.py' 1 Switching Interpolation Off >>> c.interpolation = False >>> c['section']['a'] == '%(datadir)s\\\\some path\\\\file.py' 1 >>> c['section']['b'] == '%(userdir)s\\\\some path\\\\file.py' 1 >>> c['section']['c'] == 'Yo %(a)s' 1 Testing the interpolation errors. >>> c.interpolation = True >>> c['section']['d'] Traceback (most recent call last): MissingInterpolationOption: missing option "not_here" in interpolation. >>> c['section']['e'] Traceback (most recent call last): InterpolationLoopError: interpolation loop detected in value "e". Testing Template-style interpolation >>> interp_cfg = ''' ... [DEFAULT] ... keyword1 = value1 ... 'keyword 2' = 'value 2' ... reference = ${keyword1} ... foo = 123 ... ... [ section ] ... templatebare = $keyword1/foo ... bar = $$foo ... dollar = $$300.00 ... stophere = $$notinterpolated ... with_braces = ${keyword1}s (plural) ... with_spaces = ${keyword 2}!!! ... with_several = $keyword1/$reference/$keyword1 ... configparsersample = %(keyword 2)sconfig ... deep = ${reference} ... ... [[DEFAULT]] ... baz = $foo ... ... [[ sub-section ]] ... quux = '$baz + $bar + $foo' ... ... [[[ sub-sub-section ]]] ... convoluted = "$bar + $baz + $quux + $bar" ... ''' >>> c = ConfigObj(interp_cfg.split('\\n'), interpolation='Template') >>> c['section']['templatebare'] 'value1/foo' >>> c['section']['dollar'] '$300.00' >>> c['section']['stophere'] '$notinterpolated' >>> c['section']['with_braces'] 'value1s (plural)' >>> c['section']['with_spaces'] 'value 2!!!' >>> c['section']['with_several'] 'value1/value1/value1' >>> c['section']['configparsersample'] '%(keyword 2)sconfig' >>> c['section']['deep'] 'value1' >>> c['section']['sub-section']['quux'] '123 + $foo + 123' >>> c['section']['sub-section']['sub-sub-section']['convoluted'] '$foo + 123 + 123 + $foo + 123 + $foo' Testing our quoting. >>> i._quote('\"""\'\'\'') Traceback (most recent call last): SyntaxError: EOF while scanning triple-quoted string >>> try: ... i._quote('\\n', multiline=False) ... except ConfigObjError, e: ... e.msg 'Value "\\n" cannot be safely quoted.' >>> k._quote(' "\' ', multiline=False) Traceback (most recent call last): SyntaxError: EOL while scanning single-quoted string Testing with "stringify" off. >>> c.stringify = False >>> c['test'] = 1 Traceback (most recent call last): TypeError: Value is not a string "1". Testing Empty values. >>> cfg_with_empty = ''' ... k = ... k2 =# comment test ... val = test ... val2 = , ... val3 = 1, ... val4 = 1, 2 ... val5 = 1, 2, '''.splitlines() >>> cwe = ConfigObj(cfg_with_empty) >>> cwe == {'k': '', 'k2': '', 'val': 'test', 'val2': [], ... 'val3': ['1'], 'val4': ['1', '2'], 'val5': ['1', '2']} 1 >>> cwe = ConfigObj(cfg_with_empty, list_values=False) >>> cwe == {'k': '', 'k2': '', 'val': 'test', 'val2': ',', ... 'val3': '1,', 'val4': '1, 2', 'val5': '1, 2,'} 1 Testing list values. >>> testconfig3 = ''' ... a = , ... b = test, ... c = test1, test2 , test3 ... d = test1, test2, test3, ... ''' >>> d = ConfigObj(testconfig3.split('\\n'), raise_errors=True) >>> d['a'] == [] 1 >>> d['b'] == ['test'] 1 >>> d['c'] == ['test1', 'test2', 'test3'] 1 >>> d['d'] == ['test1', 'test2', 'test3'] 1 Testing with list values off. >>> e = ConfigObj( ... testconfig3.split('\\n'), ... raise_errors=True, ... list_values=False) >>> e['a'] == ',' 1 >>> e['b'] == 'test,' 1 >>> e['c'] == 'test1, test2 , test3' 1 >>> e['d'] == 'test1, test2, test3,' 1 Testing creating from a dictionary. >>> f = { ... 'key1': 'val1', ... 'key2': 'val2', ... 'section 1': { ... 'key1': 'val1', ... 'key2': 'val2', ... 'section 1b': { ... 'key1': 'val1', ... 'key2': 'val2', ... }, ... }, ... 'section 2': { ... 'key1': 'val1', ... 'key2': 'val2', ... 'section 2b': { ... 'key1': 'val1', ... 'key2': 'val2', ... }, ... }, ... 'key3': 'val3', ... } >>> g = ConfigObj(f) >>> f == g 1 Testing we correctly detect badly built list values (4 of them). >>> testconfig4 = ''' ... config = 3,4,, ... test = 3,,4 ... fish = ,, ... dummy = ,,hello, goodbye ... ''' >>> try: ... ConfigObj(testconfig4.split('\\n')) ... except ConfigObjError, e: ... len(e.errors) 4 Testing we correctly detect badly quoted values (4 of them). >>> testconfig5 = ''' ... config = "hello # comment ... test = 'goodbye ... fish = 'goodbye # comment ... dummy = "hello again ... ''' >>> try: ... ConfigObj(testconfig5.split('\\n')) ... except ConfigObjError, e: ... len(e.errors) 4 Test Multiline Comments >>> i == { ... 'name4': ' another single line value ', ... 'multi section': { ... 'name4': '\\n Well, this is a\\n multiline ' ... 'value\\n ', ... 'name2': '\\n Well, this is a\\n multiline ' ... 'value\\n ', ... 'name3': '\\n Well, this is a\\n multiline ' ... 'value\\n ', ... 'name1': '\\n Well, this is a\\n multiline ' ... 'value\\n ', ... }, ... 'name2': ' another single line value ', ... 'name3': ' a single line value ', ... 'name1': ' a single line value ', ... } 1 >>> filename = a.filename >>> a.filename = None >>> values = a.write() >>> index = 0 >>> while index < 23: ... index += 1 ... line = values[index-1] ... assert line.endswith('# comment ' + str(index)) >>> a.filename = filename >>> start_comment = ['# Initial Comment', '', '#'] >>> end_comment = ['', '#', '# Final Comment'] >>> newconfig = start_comment + testconfig1.split('\\n') + end_comment >>> nc = ConfigObj(newconfig) >>> nc.initial_comment ['# Initial Comment', '', '#'] >>> nc.final_comment ['', '#', '# Final Comment'] >>> nc.initial_comment == start_comment 1 >>> nc.final_comment == end_comment 1 Test the _handle_comment method >>> c = ConfigObj() >>> c['foo'] = 'bar' >>> c.inline_comments['foo'] = 'Nice bar' >>> c.write() ['foo = bar # Nice bar'] tekNico: FIXME: use StringIO instead of real files >>> filename = a.filename >>> a.filename = 'test.ini' >>> a.write() >>> a.filename = filename >>> a == ConfigObj('test.ini', raise_errors=True) 1 >>> os.remove('test.ini') >>> b.filename = 'test.ini' >>> b.write() >>> b == ConfigObj('test.ini', raise_errors=True) 1 >>> os.remove('test.ini') >>> i.filename = 'test.ini' >>> i.write() >>> i == ConfigObj('test.ini', raise_errors=True) 1 >>> os.remove('test.ini') >>> a = ConfigObj() >>> a['DEFAULT'] = {'a' : 'fish'} >>> a['a'] = '%(a)s' >>> a.write() ['a = %(a)s', '[DEFAULT]', 'a = fish'] Test indentation handling >>> ConfigObj({'sect': {'sect': {'foo': 'bar'}}}).write() ['[sect]', ' [[sect]]', ' foo = bar'] >>> cfg = ['[sect]', '[[sect]]', 'foo = bar'] >>> ConfigObj(cfg).write() == cfg 1 >>> cfg = ['[sect]', ' [[sect]]', ' foo = bar'] >>> ConfigObj(cfg).write() == cfg 1 >>> cfg = ['[sect]', ' [[sect]]', ' foo = bar'] >>> ConfigObj(cfg).write() == cfg 1 >>> ConfigObj(oneTabCfg).write() == oneTabCfg 1 >>> ConfigObj(twoTabsCfg).write() == twoTabsCfg 1 >>> ConfigObj(tabsAndSpacesCfg).write() == tabsAndSpacesCfg 1 >>> ConfigObj(cfg, indent_type=chr(9)).write() == oneTabCfg 1 >>> ConfigObj(oneTabCfg, indent_type=' ').write() == cfg 1 """ def _test_validate(): """ >>> try: ... from validate import Validator ... except ImportError: ... print >> sys.stderr, 'Cannot import the Validator object, skipping the related tests' ... else: ... config = ''' ... test1=40 ... test2=hello ... test3=3 ... test4=5.0 ... [section] ... test1=40 ... test2=hello ... test3=3 ... test4=5.0 ... [[sub section]] ... test1=40 ... test2=hello ... test3=3 ... test4=5.0 ... '''.split('\\n') ... configspec = ''' ... test1= integer(30,50) ... test2= string ... test3=integer ... test4=float(6.0) ... [section ] ... test1=integer(30,50) ... test2=string ... test3=integer ... test4=float(6.0) ... [[sub section]] ... test1=integer(30,50) ... test2=string ... test3=integer ... test4=float(6.0) ... '''.split('\\n') ... val = Validator() ... c1 = ConfigObj(config, configspec=configspec) ... test = c1.validate(val) ... test == { ... 'test1': True, ... 'test2': True, ... 'test3': True, ... 'test4': False, ... 'section': { ... 'test1': True, ... 'test2': True, ... 'test3': True, ... 'test4': False, ... 'sub section': { ... 'test1': True, ... 'test2': True, ... 'test3': True, ... 'test4': False, ... }, ... }, ... } 1 >>> val.check(c1.configspec['test4'], c1['test4']) Traceback (most recent call last): VdtValueTooSmallError: the value "5.0" is too small. >>> val_test_config = ''' ... key = 0 ... key2 = 1.1 ... [section] ... key = some text ... key2 = 1.1, 3.0, 17, 6.8 ... [[sub-section]] ... key = option1 ... key2 = True'''.split('\\n') >>> val_test_configspec = ''' ... key = integer ... key2 = float ... [section] ... key = string ... key2 = float_list(4) ... [[sub-section]] ... key = option(option1, option2) ... key2 = boolean'''.split('\\n') >>> val_test = ConfigObj(val_test_config, configspec=val_test_configspec) >>> val_test.validate(val) 1 >>> val_test['key'] = 'text not a digit' >>> val_res = val_test.validate(val) >>> val_res == {'key2': True, 'section': True, 'key': False} 1 >>> configspec = ''' ... test1=integer(30,50, default=40) ... test2=string(default="hello") ... test3=integer(default=3) ... test4=float(6.0, default=6.0) ... [section ] ... test1=integer(30,50, default=40) ... test2=string(default="hello") ... test3=integer(default=3) ... test4=float(6.0, default=6.0) ... [[sub section]] ... test1=integer(30,50, default=40) ... test2=string(default="hello") ... test3=integer(default=3) ... test4=float(6.0, default=6.0) ... '''.split('\\n') >>> default_test = ConfigObj(['test1=30'], configspec=configspec) >>> default_test ConfigObj({'test1': '30', 'section': {'sub section': {}}}) >>> default_test.validate(val) 1 >>> default_test == { ... 'test1': 30, ... 'test2': 'hello', ... 'test3': 3, ... 'test4': 6.0, ... 'section': { ... 'test1': 40, ... 'test2': 'hello', ... 'test3': 3, ... 'test4': 6.0, ... 'sub section': { ... 'test1': 40, ... 'test3': 3, ... 'test2': 'hello', ... 'test4': 6.0, ... }, ... }, ... } 1 Now testing with repeated sections : BIG TEST >>> repeated_1 = ''' ... [dogs] ... [[__many__]] # spec for a dog ... fleas = boolean(default=True) ... tail = option(long, short, default=long) ... name = string(default=rover) ... [[[__many__]]] # spec for a puppy ... name = string(default="son of rover") ... age = float(default=0.0) ... [cats] ... [[__many__]] # spec for a cat ... fleas = boolean(default=True) ... tail = option(long, short, default=short) ... name = string(default=pussy) ... [[[__many__]]] # spec for a kitten ... name = string(default="son of pussy") ... age = float(default=0.0) ... '''.split('\\n') >>> repeated_2 = ''' ... [dogs] ... ... # blank dogs with puppies ... # should be filled in by the configspec ... [[dog1]] ... [[[puppy1]]] ... [[[puppy2]]] ... [[[puppy3]]] ... [[dog2]] ... [[[puppy1]]] ... [[[puppy2]]] ... [[[puppy3]]] ... [[dog3]] ... [[[puppy1]]] ... [[[puppy2]]] ... [[[puppy3]]] ... [cats] ... ... # blank cats with kittens ... # should be filled in by the configspec ... [[cat1]] ... [[[kitten1]]] ... [[[kitten2]]] ... [[[kitten3]]] ... [[cat2]] ... [[[kitten1]]] ... [[[kitten2]]] ... [[[kitten3]]] ... [[cat3]] ... [[[kitten1]]] ... [[[kitten2]]] ... [[[kitten3]]] ... '''.split('\\n') >>> repeated_3 = ''' ... [dogs] ... ... [[dog1]] ... [[dog2]] ... [[dog3]] ... [cats] ... ... [[cat1]] ... [[cat2]] ... [[cat3]] ... '''.split('\\n') >>> repeated_4 = ''' ... [__many__] ... ... name = string(default=Michael) ... age = float(default=0.0) ... sex = option(m, f, default=m) ... '''.split('\\n') >>> repeated_5 = ''' ... [cats] ... [[__many__]] ... fleas = boolean(default=True) ... tail = option(long, short, default=short) ... name = string(default=pussy) ... [[[description]]] ... height = float(default=3.3) ... weight = float(default=6) ... [[[[coat]]]] ... fur = option(black, grey, brown, "tortoise shell", default=black) ... condition = integer(0,10, default=5) ... '''.split('\\n') >>> from validate import Validator >>> val= Validator() >>> repeater = ConfigObj(repeated_2, configspec=repeated_1) >>> repeater.validate(val) 1 >>> repeater == { ... 'dogs': { ... 'dog1': { ... 'fleas': True, ... 'tail': 'long', ... 'name': 'rover', ... 'puppy1': {'name': 'son of rover', 'age': 0.0}, ... 'puppy2': {'name': 'son of rover', 'age': 0.0}, ... 'puppy3': {'name': 'son of rover', 'age': 0.0}, ... }, ... 'dog2': { ... 'fleas': True, ... 'tail': 'long', ... 'name': 'rover', ... 'puppy1': {'name': 'son of rover', 'age': 0.0}, ... 'puppy2': {'name': 'son of rover', 'age': 0.0}, ... 'puppy3': {'name': 'son of rover', 'age': 0.0}, ... }, ... 'dog3': { ... 'fleas': True, ... 'tail': 'long', ... 'name': 'rover', ... 'puppy1': {'name': 'son of rover', 'age': 0.0}, ... 'puppy2': {'name': 'son of rover', 'age': 0.0}, ... 'puppy3': {'name': 'son of rover', 'age': 0.0}, ... }, ... }, ... 'cats': { ... 'cat1': { ... 'fleas': True, ... 'tail': 'short', ... 'name': 'pussy', ... 'kitten1': {'name': 'son of pussy', 'age': 0.0}, ... 'kitten2': {'name': 'son of pussy', 'age': 0.0}, ... 'kitten3': {'name': 'son of pussy', 'age': 0.0}, ... }, ... 'cat2': { ... 'fleas': True, ... 'tail': 'short', ... 'name': 'pussy', ... 'kitten1': {'name': 'son of pussy', 'age': 0.0}, ... 'kitten2': {'name': 'son of pussy', 'age': 0.0}, ... 'kitten3': {'name': 'son of pussy', 'age': 0.0}, ... }, ... 'cat3': { ... 'fleas': True, ... 'tail': 'short', ... 'name': 'pussy', ... 'kitten1': {'name': 'son of pussy', 'age': 0.0}, ... 'kitten2': {'name': 'son of pussy', 'age': 0.0}, ... 'kitten3': {'name': 'son of pussy', 'age': 0.0}, ... }, ... }, ... } 1 >>> repeater = ConfigObj(repeated_3, configspec=repeated_1) >>> repeater.validate(val) 1 >>> repeater == { ... 'cats': { ... 'cat1': {'fleas': True, 'tail': 'short', 'name': 'pussy'}, ... 'cat2': {'fleas': True, 'tail': 'short', 'name': 'pussy'}, ... 'cat3': {'fleas': True, 'tail': 'short', 'name': 'pussy'}, ... }, ... 'dogs': { ... 'dog1': {'fleas': True, 'tail': 'long', 'name': 'rover'}, ... 'dog2': {'fleas': True, 'tail': 'long', 'name': 'rover'}, ... 'dog3': {'fleas': True, 'tail': 'long', 'name': 'rover'}, ... }, ... } 1 >>> repeater = ConfigObj(configspec=repeated_4) >>> repeater['Michael'] = {} >>> repeater.validate(val) 1 >>> repeater == { ... 'Michael': {'age': 0.0, 'name': 'Michael', 'sex': 'm'}, ... } 1 >>> repeater = ConfigObj(repeated_3, configspec=repeated_5) >>> repeater == { ... 'dogs': {'dog1': {}, 'dog2': {}, 'dog3': {}}, ... 'cats': {'cat1': {}, 'cat2': {}, 'cat3': {}}, ... } 1 >>> repeater.validate(val) 1 >>> repeater == { ... 'dogs': {'dog1': {}, 'dog2': {}, 'dog3': {}}, ... 'cats': { ... 'cat1': { ... 'fleas': True, ... 'tail': 'short', ... 'name': 'pussy', ... 'description': { ... 'weight': 6.0, ... 'height': 3.2999999999999998, ... 'coat': {'fur': 'black', 'condition': 5}, ... }, ... }, ... 'cat2': { ... 'fleas': True, ... 'tail': 'short', ... 'name': 'pussy', ... 'description': { ... 'weight': 6.0, ... 'height': 3.2999999999999998, ... 'coat': {'fur': 'black', 'condition': 5}, ... }, ... }, ... 'cat3': { ... 'fleas': True, ... 'tail': 'short', ... 'name': 'pussy', ... 'description': { ... 'weight': 6.0, ... 'height': 3.2999999999999998, ... 'coat': {'fur': 'black', 'condition': 5}, ... }, ... }, ... }, ... } 1 Test that interpolation is preserved for validated string values. Also check that interpolation works in configspecs. >>> t = ConfigObj() >>> t['DEFAULT'] = {} >>> t['DEFAULT']['def_test'] = 'a' >>> t['test'] = '%(def_test)s' >>> t['test'] 'a' >>> v = Validator() >>> t.configspec = {'test': 'string'} >>> t.validate(v) 1 >>> t.interpolation = False >>> t ConfigObj({'test': '%(def_test)s', 'DEFAULT': {'def_test': 'a'}}) >>> specs = [ ... 'interpolated string = string(default="fuzzy-%(man)s")', ... '[DEFAULT]', ... 'man = wuzzy', ... ] >>> c = ConfigObj(configspec=specs) >>> c.validate(v) 1 >>> c['interpolated string'] 'fuzzy-wuzzy' Test SimpleVal >>> val = SimpleVal() >>> config = ''' ... test1=40 ... test2=hello ... test3=3 ... test4=5.0 ... [section] ... test1=40 ... test2=hello ... test3=3 ... test4=5.0 ... [[sub section]] ... test1=40 ... test2=hello ... test3=3 ... test4=5.0 ... '''.split('\\n') >>> configspec = ''' ... test1='' ... test2='' ... test3='' ... test4='' ... [section] ... test1='' ... test2='' ... test3='' ... test4='' ... [[sub section]] ... test1='' ... test2='' ... test3='' ... test4='' ... '''.split('\\n') >>> o = ConfigObj(config, configspec=configspec) >>> o.validate(val) 1 >>> o = ConfigObj(configspec=configspec) >>> o.validate(val) 0 Test Flatten Errors >>> import validate >>> vtor = validate.Validator() >>> my_ini = ''' ... option1 = True ... [section1] ... option1 = True ... [section2] ... another_option = Probably ... [section3] ... another_option = True ... [[section3b]] ... value = 3 ... value2 = a ... value3 = 11 ... ''' >>> my_cfg = ''' ... option1 = boolean() ... option2 = boolean() ... option3 = boolean(default=Bad_value) ... [section1] ... option1 = boolean() ... option2 = boolean() ... option3 = boolean(default=Bad_value) ... [section2] ... another_option = boolean() ... [section3] ... another_option = boolean() ... [[section3b]] ... value = integer ... value2 = integer ... value3 = integer(0, 10) ... [[[section3b-sub]]] ... value = string ... [section4] ... another_option = boolean() ... ''' >>> cs = my_cfg.split('\\n') >>> ini = my_ini.split('\\n') >>> cfg = ConfigObj(ini, configspec=cs) >>> res = cfg.validate(vtor, preserve_errors=True) >>> errors = [] >>> for entry in flatten_errors(cfg, res): ... section_list, key, error = entry ... section_list.insert(0, '[root]') ... if key is not None: ... section_list.append(key) ... else: ... section_list.append('[missing]') ... section_string = ', '.join(section_list) ... errors.append((section_string, ' = ', error)) >>> errors.sort() >>> for entry in errors: ... print entry[0], entry[1], (entry[2] or 0) [root], option2 = 0 [root], option3 = the value "Bad_value" is of the wrong type. [root], section1, option2 = 0 [root], section1, option3 = the value "Bad_value" is of the wrong type. [root], section2, another_option = the value "Probably" is of the wrong type. [root], section3, section3b, section3b-sub, [missing] = 0 [root], section3, section3b, value2 = the value "a" is of the wrong type. [root], section3, section3b, value3 = the value "11" is too big. [root], section4, [missing] = 0 """ def _test_errors(): """ Test the error messages and objects, in normal mode and unrepr mode. >>> bad_syntax = ''' ... key = "value" ... key2 = "value ... '''.splitlines() >>> c = ConfigObj(bad_syntax) Traceback (most recent call last): ParseError: Parse error in value at line 3. >>> c = ConfigObj(bad_syntax, raise_errors=True) Traceback (most recent call last): ParseError: Parse error in value at line 3. >>> c = ConfigObj(bad_syntax, raise_errors=True, unrepr=True) Traceback (most recent call last): UnreprError: Parse error in value at line 3. >>> try: ... c = ConfigObj(bad_syntax) ... except Exception, e: ... pass >>> assert(isinstance(e, ConfigObjError)) >>> print e Parse error in value at line 3. >>> len(e.errors) == 1 1 >>> try: ... c = ConfigObj(bad_syntax, unrepr=True) ... except Exception, e: ... pass >>> assert(isinstance(e, ConfigObjError)) >>> print e Parse error in value at line 3. >>> len(e.errors) == 1 1 >>> the_error = e.errors[0] >>> assert(isinstance(the_error, UnreprError)) >>> multiple_bad_syntax = ''' ... key = "value" ... key2 = "value ... key3 = "value2 ... '''.splitlines() >>> try: ... c = ConfigObj(multiple_bad_syntax) ... except ConfigObjError, e: ... str(e) 'Parsing failed with several errors.\\nFirst error at line 3.' >>> c = ConfigObj(multiple_bad_syntax, raise_errors=True) Traceback (most recent call last): ParseError: Parse error in value at line 3. >>> c = ConfigObj(multiple_bad_syntax, raise_errors=True, unrepr=True) Traceback (most recent call last): UnreprError: Parse error in value at line 3. >>> try: ... c = ConfigObj(multiple_bad_syntax) ... except Exception, e: ... pass >>> assert(isinstance(e, ConfigObjError)) >>> print e Parsing failed with several errors. First error at line 3. >>> len(e.errors) == 2 1 >>> try: ... c = ConfigObj(multiple_bad_syntax, unrepr=True) ... except Exception, e: ... pass >>> assert(isinstance(e, ConfigObjError)) >>> print e Parsing failed with several errors. First error at line 3. >>> len(e.errors) == 2 1 >>> the_error = e.errors[1] >>> assert(isinstance(the_error, UnreprError)) >>> unknown_name = ''' ... key = "value" ... key2 = value ... '''.splitlines() >>> c = ConfigObj(unknown_name) >>> c = ConfigObj(unknown_name, unrepr=True) Traceback (most recent call last): UnreprError: Unknown name or type in value at line 3. >>> c = ConfigObj(unknown_name, raise_errors=True, unrepr=True) Traceback (most recent call last): UnreprError: Unknown name or type in value at line 3. """ def _test_unrepr_comments(): """ >>> config = ''' ... # initial comments ... # with two lines ... key = "value" ... # section comment ... [section] # inline section comment ... # key comment ... key = "value" ... # final comment ... # with two lines ... '''.splitlines() >>> c = ConfigObj(config, unrepr=True) >>> c == { 'key': 'value', ... 'section': { 'key': 'value'}} 1 >>> c.initial_comment == ['', '# initial comments', '# with two lines'] 1 >>> c.comments == {'section': ['# section comment'], 'key': []} 1 >>> c.inline_comments == {'section': '# inline section comment', 'key': ''} 1 >>> c['section'].comments == { 'key': ['# key comment']} 1 >>> c.final_comment == ['# final comment', '# with two lines'] 1 """ if __name__ == '__main__': # run the code tests in doctest format # testconfig1 = """\ key1= val # comment 1 key2= val # comment 2 # comment 3 [lev1a] # comment 4 key1= val # comment 5 key2= val # comment 6 # comment 7 [lev1b] # comment 8 key1= val # comment 9 key2= val # comment 10 # comment 11 [[lev2ba]] # comment 12 key1= val # comment 13 # comment 14 [[lev2bb]] # comment 15 key1= val # comment 16 # comment 17 [lev1c] # comment 18 # comment 19 [[lev2c]] # comment 20 # comment 21 [[[lev3c]]] # comment 22 key1 = val # comment 23""" # testconfig2 = """\ key1 = 'val1' key2 = "val2" key3 = val3 ["section 1"] # comment keys11 = val1 keys12 = val2 keys13 = val3 [section 2] keys21 = val1 keys22 = val2 keys23 = val3 [['section 2 sub 1']] fish = 3 """ # testconfig6 = ''' name1 = """ a single line value """ # comment name2 = \''' another single line value \''' # comment name3 = """ a single line value """ name4 = \''' another single line value \''' [ "multi section" ] name1 = """ Well, this is a multiline value """ name2 = \''' Well, this is a multiline value \''' name3 = """ Well, this is a multiline value """ # a comment name4 = \''' Well, this is a multiline value \''' # I guess this is a comment too ''' # # these cannot be put among the doctests, because the doctest module # does a string.expandtabs() on all of them, sigh oneTabCfg = ['[sect]', '\t[[sect]]', '\t\tfoo = bar'] twoTabsCfg = ['[sect]', '\t\t[[sect]]', '\t\t\t\tfoo = bar'] tabsAndSpacesCfg = ['[sect]', '\t \t [[sect]]', '\t \t \t \t foo = bar'] # import doctest m = sys.modules.get('__main__') globs = m.__dict__.copy() a = ConfigObj(testconfig1.split('\n'), raise_errors=True) b = ConfigObj(testconfig2.split('\n'), raise_errors=True) i = ConfigObj(testconfig6.split('\n'), raise_errors=True) globs.update({'INTP_VER': INTP_VER, 'a': a, 'b': b, 'i': i, 'oneTabCfg': oneTabCfg, 'twoTabsCfg': twoTabsCfg, 'tabsAndSpacesCfg': tabsAndSpacesCfg}) doctest.testmod(m, globs=globs)