2
# doctests for ConfigObj
3
# A config file reader/writer that supports nested sections in config files.
4
# Copyright (C) 2005-2010 Michael Foord, Nicola Larosa
5
# E-mail: fuzzyman AT voidspace DOT org DOT uk
6
# nico AT tekNico DOT net
9
# http://www.voidspace.org.uk/python/configobj.html
11
# Released subject to the BSD License
12
# Please see http://www.voidspace.org.uk/python/license.shtml
14
# Scripts maintained at http://www.voidspace.org.uk/python/index.shtml
15
# For information about bugfixes, updates and support, please join the
16
# ConfigObj mailing list:
17
# http://lists.sourceforge.net/lists/listinfo/configobj-develop
18
# Comments, suggestions and bug reports welcome.
21
from __future__ import generators
22
from StringIO import StringIO
26
INTP_VER = sys.version_info[:2]
28
raise RuntimeError("Python v.2.2 or later needed")
31
from codecs import BOM_UTF8
33
# Python 2.2 does not have this
35
BOM_UTF8 = '\xef\xbb\xbf'
37
from configobj import *
38
from validate import Validator, VdtValueTooSmallError
49
... 'member': 'value',
51
>>> x = ConfigObj(z.write())
59
Testing the error classes.
61
>>> raise ConfigObjError
62
Traceback (most recent call last):
65
>>> raise NestingError
66
Traceback (most recent call last):
70
Traceback (most recent call last):
73
>>> raise DuplicateError
74
Traceback (most recent call last):
77
>>> raise ConfigspecError
78
Traceback (most recent call last):
81
>>> raise InterpolationLoopError('yoda')
82
Traceback (most recent call last):
83
InterpolationLoopError: interpolation loop detected in value "yoda".
85
>>> raise RepeatSectionError
86
Traceback (most recent call last):
89
>>> raise MissingInterpolationOption('yoda')
90
Traceback (most recent call last):
91
MissingInterpolationOption: missing option "yoda" in interpolation.
94
>>> raise ReloadError()
95
Traceback (most recent call last):
96
ReloadError: reload failed, filename is not set.
98
... raise ReloadError()
102
... raise Exception('We should catch a ReloadError as an IOError')
110
Tests from Section methods.
118
>>> a = '''[section1]
121
... more_options = False
122
... # end of file'''.splitlines()
123
>>> b = '''# File is user.ini
126
... # end of file'''.splitlines()
127
>>> c1 = ConfigObj(b)
128
>>> c2 = ConfigObj(a)
131
ConfigObj({'section1': {'option1': 'False', 'subsection': {'more_options': 'False'}}})
133
>>> config = '''[XXXXsection]
134
... XXXXkey = XXXXvalue'''.splitlines()
135
>>> cfg = ConfigObj(config)
137
ConfigObj({'XXXXsection': {'XXXXkey': 'XXXXvalue'}})
138
>>> def transform(section, key):
139
... val = section[key]
140
... newkey = key.replace('XXXX', 'CLIENT1')
141
... section.rename(key, newkey)
142
... if isinstance(val, (tuple, list, dict)):
145
... val = val.replace('XXXX', 'CLIENT1')
146
... section[newkey] = val
147
>>> cfg.walk(transform, call_on_sections=True)
148
{'CLIENT1section': {'CLIENT1key': None}}
150
ConfigObj({'CLIENT1section': {'CLIENT1key': 'CLIENT1value'}})
156
>>> something = object()
158
>>> c['something'] = something
159
>>> c['section'] = {'something': something}
160
>>> c.filename = 'fish'
161
>>> c.raise_errors = something
162
>>> c.list_values = something
163
>>> c.create_empty = something
164
>>> c.file_error = something
165
>>> c.stringify = something
166
>>> c.indent_type = something
167
>>> c.encoding = something
168
>>> c.default_encoding = something
169
>>> c.BOM = something
170
>>> c.newlines = something
171
>>> c.write_empty_values = something
172
>>> c.unrepr = something
173
>>> c.initial_comment = something
174
>>> c.final_comment = something
175
>>> c.configspec = something
176
>>> c.inline_comments = something
177
>>> c.comments = something
178
>>> c.defaults = something
179
>>> c.default_values = something
198
>>> c.default_encoding
201
>>> c.write_empty_values
203
>>> c.inline_comments
220
>>> c = ConfigObj(StringIO())
222
Traceback (most recent call last):
223
ReloadError: reload failed, filename is not set.
226
Traceback (most recent call last):
227
ReloadError: reload failed, filename is not set.
228
>>> c = ConfigObj([])
230
Traceback (most recent call last):
231
ReloadError: reload failed, filename is not set.
233
We need to use a real file as reload is only for files loaded from
235
>>> h = open('temp', 'w')
259
... test1= integer(30,50)
264
... test1=integer(30,50)
269
... test1=integer(30,50)
274
... test1=integer(30,50)
279
>>> c = ConfigObj('temp', configspec=configspec)
280
>>> c.configspec['test1'] = 'integer(50,60)'
281
>>> backup = ConfigObj('temp')
285
>>> c['section2']['extra'] = '3'
289
>>> c.validate(Validator())
291
>>> os.remove('temp')
297
Dummy function to hold some of the doctests.
334
... 'keys11': 'val1',
335
... 'keys13': 'val3',
336
... 'keys12': 'val2',
339
... 'section 2 sub 1': {
342
... 'keys21': 'val1',
343
... 'keys22': 'val2',
344
... 'keys23': 'val3',
349
... 'a' = b # !"$%^&*(),::;'@~#= 33
351
... ''' .split('\\n')
352
>>> t2 = ConfigObj(t)
353
>>> assert t2 == {'a': 'b', 'b': 'b'}
354
>>> t2.inline_comments['b'] = ''
356
>>> assert t2.write() == ['','b = b', '']
358
# Test ``list_values=False`` stuff
361
... key2 = 'single quotes'
362
... key3 = "double quotes"
363
... key4 = "list", 'with', several, "quotes"
365
>>> cfg = ConfigObj(c.splitlines(), list_values=False)
366
>>> cfg == {'key1': 'no quotes', 'key2': "'single quotes'",
367
... 'key3': '"double quotes"',
368
... 'key4': '"list", \\'with\\', several, "quotes"'
371
>>> cfg = ConfigObj(list_values=False)
372
>>> cfg['key1'] = 'Multiline\\nValue'
373
>>> cfg['key2'] = '''"Value" with 'quotes' !'''
375
["key1 = '''Multiline\\nValue'''", 'key2 = "Value" with \\'quotes\\' !']
376
>>> cfg.list_values = True
377
>>> cfg.write() == ["key1 = '''Multiline\\nValue'''",
378
... 'key2 = \\'\\'\\'"Value" with \\'quotes\\' !\\'\\'\\'']
400
... test1= integer(30,50)
405
... test1=integer(30,50)
410
... test1=integer(30,50)
415
>>> val = Validator()
416
>>> c1 = ConfigObj(config, configspec=configspec)
417
>>> res = c1.validate(val)
418
>>> flatten_errors(c1, res) == [([], 'test4', False), (['section',
419
... 'sub section'], 'test4', False), (['section'], 'test4', False)]
421
>>> res = c1.validate(val, preserve_errors=True)
422
>>> check = flatten_errors(c1, res)
426
(['section', 'sub section'], 'test4')
428
(['section'], 'test4')
429
>>> for entry in check:
430
... isinstance(entry[2], VdtValueTooSmallError)
431
... print str(entry[2])
433
the value "5.0" is too small.
435
the value "5.0" is too small.
437
the value "5.0" is too small.
439
Test unicode handling, BOM, write witha file like object and line endings :
441
... # initial comment
442
... # inital comment 2
444
... test1 = some value
446
... test2 = another value # inline comment
447
... # section comment
448
... [section] # inline comment
449
... test = test # another inline comment
455
>>> u = u_base.encode('utf_8').splitlines(True)
456
>>> u[0] = BOM_UTF8 + u[0]
457
>>> uc = ConfigObj(u)
458
>>> uc.encoding = None
461
>>> uc == {'test1': 'some value', 'test2': 'another value',
462
... 'section': {'test': 'test', 'test2': 'test2'}}
464
>>> uc = ConfigObj(u, encoding='utf_8', default_encoding='latin-1')
467
>>> isinstance(uc['test1'], unicode)
473
>>> uc['latin1'] = "This costs lot's of "
474
>>> a_list = uc.write()
477
>>> isinstance(a_list[0], str)
479
>>> a_list[0].startswith(BOM_UTF8)
481
>>> u = u_base.replace('\\n', '\\r\\n').encode('utf_8').splitlines(True)
482
>>> uc = ConfigObj(u)
485
>>> uc.newlines = '\\r'
486
>>> file_like = StringIO()
487
>>> uc.write(file_like)
488
>>> file_like.seek(0)
489
>>> uc2 = ConfigObj(file_like)
492
>>> uc2.filename == None
494
>>> uc2.newlines == '\\r'
497
Test validate in copy mode
499
... # Initial Comment
501
... key1 = string(default=Hello)
503
... # section comment
504
... [section] # inline comment
506
... key1 = integer(default=6)
508
... key2 = boolean(default=True)
510
... # subsection comment
511
... [[sub-section]] # inline comment
512
... # another key1 comment
513
... key1 = float(default=3.0)'''.splitlines()
514
>>> b = ConfigObj(configspec=a)
515
>>> b.validate(val, copy=True)
517
>>> b.write() == ['',
518
... '# Initial Comment',
522
... '# section comment',
523
... '[section] # inline comment',
524
... ' # key1 comment',
526
... ' # key2 comment',
529
... ' # subsection comment',
530
... ' [[sub-section]] # inline comment',
531
... ' # another key1 comment',
535
Test Writing Empty Values
538
... key2 =# a comment'''
539
>>> b = ConfigObj(a.splitlines())
541
['', 'key1 = ""', 'key2 = "" # a comment']
542
>>> b.write_empty_values = True
544
['', 'key1 = ', 'key2 = # a comment']
546
Test unrepr when reading
548
... key1 = (1, 2, 3) # comment
550
... key3 = 'a string'
551
... key4 = [1, 2, 3, 'a mixed list']
553
>>> b = ConfigObj(a, unrepr=True)
554
>>> b == {'key1': (1, 2, 3),
556
... 'key3': 'a string',
557
... 'key4': [1, 2, 3, 'a mixed list']}
560
Test unrepr when writing
561
>>> c = ConfigObj(b.write(), unrepr=True)
565
Test unrepr with multiline values
570
>>> c = ConfigObj(a, unrepr=True)
571
>>> c == {'k': {'k1': 3, 'k2': 6.0}}
574
Test unrepr with a dictionary
575
>>> a = 'k = {"a": 1}'.splitlines()
576
>>> c = ConfigObj(a, unrepr=True)
577
>>> type(c['k']) == dict
582
... key1 = (1, 2, 3) # comment
584
... key3 = 'a string'
585
... key4 = [1, 2, 3, 'a mixed list#']
587
>>> b = ConfigObj(a, unrepr=True)
588
>>> b == {'key1': (1, 2, 3),
590
... 'key3': 'a string',
591
... 'key4': [1, 2, 3, 'a mixed list#']}
595
# Comments are no longer parsed from values in configspecs
596
# so the following test fails and is disabled
598
Test validate in copy mode
600
... # Initial Comment
602
... key1 = string(default=Hello) # comment 1
604
... # section comment
605
... [section] # inline comment
607
... key1 = integer(default=6) # an integer value
609
... key2 = boolean(default=True) # a boolean
611
... # subsection comment
612
... [[sub-section]] # inline comment
613
... # another key1 comment
614
... key1 = float(default=3.0) # a float'''.splitlines()
615
>>> b = ConfigObj(configspec=a)
616
>>> b.validate(val, copy=True)
619
>>> b.write() == ['',
620
... '# Initial Comment',
622
... 'key1 = Hello # comment 1',
624
... '# section comment',
625
... '[section] # inline comment',
626
... ' # key1 comment',
627
... ' key1 = 6 # an integer value',
628
... ' # key2 comment',
629
... ' key2 = True # a boolean',
631
... ' # subsection comment',
632
... ' [[sub-section]] # inline comment',
633
... ' # another key1 comment',
634
... ' key1 = 3.0 # a float']
639
def _test_configobj():
642
Testing with duplicate keys and sections.
652
>>> ConfigObj(c.split('\\n'), raise_errors = True)
653
Traceback (most recent call last):
654
DuplicateError: Duplicate section name at line 6.
662
... 'member1' = value
666
>>> ConfigObj(d.split('\\n'), raise_errors = True)
667
Traceback (most recent call last):
668
DuplicateError: Duplicate keyword name at line 7.
670
Testing ConfigParser-style interpolation
675
... 'userdir': 'c:\\\\home',
680
... 'a': '%(datadir)s\\\\some path\\\\file.py',
681
... 'b': '%(userdir)s\\\\some path\\\\file.py',
683
... 'd': '%(not_here)s',
686
>>> c['section']['DEFAULT'] = {
687
... 'datadir': 'c:\\\\silly_test',
688
... 'a': 'hello - %(b)s',
690
>>> c['section']['a'] == 'c:\\\\silly_test\\\\some path\\\\file.py'
692
>>> c['section']['b'] == 'c:\\\\home\\\\some path\\\\file.py'
694
>>> c['section']['c'] == 'Yo c:\\\\silly_test\\\\some path\\\\file.py'
697
Switching Interpolation Off
699
>>> c.interpolation = False
700
>>> c['section']['a'] == '%(datadir)s\\\\some path\\\\file.py'
702
>>> c['section']['b'] == '%(userdir)s\\\\some path\\\\file.py'
704
>>> c['section']['c'] == 'Yo %(a)s'
707
Testing the interpolation errors.
709
>>> c.interpolation = True
710
>>> c['section']['d']
711
Traceback (most recent call last):
712
MissingInterpolationOption: missing option "not_here" in interpolation.
713
>>> c['section']['e']
714
Traceback (most recent call last):
715
InterpolationLoopError: interpolation loop detected in value "e".
717
Testing Template-style interpolation
721
... keyword1 = value1
722
... 'keyword 2' = 'value 2'
723
... reference = ${keyword1}
727
... templatebare = $keyword1/foo
729
... dollar = $$300.00
730
... stophere = $$notinterpolated
731
... with_braces = ${keyword1}s (plural)
732
... with_spaces = ${keyword 2}!!!
733
... with_several = $keyword1/$reference/$keyword1
734
... configparsersample = %(keyword 2)sconfig
735
... deep = ${reference}
740
... [[ sub-section ]]
741
... quux = '$baz + $bar + $foo'
743
... [[[ sub-sub-section ]]]
744
... convoluted = "$bar + $baz + $quux + $bar"
746
>>> c = ConfigObj(interp_cfg.split('\\n'), interpolation='Template')
747
>>> c['section']['templatebare']
749
>>> c['section']['dollar']
751
>>> c['section']['stophere']
753
>>> c['section']['with_braces']
755
>>> c['section']['with_spaces']
757
>>> c['section']['with_several']
758
'value1/value1/value1'
759
>>> c['section']['configparsersample']
760
'%(keyword 2)sconfig'
761
>>> c['section']['deep']
763
>>> c['section']['sub-section']['quux']
765
>>> c['section']['sub-section']['sub-sub-section']['convoluted']
766
'$foo + 123 + 123 + $foo + 123 + $foo'
770
>>> i._quote('\"""\\'\\'\\'')
771
Traceback (most recent call last):
772
ConfigObjError: Value \"\"""'''" cannot be safely quoted.
774
... i._quote('\\n', multiline=False)
775
... except ConfigObjError, e:
777
'Value "\\n" cannot be safely quoted.'
778
>>> i._quote(' "\\' ', multiline=False)
779
Traceback (most recent call last):
780
ConfigObjError: Value " "' " cannot be safely quoted.
782
Testing with "stringify" off.
783
>>> c.stringify = False
785
Traceback (most recent call last):
786
TypeError: Value is not a string "1".
788
Testing Empty values.
789
>>> cfg_with_empty = '''
791
... k2 =# comment test
796
... val5 = 1, 2, \'''.splitlines()
797
>>> cwe = ConfigObj(cfg_with_empty)
798
>>> cwe == {'k': '', 'k2': '', 'val': 'test', 'val2': [],
799
... 'val3': ['1'], 'val4': ['1', '2'], 'val5': ['1', '2']}
801
>>> cwe = ConfigObj(cfg_with_empty, list_values=False)
802
>>> cwe == {'k': '', 'k2': '', 'val': 'test', 'val2': ',',
803
... 'val3': '1,', 'val4': '1, 2', 'val5': '1, 2,'}
807
>>> testconfig3 = \'''
810
... c = test1, test2 , test3
811
... d = test1, test2, test3,
813
>>> d = ConfigObj(testconfig3.split('\\n'), raise_errors=True)
816
>>> d['b'] == ['test']
818
>>> d['c'] == ['test1', 'test2', 'test3']
820
>>> d['d'] == ['test1', 'test2', 'test3']
823
Testing with list values off.
826
... testconfig3.split('\\n'),
827
... raise_errors=True,
828
... list_values=False)
831
>>> e['b'] == 'test,'
833
>>> e['c'] == 'test1, test2 , test3'
835
>>> e['d'] == 'test1, test2, test3,'
838
Testing creating from a dictionary.
865
Testing we correctly detect badly built list values (4 of them).
867
>>> testconfig4 = '''
871
... dummy = ,,hello, goodbye
874
... ConfigObj(testconfig4.split('\\n'))
875
... except ConfigObjError, e:
879
Testing we correctly detect badly quoted values (4 of them).
881
>>> testconfig5 = '''
882
... config = "hello # comment
884
... fish = 'goodbye # comment
885
... dummy = "hello again
888
... ConfigObj(testconfig5.split('\\n'))
889
... except ConfigObjError, e:
893
Test Multiline Comments
895
... 'name4': ' another single line value ',
896
... 'multi section': {
897
... 'name4': '\\n Well, this is a\\n multiline '
899
... 'name2': '\\n Well, this is a\\n multiline '
901
... 'name3': '\\n Well, this is a\\n multiline '
903
... 'name1': '\\n Well, this is a\\n multiline '
906
... 'name2': ' another single line value ',
907
... 'name3': ' a single line value ',
908
... 'name1': ' a single line value ',
912
>>> filename = a.filename
913
>>> a.filename = None
914
>>> values = a.write()
916
>>> while index < 23:
918
... line = values[index-1]
919
... assert line.endswith('# comment ' + str(index))
920
>>> a.filename = filename
922
>>> start_comment = ['# Initial Comment', '', '#']
923
>>> end_comment = ['', '#', '# Final Comment']
924
>>> newconfig = start_comment + testconfig1.split('\\n') + end_comment
925
>>> nc = ConfigObj(newconfig)
926
>>> nc.initial_comment
927
['# Initial Comment', '', '#']
929
['', '#', '# Final Comment']
930
>>> nc.initial_comment == start_comment
932
>>> nc.final_comment == end_comment
935
Test the _handle_comment method
939
>>> c.inline_comments['foo'] = 'Nice bar'
941
['foo = bar # Nice bar']
943
tekNico: FIXME: use StringIO instead of real files
945
>>> filename = a.filename
946
>>> a.filename = 'test.ini'
948
>>> a.filename = filename
949
>>> a == ConfigObj('test.ini', raise_errors=True)
951
>>> os.remove('test.ini')
952
>>> b.filename = 'test.ini'
954
>>> b == ConfigObj('test.ini', raise_errors=True)
956
>>> os.remove('test.ini')
957
>>> i.filename = 'test.ini'
959
>>> i == ConfigObj('test.ini', raise_errors=True)
961
>>> os.remove('test.ini')
963
>>> a['DEFAULT'] = {'a' : 'fish'}
966
['a = %(a)s', '[DEFAULT]', 'a = fish']
968
Test indentation handling
970
>>> ConfigObj({'sect': {'sect': {'foo': 'bar'}}}).write()
971
['[sect]', ' [[sect]]', ' foo = bar']
972
>>> cfg = ['[sect]', '[[sect]]', 'foo = bar']
973
>>> ConfigObj(cfg).write() == cfg
975
>>> cfg = ['[sect]', ' [[sect]]', ' foo = bar']
976
>>> ConfigObj(cfg).write() == cfg
978
>>> cfg = ['[sect]', ' [[sect]]', ' foo = bar']
979
>>> ConfigObj(cfg).write() == cfg
981
>>> ConfigObj(oneTabCfg).write() == oneTabCfg
983
>>> ConfigObj(twoTabsCfg).write() == twoTabsCfg
985
>>> ConfigObj(tabsAndSpacesCfg).write() == tabsAndSpacesCfg
987
>>> ConfigObj(cfg, indent_type=chr(9)).write() == oneTabCfg
989
>>> ConfigObj(oneTabCfg, indent_type=' ').write() == cfg
994
def _test_validate():
1011
... '''.split('\\n')
1012
>>> configspec = '''
1013
... test1= integer(30,50)
1016
... test4=float(6.0)
1018
... test1=integer(30,50)
1021
... test4=float(6.0)
1023
... test1=integer(30,50)
1026
... test4=float(6.0)
1027
... '''.split('\\n')
1028
>>> val = Validator()
1029
>>> c1 = ConfigObj(config, configspec=configspec)
1030
>>> test = c1.validate(val)
1041
... 'sub section': {
1050
>>> val.check(c1.configspec['test4'], c1['test4'])
1051
Traceback (most recent call last):
1052
VdtValueTooSmallError: the value "5.0" is too small.
1054
>>> val_test_config = '''
1059
... key2 = 1.1, 3.0, 17, 6.8
1062
... key2 = True'''.split('\\n')
1063
>>> val_test_configspec = '''
1068
... key2 = float_list(4)
1070
... key = option(option1, option2)
1071
... key2 = boolean'''.split('\\n')
1072
>>> val_test = ConfigObj(val_test_config, configspec=val_test_configspec)
1073
>>> val_test.validate(val)
1075
>>> val_test['key'] = 'text not a digit'
1076
>>> val_res = val_test.validate(val)
1077
>>> val_res == {'key2': True, 'section': True, 'key': False}
1079
>>> configspec = '''
1080
... test1=integer(30,50, default=40)
1081
... test2=string(default="hello")
1082
... test3=integer(default=3)
1083
... test4=float(6.0, default=6.0)
1085
... test1=integer(30,50, default=40)
1086
... test2=string(default="hello")
1087
... test3=integer(default=3)
1088
... test4=float(6.0, default=6.0)
1090
... test1=integer(30,50, default=40)
1091
... test2=string(default="hello")
1092
... test3=integer(default=3)
1093
... test4=float(6.0, default=6.0)
1094
... '''.split('\\n')
1095
>>> default_test = ConfigObj(['test1=30'], configspec=configspec)
1097
ConfigObj({'test1': '30'})
1098
>>> default_test.defaults
1100
>>> default_test.default_values
1102
>>> default_test.validate(val)
1104
>>> default_test == {
1106
... 'test2': 'hello',
1111
... 'test2': 'hello',
1114
... 'sub section': {
1117
... 'test2': 'hello',
1123
>>> default_test.defaults
1124
['test2', 'test3', 'test4']
1125
>>> default_test.default_values == {'test1': 40, 'test2': 'hello',
1126
... 'test3': 3, 'test4': 6.0}
1128
>>> default_test.restore_default('test1')
1130
>>> default_test['test1']
1132
>>> 'test1' in default_test.defaults
1134
>>> def change(section, key):
1135
... section[key] = 3
1136
>>> _ = default_test.walk(change)
1137
>>> default_test['section']['sub section']['test4']
1139
>>> default_test.restore_defaults()
1140
>>> default_test == {
1142
... 'test2': "hello",
1147
... 'test2': "hello",
1150
... 'sub section': {
1152
... 'test2': "hello",
1157
>>> a = ['foo = fish']
1158
>>> b = ['foo = integer(default=3)']
1159
>>> c = ConfigObj(a, configspec=b)
1161
ConfigObj({'foo': 'fish'})
1162
>>> from validate import Validator
1166
>>> c.default_values
1168
>>> c.restore_default('foo')
1171
Now testing with repeated sections : BIG TEST
1173
>>> repeated_1 = '''
1175
... [[__many__]] # spec for a dog
1176
... fleas = boolean(default=True)
1177
... tail = option(long, short, default=long)
1178
... name = string(default=rover)
1179
... [[[__many__]]] # spec for a puppy
1180
... name = string(default="son of rover")
1181
... age = float(default=0.0)
1183
... [[__many__]] # spec for a cat
1184
... fleas = boolean(default=True)
1185
... tail = option(long, short, default=short)
1186
... name = string(default=pussy)
1187
... [[[__many__]]] # spec for a kitten
1188
... name = string(default="son of pussy")
1189
... age = float(default=0.0)
1190
... '''.split('\\n')
1191
>>> repeated_2 = '''
1194
... # blank dogs with puppies
1195
... # should be filled in by the configspec
1210
... # blank cats with kittens
1211
... # should be filled in by the configspec
1224
... '''.split('\\n')
1225
>>> repeated_3 = '''
1236
... '''.split('\\n')
1237
>>> repeated_4 = '''
1240
... name = string(default=Michael)
1241
... age = float(default=0.0)
1242
... sex = option(m, f, default=m)
1243
... '''.split('\\n')
1244
>>> repeated_5 = '''
1247
... fleas = boolean(default=True)
1248
... tail = option(long, short, default=short)
1249
... name = string(default=pussy)
1250
... [[[description]]]
1251
... height = float(default=3.3)
1252
... weight = float(default=6)
1254
... fur = option(black, grey, brown, "tortoise shell", default=black)
1255
... condition = integer(0,10, default=5)
1256
... '''.split('\\n')
1257
>>> val= Validator()
1258
>>> repeater = ConfigObj(repeated_2, configspec=repeated_1)
1259
>>> repeater.validate(val)
1266
... 'name': 'rover',
1267
... 'puppy1': {'name': 'son of rover', 'age': 0.0},
1268
... 'puppy2': {'name': 'son of rover', 'age': 0.0},
1269
... 'puppy3': {'name': 'son of rover', 'age': 0.0},
1274
... 'name': 'rover',
1275
... 'puppy1': {'name': 'son of rover', 'age': 0.0},
1276
... 'puppy2': {'name': 'son of rover', 'age': 0.0},
1277
... 'puppy3': {'name': 'son of rover', 'age': 0.0},
1282
... 'name': 'rover',
1283
... 'puppy1': {'name': 'son of rover', 'age': 0.0},
1284
... 'puppy2': {'name': 'son of rover', 'age': 0.0},
1285
... 'puppy3': {'name': 'son of rover', 'age': 0.0},
1291
... 'tail': 'short',
1292
... 'name': 'pussy',
1293
... 'kitten1': {'name': 'son of pussy', 'age': 0.0},
1294
... 'kitten2': {'name': 'son of pussy', 'age': 0.0},
1295
... 'kitten3': {'name': 'son of pussy', 'age': 0.0},
1299
... 'tail': 'short',
1300
... 'name': 'pussy',
1301
... 'kitten1': {'name': 'son of pussy', 'age': 0.0},
1302
... 'kitten2': {'name': 'son of pussy', 'age': 0.0},
1303
... 'kitten3': {'name': 'son of pussy', 'age': 0.0},
1307
... 'tail': 'short',
1308
... 'name': 'pussy',
1309
... 'kitten1': {'name': 'son of pussy', 'age': 0.0},
1310
... 'kitten2': {'name': 'son of pussy', 'age': 0.0},
1311
... 'kitten3': {'name': 'son of pussy', 'age': 0.0},
1316
>>> repeater = ConfigObj(repeated_3, configspec=repeated_1)
1317
>>> repeater.validate(val)
1321
... 'cat1': {'fleas': True, 'tail': 'short', 'name': 'pussy'},
1322
... 'cat2': {'fleas': True, 'tail': 'short', 'name': 'pussy'},
1323
... 'cat3': {'fleas': True, 'tail': 'short', 'name': 'pussy'},
1326
... 'dog1': {'fleas': True, 'tail': 'long', 'name': 'rover'},
1327
... 'dog2': {'fleas': True, 'tail': 'long', 'name': 'rover'},
1328
... 'dog3': {'fleas': True, 'tail': 'long', 'name': 'rover'},
1332
>>> repeater = ConfigObj(configspec=repeated_4)
1333
>>> repeater['Michael'] = {}
1334
>>> repeater.validate(val)
1337
... 'Michael': {'age': 0.0, 'name': 'Michael', 'sex': 'm'},
1340
>>> repeater = ConfigObj(repeated_3, configspec=repeated_5)
1342
... 'dogs': {'dog1': {}, 'dog2': {}, 'dog3': {}},
1343
... 'cats': {'cat1': {}, 'cat2': {}, 'cat3': {}},
1346
>>> repeater.validate(val)
1349
... 'dogs': {'dog1': {}, 'dog2': {}, 'dog3': {}},
1353
... 'tail': 'short',
1354
... 'name': 'pussy',
1355
... 'description': {
1357
... 'height': 3.2999999999999998,
1358
... 'coat': {'fur': 'black', 'condition': 5},
1363
... 'tail': 'short',
1364
... 'name': 'pussy',
1365
... 'description': {
1367
... 'height': 3.2999999999999998,
1368
... 'coat': {'fur': 'black', 'condition': 5},
1373
... 'tail': 'short',
1374
... 'name': 'pussy',
1375
... 'description': {
1377
... 'height': 3.2999999999999998,
1378
... 'coat': {'fur': 'black', 'condition': 5},
1385
Test that interpolation is preserved for validated string values.
1386
Also check that interpolation works in configspecs.
1387
>>> t = ConfigObj(configspec=['test = string'])
1388
>>> t['DEFAULT'] = {}
1389
>>> t['DEFAULT']['def_test'] = 'a'
1390
>>> t['test'] = '%(def_test)s'
1396
>>> t.interpolation = False
1398
ConfigObj({'test': '%(def_test)s', 'DEFAULT': {'def_test': 'a'}})
1400
... 'interpolated string = string(default="fuzzy-%(man)s")',
1404
>>> c = ConfigObj(configspec=specs)
1407
>>> c['interpolated string']
1411
>>> val = SimpleVal()
1427
... '''.split('\\n')
1428
>>> configspec = '''
1443
... '''.split('\\n')
1444
>>> o = ConfigObj(config, configspec=configspec)
1447
>>> o = ConfigObj(configspec=configspec)
1452
>>> vtor = Validator()
1458
... another_option = Probably
1460
... another_option = True
1467
... option1 = boolean()
1468
... option2 = boolean()
1469
... option3 = boolean(default=Bad_value)
1471
... option1 = boolean()
1472
... option2 = boolean()
1473
... option3 = boolean(default=Bad_value)
1475
... another_option = boolean()
1477
... another_option = boolean()
1480
... value2 = integer
1481
... value3 = integer(0, 10)
1482
... [[[section3b-sub]]]
1485
... another_option = boolean()
1487
>>> cs = my_cfg.split('\\n')
1488
>>> ini = my_ini.split('\\n')
1489
>>> cfg = ConfigObj(ini, configspec=cs)
1490
>>> res = cfg.validate(vtor, preserve_errors=True)
1492
>>> for entry in flatten_errors(cfg, res):
1493
... section_list, key, error = entry
1494
... section_list.insert(0, '[root]')
1495
... if key is not None:
1496
... section_list.append(key)
1497
... section_string = ', '.join(section_list)
1498
... errors.append('%s%s%s' % (section_string, ' = ', error or 'missing'))
1500
>>> for entry in errors:
1502
[root], option2 = missing
1503
[root], option3 = the value "Bad_value" is of the wrong type.
1504
[root], section1, option2 = missing
1505
[root], section1, option3 = the value "Bad_value" is of the wrong type.
1506
[root], section2, another_option = the value "Probably" is of the wrong type.
1507
[root], section3, section3b, section3b-sub = missing
1508
[root], section3, section3b, value2 = the value "a" is of the wrong type.
1509
[root], section3, section3b, value3 = the value "11" is too big.
1510
[root], section4 = missing
1516
Test the error messages and objects, in normal mode and unrepr mode.
1517
>>> bad_syntax = '''
1520
... '''.splitlines()
1521
>>> c = ConfigObj(bad_syntax)
1522
Traceback (most recent call last):
1523
ParseError: Parse error in value at line 3.
1524
>>> c = ConfigObj(bad_syntax, raise_errors=True)
1525
Traceback (most recent call last):
1526
ParseError: Parse error in value at line 3.
1527
>>> c = ConfigObj(bad_syntax, raise_errors=True, unrepr=True)
1528
Traceback (most recent call last):
1529
UnreprError: Parse error in value at line 3.
1531
... c = ConfigObj(bad_syntax)
1532
... except Exception, e:
1534
>>> assert(isinstance(e, ConfigObjError))
1536
Parse error in value at line 3.
1537
>>> len(e.errors) == 1
1540
... c = ConfigObj(bad_syntax, unrepr=True)
1541
... except Exception, e:
1543
>>> assert(isinstance(e, ConfigObjError))
1545
Parse error in value at line 3.
1546
>>> len(e.errors) == 1
1548
>>> the_error = e.errors[0]
1549
>>> assert(isinstance(the_error, UnreprError))
1551
>>> multiple_bad_syntax = '''
1555
... '''.splitlines()
1557
... c = ConfigObj(multiple_bad_syntax)
1558
... except ConfigObjError, e:
1560
'Parsing failed with several errors.\\nFirst error at line 3.'
1561
>>> c = ConfigObj(multiple_bad_syntax, raise_errors=True)
1562
Traceback (most recent call last):
1563
ParseError: Parse error in value at line 3.
1564
>>> c = ConfigObj(multiple_bad_syntax, raise_errors=True, unrepr=True)
1565
Traceback (most recent call last):
1566
UnreprError: Parse error in value at line 3.
1568
... c = ConfigObj(multiple_bad_syntax)
1569
... except Exception, e:
1571
>>> assert(isinstance(e, ConfigObjError))
1573
Parsing failed with several errors.
1574
First error at line 3.
1575
>>> len(e.errors) == 2
1578
... c = ConfigObj(multiple_bad_syntax, unrepr=True)
1579
... except Exception, e:
1581
>>> assert(isinstance(e, ConfigObjError))
1583
Parsing failed with several errors.
1584
First error at line 3.
1585
>>> len(e.errors) == 2
1587
>>> the_error = e.errors[1]
1588
>>> assert(isinstance(the_error, UnreprError))
1590
>>> unknown_name = '''
1593
... '''.splitlines()
1594
>>> c = ConfigObj(unknown_name)
1595
>>> c = ConfigObj(unknown_name, unrepr=True)
1596
Traceback (most recent call last):
1597
UnreprError: Unknown name or type in value at line 3.
1598
>>> c = ConfigObj(unknown_name, raise_errors=True, unrepr=True)
1599
Traceback (most recent call last):
1600
UnreprError: Unknown name or type in value at line 3.
1604
def _test_unrepr_comments():
1607
... # initial comments
1608
... # with two lines
1610
... # section comment
1611
... [section] # inline section comment
1615
... # with two lines
1616
... '''.splitlines()
1617
>>> c = ConfigObj(config, unrepr=True)
1618
>>> c == { 'key': 'value',
1619
... 'section': { 'key': 'value'}}
1621
>>> c.initial_comment == ['', '# initial comments', '# with two lines']
1623
>>> c.comments == {'section': ['# section comment'], 'key': []}
1625
>>> c.inline_comments == {'section': '# inline section comment', 'key': ''}
1627
>>> c['section'].comments == { 'key': ['# key comment']}
1629
>>> c.final_comment == ['# final comment', '# with two lines']
1634
def _test_newline_terminated():
1637
>>> c.newlines = '\\n'
1639
>>> collector = StringIO()
1640
>>> c.write(collector)
1641
>>> collector.getvalue()
1646
def _test_hash_escaping():
1649
>>> c.newlines = '\\n'
1650
>>> c['#a'] = 'b # something'
1651
>>> collector = StringIO()
1652
>>> c.write(collector)
1653
>>> collector.getvalue()
1654
'"#a" = "b # something"\\n'
1657
>>> c.newlines = '\\n'
1658
>>> c['a'] = 'b # something', 'c # something'
1659
>>> collector = StringIO()
1660
>>> c.write(collector)
1661
>>> collector.getvalue()
1662
'a = "b # something", "c # something"\\n'
1666
def _test_lineendings():
1668
NOTE: Need to use a real file because this code is only
1669
exercised when reading from the filesystem.
1671
>>> h = open('temp', 'wb')
1672
>>> h.write('\\r\\n')
1674
>>> c = ConfigObj('temp')
1677
>>> h = open('temp', 'wb')
1680
>>> c = ConfigObj('temp')
1683
>>> os.remove('temp')
1687
def _test_validate_with_copy_and_many():
1692
... value = string(default='nothing')
1698
>>> c = ConfigObj(StringIO(config), configspec=StringIO(spec))
1700
>>> r = c.validate(v, copy=True)
1701
>>> c['section']['something']['value']
1705
def _test_configspec_with_hash():
1707
>>> spec = ['stuff = string(default="#ff00dd")']
1708
>>> c = ConfigObj(spec, _inspec=True)
1710
'string(default="#ff00dd")'
1711
>>> c = ConfigObj(configspec=spec)
1719
>>> spec = ['stuff = string(default="fish") # wooble']
1720
>>> c = ConfigObj(spec, _inspec=True)
1722
'string(default="fish") # wooble'
1725
def _test_many_check():
1727
>>> spec = ['__many__ = integer()']
1728
>>> config = ['a = 6', 'b = 7']
1729
>>> c = ConfigObj(config, configspec=spec)
1739
>>> spec = ['[name]', '__many__ = integer()']
1740
>>> config = ['[name]', 'a = 6', 'b = 7']
1741
>>> c = ConfigObj(config, configspec=spec)
1745
>>> type(c['name']['a'])
1747
>>> type(c['name']['b'])
1751
>>> spec = ['[__many__]', '__many__ = integer()']
1752
>>> config = ['[name]', 'hello = 7', '[thing]', 'fish = 0']
1753
>>> c = ConfigObj(config, configspec=spec)
1757
>>> type(c['name']['hello'])
1759
>>> type(c['thing']['fish'])
1764
... ___many___ = integer
1766
... ___many___ = boolean
1768
... __many__ = float
1769
... '''.splitlines()
1785
... '''.splitlines()
1786
>>> c = ConfigObj(config, configspec=spec)
1792
>>> type(c['buggle'])
1798
>>> type(c['hi']['bye']['odd'])
1800
>>> type(c['hi']['bye']['whoops'])
1806
>>> type(c['bye']['lots']['odd'])
1808
>>> type(c['bye']['lots']['whoops'])
1812
>>> spec = ['___many___ = integer()']
1813
>>> config = ['a = 6', 'b = 7']
1814
>>> c = ConfigObj(config, configspec=spec)
1827
... __many__ = float
1828
... '''.splitlines()
1838
... '''.splitlines()
1839
>>> c = ConfigObj(config, configspec=spec)
1843
>>> type(c['hi']['bye']['odd'])
1845
>>> type(c['hi']['bye']['whoops'])
1847
>>> type(c['bye']['lots']['odd'])
1849
>>> type(c['bye']['lots']['whoops'])
1852
>>> s = ['[dog]', '[[cow]]', 'something = boolean', '[[__many__]]',
1853
... 'fish = integer']
1854
>>> c = ['[dog]', '[[cow]]', 'something = true', '[[ob]]',
1855
... 'fish = 3', '[[bo]]', 'fish = 6']
1856
>>> ini = ConfigObj(c, configspec=s)
1860
>>> ini['dog']['cow']['something']
1862
>>> ini['dog']['ob']['fish']
1864
>>> ini['dog']['bo']['fish']
1868
>>> s = ['[cow]', 'something = boolean', '[__many__]',
1869
... 'fish = integer']
1870
>>> c = ['[cow]', 'something = true', '[ob]',
1871
... 'fish = 3', '[bo]', 'fish = 6']
1872
>>> ini = ConfigObj(c, configspec=s)
1876
>>> ini['cow']['something']
1878
>>> ini['ob']['fish']
1880
>>> ini['bo']['fish']
1885
def _unexpected_validation_errors():
1887
Although the input is nonsensical we should not crash but correctly
1888
report the failure to validate
1890
# section specified, got scalar
1891
>>> from validate import ValidateError
1892
>>> s = ['[cow]', 'something = boolean']
1893
>>> c = ['cow = true']
1894
>>> ini = ConfigObj(c, configspec=s)
1899
>>> ini = ConfigObj(c, configspec=s)
1900
>>> res = ini.validate(v, preserve_errors=True)
1901
>>> check = flatten_errors(ini, res)
1902
>>> for entry in check:
1903
... isinstance(entry[2], ValidateError)
1904
... print str(entry[2])
1906
Section 'cow' was provided as a single value
1909
# scalar specified, got section
1910
>>> s = ['something = boolean']
1911
>>> c = ['[something]', 'cow = true']
1912
>>> ini = ConfigObj(c, configspec=s)
1917
>>> ini = ConfigObj(c, configspec=s)
1918
>>> res = ini.validate(v, preserve_errors=True)
1919
>>> check = flatten_errors(ini, res)
1920
>>> for entry in check:
1921
... isinstance(entry[2], ValidateError)
1922
... print str(entry[2])
1924
Value 'something' was provided as a section
1926
# unexpected section
1928
>>> c = ['[cow]', 'dog = true']
1929
>>> ini = ConfigObj(c, configspec=s)
1935
>>> s = ['[cow]', 'dog = boolean']
1936
>>> c = ['[cow]', 'dog = true']
1937
>>> ini = ConfigObj(c, configspec=s)
1939
>>> ini.validate(v, preserve_errors=True)
1946
>>> s = ['[cow]', 'dog = boolean']
1947
>>> c = ['[cow]', 'dog = true']
1948
>>> ini = ConfigObj(c, configspec=s)
1950
>>> string = pickle.dumps(ini)
1951
>>> new = pickle.loads(string)
1956
def _test_as_list():
1970
def _test_list_interpolation():
1974
>>> c['list'] = ['%(x)s', 3]
1979
def _test_extra_values():
1981
>>> spec = ['[section]']
1982
>>> infile = ['bar = 3', '[something]', 'foo = fish', '[section]', 'foo=boo']
1983
>>> c = ConfigObj(infile, configspec=spec)
1986
>>> c.extra_values = ['bar', 'gosh', 'what']
1987
>>> c.validate(Validator())
1990
['bar', 'something']
1991
>>> c['section'].extra_values
1993
>>> c['something'].extra_values
1997
def _test_reset_and_clear_more():
2000
>>> c.extra_values = ['foo']
2001
>>> c.defaults = ['bar']
2002
>>> c.default_values = {'bar': 'baz'}
2008
>>> c.default_values
2010
>>> c.extra_values = ['foo']
2011
>>> c.defaults = ['bar']
2017
>>> c.default_values
2021
def _test_invalid_lists():
2023
>>> v = ['string = val, val2, , val3']
2024
>>> c = ConfigObj(v)
2025
Traceback (most recent call last):
2026
ParseError: Parse error in value at line 1.
2027
>>> v = ['string = val, val2,, val3']
2028
>>> c = ConfigObj(v)
2029
Traceback (most recent call last):
2030
ParseError: Parse error in value at line 1.
2031
>>> v = ['string = val, val2,,']
2032
>>> c = ConfigObj(v)
2033
Traceback (most recent call last):
2034
ParseError: Parse error in value at line 1.
2035
>>> v = ['string = val, ,']
2036
>>> c = ConfigObj(v)
2037
Traceback (most recent call last):
2038
ParseError: Parse error in value at line 1.
2039
>>> v = ['string = val, , ']
2040
>>> c = ConfigObj(v)
2041
Traceback (most recent call last):
2042
ParseError: Parse error in value at line 1.
2043
>>> v = ['string = ,,']
2044
>>> c = ConfigObj(v)
2045
Traceback (most recent call last):
2046
ParseError: Parse error in value at line 1.
2047
>>> v = ['string = ,, ']
2048
>>> c = ConfigObj(v)
2049
Traceback (most recent call last):
2050
ParseError: Parse error in value at line 1.
2051
>>> v = ['string = ,foo']
2052
>>> c = ConfigObj(v)
2053
Traceback (most recent call last):
2054
ParseError: Parse error in value at line 1.
2055
>>> v = ['string = foo, ']
2056
>>> c = ConfigObj(v)
2059
>>> v = ['string = foo, "']
2060
>>> c = ConfigObj(v)
2061
Traceback (most recent call last):
2062
ParseError: Parse error in value at line 1.
2065
def _test_validation_with_preserve_errors():
2068
>>> spec = ['[section]', 'foo = integer']
2069
>>> c = ConfigObj(configspec=spec)
2070
>>> c.validate(v, preserve_errors=True)
2072
>>> c = ConfigObj(['[section]'], configspec=spec)
2075
>>> c.validate(v, preserve_errors=True)
2076
{'section': {'foo': False}}
2080
# test _created on Section
2082
# TODO: Test BOM handling
2083
# TODO: Test error code for badly built multiline values
2084
# TODO: Test handling of StringIO
2085
# TODO: Test interpolation with writing
2088
if __name__ == '__main__':
2089
# run the code tests in doctest format
2092
key1= val # comment 1
2093
key2= val # comment 2
2096
key1= val # comment 5
2097
key2= val # comment 6
2100
key1= val # comment 9
2101
key2= val # comment 10
2103
[[lev2ba]] # comment 12
2104
key1= val # comment 13
2106
[[lev2bb]] # comment 15
2107
key1= val # comment 16
2109
[lev1c] # comment 18
2111
[[lev2c]] # comment 20
2113
[[[lev3c]]] # comment 22
2114
key1 = val # comment 23"""
2120
["section 1"] # comment
2129
[['section 2 sub 1']]
2134
name1 = """ a single line value """ # comment
2135
name2 = \''' another single line value \''' # comment
2136
name3 = """ a single line value """
2137
name4 = \''' another single line value \'''
2154
\''' # I guess this is a comment too
2157
# these cannot be put among the doctests, because the doctest module
2158
# does a string.expandtabs() on all of them, sigh
2159
oneTabCfg = ['[sect]', '\t[[sect]]', '\t\tfoo = bar']
2160
twoTabsCfg = ['[sect]', '\t\t[[sect]]', '\t\t\t\tfoo = bar']
2161
tabsAndSpacesCfg = ['[sect]', '\t \t [[sect]]', '\t \t \t \t foo = bar']
2164
m = sys.modules.get('__main__')
2165
globs = m.__dict__.copy()
2166
a = ConfigObj(testconfig1.split('\n'), raise_errors=True)
2167
b = ConfigObj(testconfig2.split('\n'), raise_errors=True)
2168
i = ConfigObj(testconfig6.split('\n'), raise_errors=True)
2169
globs.update({'INTP_VER': INTP_VER, 'a': a, 'b': b, 'i': i,
2170
'oneTabCfg': oneTabCfg, 'twoTabsCfg': twoTabsCfg,
2171
'tabsAndSpacesCfg': tabsAndSpacesCfg})
2172
doctest.testmod(m, globs=globs)
2175
failures, tests = doctest.testmod(configobj, globs=globs)
2176
print "Ran %i tests. Failures: %i" % (tests, failures)
2181
# Man alive I prefer unittest ;-)