• R/O
  • SSH
  • HTTPS

traclight: Commit


Commit MetaInfo

Revision40 (tree)
Time2010-09-20 10:23:41
Authortag

Log Message

configobjを4.7.2に置き換えた。

Change Summary

Incremental Difference

--- branches/tl3_0/python-lib/configobj/configobj_test.py (revision 39)
+++ branches/tl3_0/python-lib/configobj/configobj_test.py (nonexistent)
@@ -1,1790 +0,0 @@
1-# configobj_test.py
2-# doctests for ConfigObj
3-# A config file reader/writer that supports nested sections in config files.
4-# Copyright (C) 2005-2008 Michael Foord, Nicola Larosa
5-# E-mail: fuzzyman AT voidspace DOT org DOT uk
6-# nico AT tekNico DOT net
7-
8-# ConfigObj 4
9-# http://www.voidspace.org.uk/python/configobj.html
10-
11-# Released subject to the BSD License
12-# Please see http://www.voidspace.org.uk/python/license.shtml
13-
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.
19-
20-
21-from __future__ import generators
22-from StringIO import StringIO
23-
24-import os
25-import sys
26-INTP_VER = sys.version_info[:2]
27-if INTP_VER < (2, 2):
28- raise RuntimeError("Python v.2.2 or later needed")
29-
30-try:
31- from codecs import BOM_UTF8
32-except ImportError:
33- # Python 2.2 does not have this
34- # UTF-8
35- BOM_UTF8 = '\xef\xbb\xbf'
36-
37-from configobj import *
38-from validate import Validator, VdtValueTooSmallError
39-
40-
41-"""
42- >>> z = ConfigObj()
43- >>> z['a'] = 'a'
44- >>> z['sect'] = {
45- ... 'subsect': {
46- ... 'a': 'fish',
47- ... 'b': 'wobble',
48- ... },
49- ... 'member': 'value',
50- ... }
51- >>> x = ConfigObj(z.write())
52- >>> z == x
53- 1
54-"""
55-
56-
57-def _error_test():
58- """
59- Testing the error classes.
60-
61- >>> raise ConfigObjError
62- Traceback (most recent call last):
63- ConfigObjError
64-
65- >>> raise NestingError
66- Traceback (most recent call last):
67- NestingError
68-
69- >>> raise ParseError
70- Traceback (most recent call last):
71- ParseError
72-
73- >>> raise DuplicateError
74- Traceback (most recent call last):
75- DuplicateError
76-
77- >>> raise ConfigspecError
78- Traceback (most recent call last):
79- ConfigspecError
80-
81- >>> raise InterpolationLoopError('yoda')
82- Traceback (most recent call last):
83- InterpolationLoopError: interpolation loop detected in value "yoda".
84-
85- >>> raise RepeatSectionError
86- Traceback (most recent call last):
87- RepeatSectionError
88-
89- >>> raise MissingInterpolationOption('yoda')
90- Traceback (most recent call last):
91- MissingInterpolationOption: missing option "yoda" in interpolation.
92-
93-
94- >>> raise ReloadError()
95- Traceback (most recent call last):
96- ReloadError: reload failed, filename is not set.
97- >>> try:
98- ... raise ReloadError()
99- ... except IOError:
100- ... pass
101- ... else:
102- ... raise Exception('We should catch a ReloadError as an IOError')
103- >>>
104-
105- """
106-
107-
108-def _section_test():
109- """
110- Tests from Section methods.
111-
112- >>> n = a.dict()
113- >>> n == a
114- 1
115- >>> n is a
116- 0
117-
118- >>> a = '''[section1]
119- ... option1 = True
120- ... [[subsection]]
121- ... more_options = False
122- ... # end of file'''.splitlines()
123- >>> b = '''# File is user.ini
124- ... [section1]
125- ... option1 = False
126- ... # end of file'''.splitlines()
127- >>> c1 = ConfigObj(b)
128- >>> c2 = ConfigObj(a)
129- >>> c2.merge(c1)
130- >>> c2
131- ConfigObj({'section1': {'option1': 'False', 'subsection': {'more_options': 'False'}}})
132-
133- >>> config = '''[XXXXsection]
134- ... XXXXkey = XXXXvalue'''.splitlines()
135- >>> cfg = ConfigObj(config)
136- >>> cfg
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)):
143- ... pass
144- ... else:
145- ... val = val.replace('XXXX', 'CLIENT1')
146- ... section[newkey] = val
147- >>> cfg.walk(transform, call_on_sections=True)
148- {'CLIENT1section': {'CLIENT1key': None}}
149- >>> cfg
150- ConfigObj({'CLIENT1section': {'CLIENT1key': 'CLIENT1value'}})
151- """
152-
153-
154-def _test_reset():
155- """
156- >>> something = object()
157- >>> c = ConfigObj()
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
180- >>> c.reset()
181- >>>
182- >>> c.filename
183- >>> c.raise_errors
184- False
185- >>> c.list_values
186- True
187- >>> c.create_empty
188- False
189- >>> c.file_error
190- False
191- >>> c.interpolation
192- True
193- >>> c.configspec
194- >>> c.stringify
195- True
196- >>> c.indent_type
197- >>> c.encoding
198- >>> c.default_encoding
199- >>> c.unrepr
200- False
201- >>> c.write_empty_values
202- False
203- >>> c.inline_comments
204- {}
205- >>> c.comments
206- {}
207- >>> c.defaults
208- []
209- >>> c.default_values
210- {}
211- >>> c == ConfigObj()
212- True
213- >>> c
214- ConfigObj({})
215- """
216-
217-
218-def _test_reload():
219- """
220- >>> c = ConfigObj(StringIO())
221- >>> c.reload()
222- Traceback (most recent call last):
223- ReloadError: reload failed, filename is not set.
224- >>> c = ConfigObj()
225- >>> c.reload()
226- Traceback (most recent call last):
227- ReloadError: reload failed, filename is not set.
228- >>> c = ConfigObj([])
229- >>> c.reload()
230- Traceback (most recent call last):
231- ReloadError: reload failed, filename is not set.
232-
233- We need to use a real file as reload is only for files loaded from
234- the filesystem.
235- >>> h = open('temp', 'w')
236- >>> h.write('''
237- ... test1=40
238- ... test2=hello
239- ... test3=3
240- ... test4=5.0
241- ... [section]
242- ... test1=40
243- ... test2=hello
244- ... test3=3
245- ... test4=5.0
246- ... [[sub section]]
247- ... test1=40
248- ... test2=hello
249- ... test3=3
250- ... test4=5.0
251- ... [section2]
252- ... test1=40
253- ... test2=hello
254- ... test3=3
255- ... test4=5.0
256- ... ''')
257- >>> h.close()
258- >>> configspec = '''
259- ... test1= integer(30,50)
260- ... test2= string
261- ... test3=integer
262- ... test4=float(4.5)
263- ... [section]
264- ... test1=integer(30,50)
265- ... test2=string
266- ... test3=integer
267- ... test4=float(4.5)
268- ... [[sub section]]
269- ... test1=integer(30,50)
270- ... test2=string
271- ... test3=integer
272- ... test4=float(4.5)
273- ... [section2]
274- ... test1=integer(30,50)
275- ... test2=string
276- ... test3=integer
277- ... test4=float(4.5)
278- ... '''.split('\\n')
279- >>> c = ConfigObj('temp', configspec=configspec)
280- >>> c.configspec['test1'] = 'integer(50,60)'
281- >>> backup = ConfigObj('temp')
282- >>> del c['section']
283- >>> del c['test1']
284- >>> c['extra'] = '3'
285- >>> c['section2']['extra'] = '3'
286- >>> c.reload()
287- >>> c == backup
288- True
289- >>> c.validate(Validator())
290- True
291- >>> os.remove('temp')
292- """
293-
294-
295-def _doctest():
296- """
297- Dummy function to hold some of the doctests.
298-
299- >>> a.depth
300- 0
301- >>> a == {
302- ... 'key2': 'val',
303- ... 'key1': 'val',
304- ... 'lev1c': {
305- ... 'lev2c': {
306- ... 'lev3c': {
307- ... 'key1': 'val',
308- ... },
309- ... },
310- ... },
311- ... 'lev1b': {
312- ... 'key2': 'val',
313- ... 'key1': 'val',
314- ... 'lev2ba': {
315- ... 'key1': 'val',
316- ... },
317- ... 'lev2bb': {
318- ... 'key1': 'val',
319- ... },
320- ... },
321- ... 'lev1a': {
322- ... 'key2': 'val',
323- ... 'key1': 'val',
324- ... },
325- ... }
326- 1
327- >>> b.depth
328- 0
329- >>> b == {
330- ... 'key3': 'val3',
331- ... 'key2': 'val2',
332- ... 'key1': 'val1',
333- ... 'section 1': {
334- ... 'keys11': 'val1',
335- ... 'keys13': 'val3',
336- ... 'keys12': 'val2',
337- ... },
338- ... 'section 2': {
339- ... 'section 2 sub 1': {
340- ... 'fish': '3',
341- ... },
342- ... 'keys21': 'val1',
343- ... 'keys22': 'val2',
344- ... 'keys23': 'val3',
345- ... },
346- ... }
347- 1
348- >>> t = '''
349- ... 'a' = b # !"$%^&*(),::;'@~#= 33
350- ... "b" = b #= 6, 33
351- ... ''' .split('\\n')
352- >>> t2 = ConfigObj(t)
353- >>> assert t2 == {'a': 'b', 'b': 'b'}
354- >>> t2.inline_comments['b'] = ''
355- >>> del t2['a']
356- >>> assert t2.write() == ['','b = b', '']
357-
358- # Test ``list_values=False`` stuff
359- >>> c = '''
360- ... key1 = no quotes
361- ... key2 = 'single quotes'
362- ... key3 = "double quotes"
363- ... key4 = "list", 'with', several, "quotes"
364- ... '''
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"'
369- ... }
370- 1
371- >>> cfg = ConfigObj(list_values=False)
372- >>> cfg['key1'] = 'Multiline\\nValue'
373- >>> cfg['key2'] = '''"Value" with 'quotes' !'''
374- >>> cfg.write()
375- ["key1 = '''Multiline\\nValue'''", 'key2 = "Value" with \\'quotes\\' !']
376- >>> cfg.list_values = True
377- >>> cfg.write() == ["key1 = '''Multiline\\nValue'''",
378- ... 'key2 = \\'\\'\\'"Value" with \\'quotes\\' !\\'\\'\\'']
379- 1
380-
381- Test flatten_errors:
382-
383- >>> config = '''
384- ... test1=40
385- ... test2=hello
386- ... test3=3
387- ... test4=5.0
388- ... [section]
389- ... test1=40
390- ... test2=hello
391- ... test3=3
392- ... test4=5.0
393- ... [[sub section]]
394- ... test1=40
395- ... test2=hello
396- ... test3=3
397- ... test4=5.0
398- ... '''.split('\\n')
399- >>> configspec = '''
400- ... test1= integer(30,50)
401- ... test2= string
402- ... test3=integer
403- ... test4=float(6.0)
404- ... [section]
405- ... test1=integer(30,50)
406- ... test2=string
407- ... test3=integer
408- ... test4=float(6.0)
409- ... [[sub section]]
410- ... test1=integer(30,50)
411- ... test2=string
412- ... test3=integer
413- ... test4=float(6.0)
414- ... '''.split('\\n')
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)]
420- True
421- >>> res = c1.validate(val, preserve_errors=True)
422- >>> check = flatten_errors(c1, res)
423- >>> check[0][:2]
424- ([], 'test4')
425- >>> check[1][:2]
426- (['section', 'sub section'], 'test4')
427- >>> check[2][:2]
428- (['section'], 'test4')
429- >>> for entry in check:
430- ... isinstance(entry[2], VdtValueTooSmallError)
431- ... print str(entry[2])
432- True
433- the value "5.0" is too small.
434- True
435- the value "5.0" is too small.
436- True
437- the value "5.0" is too small.
438-
439- Test unicode handling, BOM, write witha file like object and line endings :
440- >>> u_base = '''
441- ... # initial comment
442- ... # inital comment 2
443- ...
444- ... test1 = some value
445- ... # comment
446- ... test2 = another value # inline comment
447- ... # section comment
448- ... [section] # inline comment
449- ... test = test # another inline comment
450- ... test2 = test2
451- ...
452- ... # final comment
453- ... # final comment2
454- ... '''
455- >>> u = u_base.encode('utf_8').splitlines(True)
456- >>> u[0] = BOM_UTF8 + u[0]
457- >>> uc = ConfigObj(u)
458- >>> uc.encoding = None
459- >>> uc.BOM == True
460- 1
461- >>> uc == {'test1': 'some value', 'test2': 'another value',
462- ... 'section': {'test': 'test', 'test2': 'test2'}}
463- 1
464- >>> uc = ConfigObj(u, encoding='utf_8', default_encoding='latin-1')
465- >>> uc.BOM
466- 1
467- >>> isinstance(uc['test1'], unicode)
468- 1
469- >>> uc.encoding
470- 'utf_8'
471- >>> uc.newlines
472- '\\n'
473- >>> uc['latin1'] = "This costs lot's of "
474- >>> a_list = uc.write()
475- >>> len(a_list)
476- 15
477- >>> isinstance(a_list[0], str)
478- 1
479- >>> a_list[0].startswith(BOM_UTF8)
480- 1
481- >>> u = u_base.replace('\\n', '\\r\\n').encode('utf_8').splitlines(True)
482- >>> uc = ConfigObj(u)
483- >>> uc.newlines
484- '\\r\\n'
485- >>> uc.newlines = '\\r'
486- >>> file_like = StringIO()
487- >>> uc.write(file_like)
488- >>> file_like.seek(0)
489- >>> uc2 = ConfigObj(file_like)
490- >>> uc2 == uc
491- 1
492- >>> uc2.filename == None
493- 1
494- >>> uc2.newlines == '\\r'
495- 1
496-
497- Test validate in copy mode
498- >>> a = '''
499- ... # Initial Comment
500- ...
501- ... key1 = string(default=Hello) # comment 1
502- ...
503- ... # section comment
504- ... [section] # inline comment
505- ... # key1 comment
506- ... key1 = integer(default=6) # an integer value
507- ... # key2 comment
508- ... key2 = boolean(default=True) # a boolean
509- ...
510- ... # subsection comment
511- ... [[sub-section]] # inline comment
512- ... # another key1 comment
513- ... key1 = float(default=3.0) # a float'''.splitlines()
514- >>> b = ConfigObj(configspec=a)
515- >>> b.validate(val, copy=True)
516- 1
517- >>> b.write() == ['',
518- ... '# Initial Comment',
519- ... '',
520- ... 'key1 = Hello # comment 1',
521- ... '',
522- ... '# section comment',
523- ... '[section] # inline comment',
524- ... ' # key1 comment',
525- ... ' key1 = 6 # an integer value',
526- ... ' # key2 comment',
527- ... ' key2 = True # a boolean',
528- ... ' ',
529- ... ' # subsection comment',
530- ... ' [[sub-section]] # inline comment',
531- ... ' # another key1 comment',
532- ... ' key1 = 3.0 # a float']
533- 1
534-
535- Test Writing Empty Values
536- >>> a = '''
537- ... key1 =
538- ... key2 =# a comment'''
539- >>> b = ConfigObj(a.splitlines())
540- >>> b.write()
541- ['', 'key1 = ""', 'key2 = "" # a comment']
542- >>> b.write_empty_values = True
543- >>> b.write()
544- ['', 'key1 = ', 'key2 = # a comment']
545-
546- Test unrepr when reading
547- >>> a = '''
548- ... key1 = (1, 2, 3) # comment
549- ... key2 = True
550- ... key3 = 'a string'
551- ... key4 = [1, 2, 3, 'a mixed list']
552- ... '''.splitlines()
553- >>> b = ConfigObj(a, unrepr=True)
554- >>> b == {'key1': (1, 2, 3),
555- ... 'key2': True,
556- ... 'key3': 'a string',
557- ... 'key4': [1, 2, 3, 'a mixed list']}
558- 1
559-
560- Test unrepr when writing
561- >>> c = ConfigObj(b.write(), unrepr=True)
562- >>> c == b
563- 1
564-
565- Test unrepr with multiline values
566- >>> a = '''k = \"""{
567- ... 'k1': 3,
568- ... 'k2': 6.0}\"""
569- ... '''.splitlines()
570- >>> c = ConfigObj(a, unrepr=True)
571- >>> c == {'k': {'k1': 3, 'k2': 6.0}}
572- 1
573-
574- Test unrepr with a dictionary
575- >>> a = 'k = {"a": 1}'.splitlines()
576- >>> c = ConfigObj(a, unrepr=True)
577- >>> type(c['k']) == dict
578- 1
579-
580- >>> a = ConfigObj()
581- >>> a['a'] = 'fish'
582- >>> a.as_bool('a')
583- Traceback (most recent call last):
584- ValueError: Value "fish" is neither True nor False
585- >>> a['b'] = 'True'
586- >>> a.as_bool('b')
587- 1
588- >>> a['b'] = 'off'
589- >>> a.as_bool('b')
590- 0
591-
592- >>> a = ConfigObj()
593- >>> a['a'] = 'fish'
594- >>> try:
595- ... a.as_int('a') #doctest: +ELLIPSIS
596- ... except ValueError, e:
597- ... err_mess = str(e)
598- >>> err_mess.startswith('invalid literal for int()')
599- 1
600- >>> a['b'] = '1'
601- >>> a.as_int('b')
602- 1
603- >>> a['b'] = '3.2'
604- >>> try:
605- ... a.as_int('b') #doctest: +ELLIPSIS
606- ... except ValueError, e:
607- ... err_mess = str(e)
608- >>> err_mess.startswith('invalid literal for int()')
609- 1
610-
611- >>> a = ConfigObj()
612- >>> a['a'] = 'fish'
613- >>> a.as_float('a')
614- Traceback (most recent call last):
615- ValueError: invalid literal for float(): fish
616- >>> a['b'] = '1'
617- >>> a.as_float('b')
618- 1.0
619- >>> a['b'] = '3.2'
620- >>> a.as_float('b')
621- 3.2000000000000002
622-
623- Test # with unrepr
624- >>> a = '''
625- ... key1 = (1, 2, 3) # comment
626- ... key2 = True
627- ... key3 = 'a string'
628- ... key4 = [1, 2, 3, 'a mixed list#']
629- ... '''.splitlines()
630- >>> b = ConfigObj(a, unrepr=True)
631- >>> b == {'key1': (1, 2, 3),
632- ... 'key2': True,
633- ... 'key3': 'a string',
634- ... 'key4': [1, 2, 3, 'a mixed list#']}
635- 1
636- """
637-
638-
639-def _test_configobj():
640- """
641- Testing ConfigObj
642- Testing with duplicate keys and sections.
643-
644- >>> c = '''
645- ... [hello]
646- ... member = value
647- ... [hello again]
648- ... member = value
649- ... [ "hello" ]
650- ... member = value
651- ... '''
652- >>> ConfigObj(c.split('\\n'), raise_errors = True)
653- Traceback (most recent call last):
654- DuplicateError: Duplicate section name at line 6.
655-
656- >>> d = '''
657- ... [hello]
658- ... member = value
659- ... [hello again]
660- ... member1 = value
661- ... member2 = value
662- ... 'member1' = value
663- ... [ "and again" ]
664- ... member = value
665- ... '''
666- >>> ConfigObj(d.split('\\n'), raise_errors = True)
667- Traceback (most recent call last):
668- DuplicateError: Duplicate keyword name at line 7.
669-
670- Testing ConfigParser-style interpolation
671-
672- >>> c = ConfigObj()
673- >>> c['DEFAULT'] = {
674- ... 'b': 'goodbye',
675- ... 'userdir': 'c:\\\\home',
676- ... 'c': '%(d)s',
677- ... 'd': '%(c)s'
678- ... }
679- >>> c['section'] = {
680- ... 'a': '%(datadir)s\\\\some path\\\\file.py',
681- ... 'b': '%(userdir)s\\\\some path\\\\file.py',
682- ... 'c': 'Yo %(a)s',
683- ... 'd': '%(not_here)s',
684- ... 'e': '%(e)s',
685- ... }
686- >>> c['section']['DEFAULT'] = {
687- ... 'datadir': 'c:\\\\silly_test',
688- ... 'a': 'hello - %(b)s',
689- ... }
690- >>> c['section']['a'] == 'c:\\\\silly_test\\\\some path\\\\file.py'
691- 1
692- >>> c['section']['b'] == 'c:\\\\home\\\\some path\\\\file.py'
693- 1
694- >>> c['section']['c'] == 'Yo c:\\\\silly_test\\\\some path\\\\file.py'
695- 1
696-
697- Switching Interpolation Off
698-
699- >>> c.interpolation = False
700- >>> c['section']['a'] == '%(datadir)s\\\\some path\\\\file.py'
701- 1
702- >>> c['section']['b'] == '%(userdir)s\\\\some path\\\\file.py'
703- 1
704- >>> c['section']['c'] == 'Yo %(a)s'
705- 1
706-
707- Testing the interpolation errors.
708-
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".
716-
717- Testing Template-style interpolation
718-
719- >>> interp_cfg = '''
720- ... [DEFAULT]
721- ... keyword1 = value1
722- ... 'keyword 2' = 'value 2'
723- ... reference = ${keyword1}
724- ... foo = 123
725- ...
726- ... [ section ]
727- ... templatebare = $keyword1/foo
728- ... bar = $$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}
736- ...
737- ... [[DEFAULT]]
738- ... baz = $foo
739- ...
740- ... [[ sub-section ]]
741- ... quux = '$baz + $bar + $foo'
742- ...
743- ... [[[ sub-sub-section ]]]
744- ... convoluted = "$bar + $baz + $quux + $bar"
745- ... '''
746- >>> c = ConfigObj(interp_cfg.split('\\n'), interpolation='Template')
747- >>> c['section']['templatebare']
748- 'value1/foo'
749- >>> c['section']['dollar']
750- '$300.00'
751- >>> c['section']['stophere']
752- '$notinterpolated'
753- >>> c['section']['with_braces']
754- 'value1s (plural)'
755- >>> c['section']['with_spaces']
756- 'value 2!!!'
757- >>> c['section']['with_several']
758- 'value1/value1/value1'
759- >>> c['section']['configparsersample']
760- '%(keyword 2)sconfig'
761- >>> c['section']['deep']
762- 'value1'
763- >>> c['section']['sub-section']['quux']
764- '123 + $foo + 123'
765- >>> c['section']['sub-section']['sub-sub-section']['convoluted']
766- '$foo + 123 + 123 + $foo + 123 + $foo'
767-
768- Testing our quoting.
769-
770- >>> i._quote('\"""\'\'\'')
771- Traceback (most recent call last):
772- SyntaxError: EOF while scanning triple-quoted string
773- >>> try:
774- ... i._quote('\\n', multiline=False)
775- ... except ConfigObjError, e:
776- ... e.msg
777- 'Value "\\n" cannot be safely quoted.'
778- >>> k._quote(' "\' ', multiline=False)
779- Traceback (most recent call last):
780- SyntaxError: EOL while scanning single-quoted string
781-
782- Testing with "stringify" off.
783- >>> c.stringify = False
784- >>> c['test'] = 1
785- Traceback (most recent call last):
786- TypeError: Value is not a string "1".
787-
788- Testing Empty values.
789- >>> cfg_with_empty = '''
790- ... k =
791- ... k2 =# comment test
792- ... val = test
793- ... val2 = ,
794- ... val3 = 1,
795- ... val4 = 1, 2
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']}
800- 1
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,'}
804- 1
805-
806- Testing list values.
807- >>> testconfig3 = '''
808- ... a = ,
809- ... b = test,
810- ... c = test1, test2 , test3
811- ... d = test1, test2, test3,
812- ... '''
813- >>> d = ConfigObj(testconfig3.split('\\n'), raise_errors=True)
814- >>> d['a'] == []
815- 1
816- >>> d['b'] == ['test']
817- 1
818- >>> d['c'] == ['test1', 'test2', 'test3']
819- 1
820- >>> d['d'] == ['test1', 'test2', 'test3']
821- 1
822-
823- Testing with list values off.
824-
825- >>> e = ConfigObj(
826- ... testconfig3.split('\\n'),
827- ... raise_errors=True,
828- ... list_values=False)
829- >>> e['a'] == ','
830- 1
831- >>> e['b'] == 'test,'
832- 1
833- >>> e['c'] == 'test1, test2 , test3'
834- 1
835- >>> e['d'] == 'test1, test2, test3,'
836- 1
837-
838- Testing creating from a dictionary.
839-
840- >>> f = {
841- ... 'key1': 'val1',
842- ... 'key2': 'val2',
843- ... 'section 1': {
844- ... 'key1': 'val1',
845- ... 'key2': 'val2',
846- ... 'section 1b': {
847- ... 'key1': 'val1',
848- ... 'key2': 'val2',
849- ... },
850- ... },
851- ... 'section 2': {
852- ... 'key1': 'val1',
853- ... 'key2': 'val2',
854- ... 'section 2b': {
855- ... 'key1': 'val1',
856- ... 'key2': 'val2',
857- ... },
858- ... },
859- ... 'key3': 'val3',
860- ... }
861- >>> g = ConfigObj(f)
862- >>> f == g
863- 1
864-
865- Testing we correctly detect badly built list values (4 of them).
866-
867- >>> testconfig4 = '''
868- ... config = 3,4,,
869- ... test = 3,,4
870- ... fish = ,,
871- ... dummy = ,,hello, goodbye
872- ... '''
873- >>> try:
874- ... ConfigObj(testconfig4.split('\\n'))
875- ... except ConfigObjError, e:
876- ... len(e.errors)
877- 4
878-
879- Testing we correctly detect badly quoted values (4 of them).
880-
881- >>> testconfig5 = '''
882- ... config = "hello # comment
883- ... test = 'goodbye
884- ... fish = 'goodbye # comment
885- ... dummy = "hello again
886- ... '''
887- >>> try:
888- ... ConfigObj(testconfig5.split('\\n'))
889- ... except ConfigObjError, e:
890- ... len(e.errors)
891- 4
892-
893- Test Multiline Comments
894- >>> i == {
895- ... 'name4': ' another single line value ',
896- ... 'multi section': {
897- ... 'name4': '\\n Well, this is a\\n multiline '
898- ... 'value\\n ',
899- ... 'name2': '\\n Well, this is a\\n multiline '
900- ... 'value\\n ',
901- ... 'name3': '\\n Well, this is a\\n multiline '
902- ... 'value\\n ',
903- ... 'name1': '\\n Well, this is a\\n multiline '
904- ... 'value\\n ',
905- ... },
906- ... 'name2': ' another single line value ',
907- ... 'name3': ' a single line value ',
908- ... 'name1': ' a single line value ',
909- ... }
910- 1
911-
912- >>> filename = a.filename
913- >>> a.filename = None
914- >>> values = a.write()
915- >>> index = 0
916- >>> while index < 23:
917- ... index += 1
918- ... line = values[index-1]
919- ... assert line.endswith('# comment ' + str(index))
920- >>> a.filename = filename
921-
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', '', '#']
928- >>> nc.final_comment
929- ['', '#', '# Final Comment']
930- >>> nc.initial_comment == start_comment
931- 1
932- >>> nc.final_comment == end_comment
933- 1
934-
935- Test the _handle_comment method
936-
937- >>> c = ConfigObj()
938- >>> c['foo'] = 'bar'
939- >>> c.inline_comments['foo'] = 'Nice bar'
940- >>> c.write()
941- ['foo = bar # Nice bar']
942-
943- tekNico: FIXME: use StringIO instead of real files
944-
945- >>> filename = a.filename
946- >>> a.filename = 'test.ini'
947- >>> a.write()
948- >>> a.filename = filename
949- >>> a == ConfigObj('test.ini', raise_errors=True)
950- 1
951- >>> os.remove('test.ini')
952- >>> b.filename = 'test.ini'
953- >>> b.write()
954- >>> b == ConfigObj('test.ini', raise_errors=True)
955- 1
956- >>> os.remove('test.ini')
957- >>> i.filename = 'test.ini'
958- >>> i.write()
959- >>> i == ConfigObj('test.ini', raise_errors=True)
960- 1
961- >>> os.remove('test.ini')
962- >>> a = ConfigObj()
963- >>> a['DEFAULT'] = {'a' : 'fish'}
964- >>> a['a'] = '%(a)s'
965- >>> a.write()
966- ['a = %(a)s', '[DEFAULT]', 'a = fish']
967-
968- Test indentation handling
969-
970- >>> ConfigObj({'sect': {'sect': {'foo': 'bar'}}}).write()
971- ['[sect]', ' [[sect]]', ' foo = bar']
972- >>> cfg = ['[sect]', '[[sect]]', 'foo = bar']
973- >>> ConfigObj(cfg).write() == cfg
974- 1
975- >>> cfg = ['[sect]', ' [[sect]]', ' foo = bar']
976- >>> ConfigObj(cfg).write() == cfg
977- 1
978- >>> cfg = ['[sect]', ' [[sect]]', ' foo = bar']
979- >>> ConfigObj(cfg).write() == cfg
980- 1
981- >>> ConfigObj(oneTabCfg).write() == oneTabCfg
982- 1
983- >>> ConfigObj(twoTabsCfg).write() == twoTabsCfg
984- 1
985- >>> ConfigObj(tabsAndSpacesCfg).write() == tabsAndSpacesCfg
986- 1
987- >>> ConfigObj(cfg, indent_type=chr(9)).write() == oneTabCfg
988- 1
989- >>> ConfigObj(oneTabCfg, indent_type=' ').write() == cfg
990- 1
991- """
992-
993-
994-def _test_validate():
995- """
996- >>> config = '''
997- ... test1=40
998- ... test2=hello
999- ... test3=3
1000- ... test4=5.0
1001- ... [section]
1002- ... test1=40
1003- ... test2=hello
1004- ... test3=3
1005- ... test4=5.0
1006- ... [[sub section]]
1007- ... test1=40
1008- ... test2=hello
1009- ... test3=3
1010- ... test4=5.0
1011- ... '''.split('\\n')
1012- >>> configspec = '''
1013- ... test1= integer(30,50)
1014- ... test2= string
1015- ... test3=integer
1016- ... test4=float(6.0)
1017- ... [section ]
1018- ... test1=integer(30,50)
1019- ... test2=string
1020- ... test3=integer
1021- ... test4=float(6.0)
1022- ... [[sub section]]
1023- ... test1=integer(30,50)
1024- ... test2=string
1025- ... test3=integer
1026- ... test4=float(6.0)
1027- ... '''.split('\\n')
1028- >>> val = Validator()
1029- >>> c1 = ConfigObj(config, configspec=configspec)
1030- >>> test = c1.validate(val)
1031- >>> test == {
1032- ... 'test1': True,
1033- ... 'test2': True,
1034- ... 'test3': True,
1035- ... 'test4': False,
1036- ... 'section': {
1037- ... 'test1': True,
1038- ... 'test2': True,
1039- ... 'test3': True,
1040- ... 'test4': False,
1041- ... 'sub section': {
1042- ... 'test1': True,
1043- ... 'test2': True,
1044- ... 'test3': True,
1045- ... 'test4': False,
1046- ... },
1047- ... },
1048- ... }
1049- 1
1050- >>> val.check(c1.configspec['test4'], c1['test4'])
1051- Traceback (most recent call last):
1052- VdtValueTooSmallError: the value "5.0" is too small.
1053-
1054- >>> val_test_config = '''
1055- ... key = 0
1056- ... key2 = 1.1
1057- ... [section]
1058- ... key = some text
1059- ... key2 = 1.1, 3.0, 17, 6.8
1060- ... [[sub-section]]
1061- ... key = option1
1062- ... key2 = True'''.split('\\n')
1063- >>> val_test_configspec = '''
1064- ... key = integer
1065- ... key2 = float
1066- ... [section]
1067- ... key = string
1068- ... key2 = float_list(4)
1069- ... [[sub-section]]
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)
1074- 1
1075- >>> val_test['key'] = 'text not a digit'
1076- >>> val_res = val_test.validate(val)
1077- >>> val_res == {'key2': True, 'section': True, 'key': False}
1078- 1
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)
1084- ... [section ]
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)
1089- ... [[sub section]]
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)
1096- >>> default_test
1097- ConfigObj({'test1': '30', 'section': {'sub section': {}}})
1098- >>> default_test.defaults
1099- []
1100- >>> default_test.default_values
1101- {}
1102- >>> default_test.validate(val)
1103- 1
1104- >>> default_test == {
1105- ... 'test1': 30,
1106- ... 'test2': 'hello',
1107- ... 'test3': 3,
1108- ... 'test4': 6.0,
1109- ... 'section': {
1110- ... 'test1': 40,
1111- ... 'test2': 'hello',
1112- ... 'test3': 3,
1113- ... 'test4': 6.0,
1114- ... 'sub section': {
1115- ... 'test1': 40,
1116- ... 'test3': 3,
1117- ... 'test2': 'hello',
1118- ... 'test4': 6.0,
1119- ... },
1120- ... },
1121- ... }
1122- 1
1123- >>> default_test.defaults
1124- ['test2', 'test3', 'test4']
1125- >>> default_test.default_values == {'test1': 40, 'test2': 'hello',
1126- ... 'test3': 3, 'test4': 6.0}
1127- 1
1128- >>> default_test.restore_default('test1')
1129- 40
1130- >>> default_test['test1']
1131- 40
1132- >>> 'test1' in default_test.defaults
1133- 1
1134- >>> def change(section, key):
1135- ... section[key] = 3
1136- >>> _ = default_test.walk(change)
1137- >>> default_test['section']['sub section']['test4']
1138- 3
1139- >>> default_test.restore_defaults()
1140- >>> default_test == {
1141- ... 'test1': 40,
1142- ... 'test2': "hello",
1143- ... 'test3': 3,
1144- ... 'test4': 6.0,
1145- ... 'section': {
1146- ... 'test1': 40,
1147- ... 'test2': "hello",
1148- ... 'test3': 3,
1149- ... 'test4': 6.0,
1150- ... 'sub section': {
1151- ... 'test1': 40,
1152- ... 'test2': "hello",
1153- ... 'test3': 3,
1154- ... 'test4': 6.0
1155- ... }}}
1156- 1
1157-
1158- Now testing with repeated sections : BIG TEST
1159-
1160- >>> repeated_1 = '''
1161- ... [dogs]
1162- ... [[__many__]] # spec for a dog
1163- ... fleas = boolean(default=True)
1164- ... tail = option(long, short, default=long)
1165- ... name = string(default=rover)
1166- ... [[[__many__]]] # spec for a puppy
1167- ... name = string(default="son of rover")
1168- ... age = float(default=0.0)
1169- ... [cats]
1170- ... [[__many__]] # spec for a cat
1171- ... fleas = boolean(default=True)
1172- ... tail = option(long, short, default=short)
1173- ... name = string(default=pussy)
1174- ... [[[__many__]]] # spec for a kitten
1175- ... name = string(default="son of pussy")
1176- ... age = float(default=0.0)
1177- ... '''.split('\\n')
1178- >>> repeated_2 = '''
1179- ... [dogs]
1180- ...
1181- ... # blank dogs with puppies
1182- ... # should be filled in by the configspec
1183- ... [[dog1]]
1184- ... [[[puppy1]]]
1185- ... [[[puppy2]]]
1186- ... [[[puppy3]]]
1187- ... [[dog2]]
1188- ... [[[puppy1]]]
1189- ... [[[puppy2]]]
1190- ... [[[puppy3]]]
1191- ... [[dog3]]
1192- ... [[[puppy1]]]
1193- ... [[[puppy2]]]
1194- ... [[[puppy3]]]
1195- ... [cats]
1196- ...
1197- ... # blank cats with kittens
1198- ... # should be filled in by the configspec
1199- ... [[cat1]]
1200- ... [[[kitten1]]]
1201- ... [[[kitten2]]]
1202- ... [[[kitten3]]]
1203- ... [[cat2]]
1204- ... [[[kitten1]]]
1205- ... [[[kitten2]]]
1206- ... [[[kitten3]]]
1207- ... [[cat3]]
1208- ... [[[kitten1]]]
1209- ... [[[kitten2]]]
1210- ... [[[kitten3]]]
1211- ... '''.split('\\n')
1212- >>> repeated_3 = '''
1213- ... [dogs]
1214- ...
1215- ... [[dog1]]
1216- ... [[dog2]]
1217- ... [[dog3]]
1218- ... [cats]
1219- ...
1220- ... [[cat1]]
1221- ... [[cat2]]
1222- ... [[cat3]]
1223- ... '''.split('\\n')
1224- >>> repeated_4 = '''
1225- ... [__many__]
1226- ...
1227- ... name = string(default=Michael)
1228- ... age = float(default=0.0)
1229- ... sex = option(m, f, default=m)
1230- ... '''.split('\\n')
1231- >>> repeated_5 = '''
1232- ... [cats]
1233- ... [[__many__]]
1234- ... fleas = boolean(default=True)
1235- ... tail = option(long, short, default=short)
1236- ... name = string(default=pussy)
1237- ... [[[description]]]
1238- ... height = float(default=3.3)
1239- ... weight = float(default=6)
1240- ... [[[[coat]]]]
1241- ... fur = option(black, grey, brown, "tortoise shell", default=black)
1242- ... condition = integer(0,10, default=5)
1243- ... '''.split('\\n')
1244- >>> val= Validator()
1245- >>> repeater = ConfigObj(repeated_2, configspec=repeated_1)
1246- >>> repeater.validate(val)
1247- 1
1248- >>> repeater == {
1249- ... 'dogs': {
1250- ... 'dog1': {
1251- ... 'fleas': True,
1252- ... 'tail': 'long',
1253- ... 'name': 'rover',
1254- ... 'puppy1': {'name': 'son of rover', 'age': 0.0},
1255- ... 'puppy2': {'name': 'son of rover', 'age': 0.0},
1256- ... 'puppy3': {'name': 'son of rover', 'age': 0.0},
1257- ... },
1258- ... 'dog2': {
1259- ... 'fleas': True,
1260- ... 'tail': 'long',
1261- ... 'name': 'rover',
1262- ... 'puppy1': {'name': 'son of rover', 'age': 0.0},
1263- ... 'puppy2': {'name': 'son of rover', 'age': 0.0},
1264- ... 'puppy3': {'name': 'son of rover', 'age': 0.0},
1265- ... },
1266- ... 'dog3': {
1267- ... 'fleas': True,
1268- ... 'tail': 'long',
1269- ... 'name': 'rover',
1270- ... 'puppy1': {'name': 'son of rover', 'age': 0.0},
1271- ... 'puppy2': {'name': 'son of rover', 'age': 0.0},
1272- ... 'puppy3': {'name': 'son of rover', 'age': 0.0},
1273- ... },
1274- ... },
1275- ... 'cats': {
1276- ... 'cat1': {
1277- ... 'fleas': True,
1278- ... 'tail': 'short',
1279- ... 'name': 'pussy',
1280- ... 'kitten1': {'name': 'son of pussy', 'age': 0.0},
1281- ... 'kitten2': {'name': 'son of pussy', 'age': 0.0},
1282- ... 'kitten3': {'name': 'son of pussy', 'age': 0.0},
1283- ... },
1284- ... 'cat2': {
1285- ... 'fleas': True,
1286- ... 'tail': 'short',
1287- ... 'name': 'pussy',
1288- ... 'kitten1': {'name': 'son of pussy', 'age': 0.0},
1289- ... 'kitten2': {'name': 'son of pussy', 'age': 0.0},
1290- ... 'kitten3': {'name': 'son of pussy', 'age': 0.0},
1291- ... },
1292- ... 'cat3': {
1293- ... 'fleas': True,
1294- ... 'tail': 'short',
1295- ... 'name': 'pussy',
1296- ... 'kitten1': {'name': 'son of pussy', 'age': 0.0},
1297- ... 'kitten2': {'name': 'son of pussy', 'age': 0.0},
1298- ... 'kitten3': {'name': 'son of pussy', 'age': 0.0},
1299- ... },
1300- ... },
1301- ... }
1302- 1
1303- >>> repeater = ConfigObj(repeated_3, configspec=repeated_1)
1304- >>> repeater.validate(val)
1305- 1
1306- >>> repeater == {
1307- ... 'cats': {
1308- ... 'cat1': {'fleas': True, 'tail': 'short', 'name': 'pussy'},
1309- ... 'cat2': {'fleas': True, 'tail': 'short', 'name': 'pussy'},
1310- ... 'cat3': {'fleas': True, 'tail': 'short', 'name': 'pussy'},
1311- ... },
1312- ... 'dogs': {
1313- ... 'dog1': {'fleas': True, 'tail': 'long', 'name': 'rover'},
1314- ... 'dog2': {'fleas': True, 'tail': 'long', 'name': 'rover'},
1315- ... 'dog3': {'fleas': True, 'tail': 'long', 'name': 'rover'},
1316- ... },
1317- ... }
1318- 1
1319- >>> repeater = ConfigObj(configspec=repeated_4)
1320- >>> repeater['Michael'] = {}
1321- >>> repeater.validate(val)
1322- 1
1323- >>> repeater == {
1324- ... 'Michael': {'age': 0.0, 'name': 'Michael', 'sex': 'm'},
1325- ... }
1326- 1
1327- >>> repeater = ConfigObj(repeated_3, configspec=repeated_5)
1328- >>> repeater == {
1329- ... 'dogs': {'dog1': {}, 'dog2': {}, 'dog3': {}},
1330- ... 'cats': {'cat1': {}, 'cat2': {}, 'cat3': {}},
1331- ... }
1332- 1
1333- >>> repeater.validate(val)
1334- 1
1335- >>> repeater == {
1336- ... 'dogs': {'dog1': {}, 'dog2': {}, 'dog3': {}},
1337- ... 'cats': {
1338- ... 'cat1': {
1339- ... 'fleas': True,
1340- ... 'tail': 'short',
1341- ... 'name': 'pussy',
1342- ... 'description': {
1343- ... 'weight': 6.0,
1344- ... 'height': 3.2999999999999998,
1345- ... 'coat': {'fur': 'black', 'condition': 5},
1346- ... },
1347- ... },
1348- ... 'cat2': {
1349- ... 'fleas': True,
1350- ... 'tail': 'short',
1351- ... 'name': 'pussy',
1352- ... 'description': {
1353- ... 'weight': 6.0,
1354- ... 'height': 3.2999999999999998,
1355- ... 'coat': {'fur': 'black', 'condition': 5},
1356- ... },
1357- ... },
1358- ... 'cat3': {
1359- ... 'fleas': True,
1360- ... 'tail': 'short',
1361- ... 'name': 'pussy',
1362- ... 'description': {
1363- ... 'weight': 6.0,
1364- ... 'height': 3.2999999999999998,
1365- ... 'coat': {'fur': 'black', 'condition': 5},
1366- ... },
1367- ... },
1368- ... },
1369- ... }
1370- 1
1371-
1372- Test that interpolation is preserved for validated string values.
1373- Also check that interpolation works in configspecs.
1374- >>> t = ConfigObj()
1375- >>> t['DEFAULT'] = {}
1376- >>> t['DEFAULT']['def_test'] = 'a'
1377- >>> t['test'] = '%(def_test)s'
1378- >>> t['test']
1379- 'a'
1380- >>> v = Validator()
1381- >>> t.configspec = {'test': 'string'}
1382- >>> t.validate(v)
1383- 1
1384- >>> t.interpolation = False
1385- >>> t
1386- ConfigObj({'test': '%(def_test)s', 'DEFAULT': {'def_test': 'a'}})
1387- >>> specs = [
1388- ... 'interpolated string = string(default="fuzzy-%(man)s")',
1389- ... '[DEFAULT]',
1390- ... 'man = wuzzy',
1391- ... ]
1392- >>> c = ConfigObj(configspec=specs)
1393- >>> c.validate(v)
1394- 1
1395- >>> c['interpolated string']
1396- 'fuzzy-wuzzy'
1397-
1398- Test SimpleVal
1399- >>> val = SimpleVal()
1400- >>> config = '''
1401- ... test1=40
1402- ... test2=hello
1403- ... test3=3
1404- ... test4=5.0
1405- ... [section]
1406- ... test1=40
1407- ... test2=hello
1408- ... test3=3
1409- ... test4=5.0
1410- ... [[sub section]]
1411- ... test1=40
1412- ... test2=hello
1413- ... test3=3
1414- ... test4=5.0
1415- ... '''.split('\\n')
1416- >>> configspec = '''
1417- ... test1=''
1418- ... test2=''
1419- ... test3=''
1420- ... test4=''
1421- ... [section]
1422- ... test1=''
1423- ... test2=''
1424- ... test3=''
1425- ... test4=''
1426- ... [[sub section]]
1427- ... test1=''
1428- ... test2=''
1429- ... test3=''
1430- ... test4=''
1431- ... '''.split('\\n')
1432- >>> o = ConfigObj(config, configspec=configspec)
1433- >>> o.validate(val)
1434- 1
1435- >>> o = ConfigObj(configspec=configspec)
1436- >>> o.validate(val)
1437- 0
1438-
1439- Test Flatten Errors
1440- >>> vtor = Validator()
1441- >>> my_ini = '''
1442- ... option1 = True
1443- ... [section1]
1444- ... option1 = True
1445- ... [section2]
1446- ... another_option = Probably
1447- ... [section3]
1448- ... another_option = True
1449- ... [[section3b]]
1450- ... value = 3
1451- ... value2 = a
1452- ... value3 = 11
1453- ... '''
1454- >>> my_cfg = '''
1455- ... option1 = boolean()
1456- ... option2 = boolean()
1457- ... option3 = boolean(default=Bad_value)
1458- ... [section1]
1459- ... option1 = boolean()
1460- ... option2 = boolean()
1461- ... option3 = boolean(default=Bad_value)
1462- ... [section2]
1463- ... another_option = boolean()
1464- ... [section3]
1465- ... another_option = boolean()
1466- ... [[section3b]]
1467- ... value = integer
1468- ... value2 = integer
1469- ... value3 = integer(0, 10)
1470- ... [[[section3b-sub]]]
1471- ... value = string
1472- ... [section4]
1473- ... another_option = boolean()
1474- ... '''
1475- >>> cs = my_cfg.split('\\n')
1476- >>> ini = my_ini.split('\\n')
1477- >>> cfg = ConfigObj(ini, configspec=cs)
1478- >>> res = cfg.validate(vtor, preserve_errors=True)
1479- >>> errors = []
1480- >>> for entry in flatten_errors(cfg, res):
1481- ... section_list, key, error = entry
1482- ... section_list.insert(0, '[root]')
1483- ... if key is not None:
1484- ... section_list.append(key)
1485- ... else:
1486- ... section_list.append('[missing]')
1487- ... section_string = ', '.join(section_list)
1488- ... errors.append((section_string, ' = ', error))
1489- >>> errors.sort()
1490- >>> for entry in errors:
1491- ... print entry[0], entry[1], (entry[2] or 0)
1492- [root], option2 = 0
1493- [root], option3 = the value "Bad_value" is of the wrong type.
1494- [root], section1, option2 = 0
1495- [root], section1, option3 = the value "Bad_value" is of the wrong type.
1496- [root], section2, another_option = the value "Probably" is of the wrong type.
1497- [root], section3, section3b, section3b-sub, [missing] = 0
1498- [root], section3, section3b, value2 = the value "a" is of the wrong type.
1499- [root], section3, section3b, value3 = the value "11" is too big.
1500- [root], section4, [missing] = 0
1501- """
1502-
1503-
1504-def _test_errors():
1505- """
1506- Test the error messages and objects, in normal mode and unrepr mode.
1507- >>> bad_syntax = '''
1508- ... key = "value"
1509- ... key2 = "value
1510- ... '''.splitlines()
1511- >>> c = ConfigObj(bad_syntax)
1512- Traceback (most recent call last):
1513- ParseError: Parse error in value at line 3.
1514- >>> c = ConfigObj(bad_syntax, raise_errors=True)
1515- Traceback (most recent call last):
1516- ParseError: Parse error in value at line 3.
1517- >>> c = ConfigObj(bad_syntax, raise_errors=True, unrepr=True)
1518- Traceback (most recent call last):
1519- UnreprError: Parse error in value at line 3.
1520- >>> try:
1521- ... c = ConfigObj(bad_syntax)
1522- ... except Exception, e:
1523- ... pass
1524- >>> assert(isinstance(e, ConfigObjError))
1525- >>> print e
1526- Parse error in value at line 3.
1527- >>> len(e.errors) == 1
1528- 1
1529- >>> try:
1530- ... c = ConfigObj(bad_syntax, unrepr=True)
1531- ... except Exception, e:
1532- ... pass
1533- >>> assert(isinstance(e, ConfigObjError))
1534- >>> print e
1535- Parse error in value at line 3.
1536- >>> len(e.errors) == 1
1537- 1
1538- >>> the_error = e.errors[0]
1539- >>> assert(isinstance(the_error, UnreprError))
1540-
1541- >>> multiple_bad_syntax = '''
1542- ... key = "value"
1543- ... key2 = "value
1544- ... key3 = "value2
1545- ... '''.splitlines()
1546- >>> try:
1547- ... c = ConfigObj(multiple_bad_syntax)
1548- ... except ConfigObjError, e:
1549- ... str(e)
1550- 'Parsing failed with several errors.\\nFirst error at line 3.'
1551- >>> c = ConfigObj(multiple_bad_syntax, raise_errors=True)
1552- Traceback (most recent call last):
1553- ParseError: Parse error in value at line 3.
1554- >>> c = ConfigObj(multiple_bad_syntax, raise_errors=True, unrepr=True)
1555- Traceback (most recent call last):
1556- UnreprError: Parse error in value at line 3.
1557- >>> try:
1558- ... c = ConfigObj(multiple_bad_syntax)
1559- ... except Exception, e:
1560- ... pass
1561- >>> assert(isinstance(e, ConfigObjError))
1562- >>> print e
1563- Parsing failed with several errors.
1564- First error at line 3.
1565- >>> len(e.errors) == 2
1566- 1
1567- >>> try:
1568- ... c = ConfigObj(multiple_bad_syntax, unrepr=True)
1569- ... except Exception, e:
1570- ... pass
1571- >>> assert(isinstance(e, ConfigObjError))
1572- >>> print e
1573- Parsing failed with several errors.
1574- First error at line 3.
1575- >>> len(e.errors) == 2
1576- 1
1577- >>> the_error = e.errors[1]
1578- >>> assert(isinstance(the_error, UnreprError))
1579-
1580- >>> unknown_name = '''
1581- ... key = "value"
1582- ... key2 = value
1583- ... '''.splitlines()
1584- >>> c = ConfigObj(unknown_name)
1585- >>> c = ConfigObj(unknown_name, unrepr=True)
1586- Traceback (most recent call last):
1587- UnreprError: Unknown name or type in value at line 3.
1588- >>> c = ConfigObj(unknown_name, raise_errors=True, unrepr=True)
1589- Traceback (most recent call last):
1590- UnreprError: Unknown name or type in value at line 3.
1591- """
1592-
1593-
1594-def _test_unrepr_comments():
1595- """
1596- >>> config = '''
1597- ... # initial comments
1598- ... # with two lines
1599- ... key = "value"
1600- ... # section comment
1601- ... [section] # inline section comment
1602- ... # key comment
1603- ... key = "value"
1604- ... # final comment
1605- ... # with two lines
1606- ... '''.splitlines()
1607- >>> c = ConfigObj(config, unrepr=True)
1608- >>> c == { 'key': 'value',
1609- ... 'section': { 'key': 'value'}}
1610- 1
1611- >>> c.initial_comment == ['', '# initial comments', '# with two lines']
1612- 1
1613- >>> c.comments == {'section': ['# section comment'], 'key': []}
1614- 1
1615- >>> c.inline_comments == {'section': '# inline section comment', 'key': ''}
1616- 1
1617- >>> c['section'].comments == { 'key': ['# key comment']}
1618- 1
1619- >>> c.final_comment == ['# final comment', '# with two lines']
1620- 1
1621- """
1622-
1623-
1624-def _test_newline_terminated():
1625- """
1626- >>> c = ConfigObj()
1627- >>> c.newlines = '\\n'
1628- >>> c['a'] = 'b'
1629- >>> collector = StringIO()
1630- >>> c.write(collector)
1631- >>> collector.getvalue()
1632- 'a = b\\n'
1633- """
1634-
1635-
1636-def _test_hash_escaping():
1637- """
1638- >>> c = ConfigObj()
1639- >>> c.newlines = '\\n'
1640- >>> c['#a'] = 'b # something'
1641- >>> collector = StringIO()
1642- >>> c.write(collector)
1643- >>> collector.getvalue()
1644- '"#a" = "b # something"\\n'
1645-
1646- >>> c = ConfigObj()
1647- >>> c.newlines = '\\n'
1648- >>> c['a'] = 'b # something', 'c # something'
1649- >>> collector = StringIO()
1650- >>> c.write(collector)
1651- >>> collector.getvalue()
1652- 'a = "b # something", "c # something"\\n'
1653- """
1654-
1655-
1656-def _test_lineendings():
1657- """
1658- NOTE: Need to use a real file because this code is only
1659- exercised when reading from the filesystem.
1660-
1661- >>> h = open('temp', 'wb')
1662- >>> h.write('\\r\\n')
1663- >>> h.close()
1664- >>> c = ConfigObj('temp')
1665- >>> c.newlines
1666- '\\r\\n'
1667- >>> h = open('temp', 'wb')
1668- >>> h.write('\\n')
1669- >>> h.close()
1670- >>> c = ConfigObj('temp')
1671- >>> c.newlines
1672- '\\n'
1673- >>> os.remove('temp')
1674- """
1675-
1676-
1677-def _test_validate_with_copy_and_many():
1678- """
1679- >>> spec = '''
1680- ... [section]
1681- ... [[__many__]]
1682- ... value = string(default='nothing')
1683- ... '''
1684- >>> config = '''
1685- ... [section]
1686- ... [[something]]
1687- ... '''
1688- >>> c = ConfigObj(StringIO(config), configspec=StringIO(spec))
1689- >>> v = Validator()
1690- >>> r = c.validate(v, copy=True)
1691- >>> c['section']['something']['value']
1692- 'nothing'
1693- """
1694-
1695-
1696-
1697-# TODO: Test BOM handling
1698-# TODO: Test error code for badly built multiline values
1699-# TODO: Test handling of StringIO
1700-# TODO: Test interpolation with writing
1701-
1702-
1703-if __name__ == '__main__':
1704- # run the code tests in doctest format
1705- #
1706- testconfig1 = """\
1707- key1= val # comment 1
1708- key2= val # comment 2
1709- # comment 3
1710- [lev1a] # comment 4
1711- key1= val # comment 5
1712- key2= val # comment 6
1713- # comment 7
1714- [lev1b] # comment 8
1715- key1= val # comment 9
1716- key2= val # comment 10
1717- # comment 11
1718- [[lev2ba]] # comment 12
1719- key1= val # comment 13
1720- # comment 14
1721- [[lev2bb]] # comment 15
1722- key1= val # comment 16
1723- # comment 17
1724- [lev1c] # comment 18
1725- # comment 19
1726- [[lev2c]] # comment 20
1727- # comment 21
1728- [[[lev3c]]] # comment 22
1729- key1 = val # comment 23"""
1730- #
1731- testconfig2 = """\
1732- key1 = 'val1'
1733- key2 = "val2"
1734- key3 = val3
1735- ["section 1"] # comment
1736- keys11 = val1
1737- keys12 = val2
1738- keys13 = val3
1739- [section 2]
1740- keys21 = val1
1741- keys22 = val2
1742- keys23 = val3
1743-
1744- [['section 2 sub 1']]
1745- fish = 3
1746- """
1747- #
1748- testconfig6 = '''
1749- name1 = """ a single line value """ # comment
1750- name2 = \''' another single line value \''' # comment
1751- name3 = """ a single line value """
1752- name4 = \''' another single line value \'''
1753- [ "multi section" ]
1754- name1 = """
1755- Well, this is a
1756- multiline value
1757- """
1758- name2 = \'''
1759- Well, this is a
1760- multiline value
1761- \'''
1762- name3 = """
1763- Well, this is a
1764- multiline value
1765- """ # a comment
1766- name4 = \'''
1767- Well, this is a
1768- multiline value
1769- \''' # I guess this is a comment too
1770- '''
1771- #
1772- # these cannot be put among the doctests, because the doctest module
1773- # does a string.expandtabs() on all of them, sigh
1774- oneTabCfg = ['[sect]', '\t[[sect]]', '\t\tfoo = bar']
1775- twoTabsCfg = ['[sect]', '\t\t[[sect]]', '\t\t\t\tfoo = bar']
1776- tabsAndSpacesCfg = ['[sect]', '\t \t [[sect]]', '\t \t \t \t foo = bar']
1777- #
1778- import doctest
1779- m = sys.modules.get('__main__')
1780- globs = m.__dict__.copy()
1781- a = ConfigObj(testconfig1.split('\n'), raise_errors=True)
1782- b = ConfigObj(testconfig2.split('\n'), raise_errors=True)
1783- i = ConfigObj(testconfig6.split('\n'), raise_errors=True)
1784- globs.update({'INTP_VER': INTP_VER, 'a': a, 'b': b, 'i': i,
1785- 'oneTabCfg': oneTabCfg, 'twoTabsCfg': twoTabsCfg,
1786- 'tabsAndSpacesCfg': tabsAndSpacesCfg})
1787- doctest.testmod(m, globs=globs)
1788-
1789-
1790-# Man alive I prefer unittest ;-)
\ No newline at end of file
--- branches/tl3_0/python-lib/configobj/validate.py (revision 39)
+++ branches/tl3_0/python-lib/configobj/validate.py (revision 40)
@@ -1,1414 +1,1450 @@
1-# validate.py
2-# A Validator object
3-# Copyright (C) 2005 Michael Foord, Mark Andrews, Nicola Larosa
4-# E-mail: fuzzyman AT voidspace DOT org DOT uk
5-# mark AT la-la DOT com
6-# nico AT tekNico DOT net
7-
8-# This software is licensed under the terms of the BSD license.
9-# http://www.voidspace.org.uk/python/license.shtml
10-# Basically you're free to copy, modify, distribute and relicense it,
11-# So long as you keep a copy of the license with it.
12-
13-# Scripts maintained at http://www.voidspace.org.uk/python/index.shtml
14-# For information about bugfixes, updates and support, please join the
15-# ConfigObj mailing list:
16-# http://lists.sourceforge.net/lists/listinfo/configobj-develop
17-# Comments, suggestions and bug reports welcome.
18-
19-"""
20- The Validator object is used to check that supplied values
21- conform to a specification.
22-
23- The value can be supplied as a string - e.g. from a config file.
24- In this case the check will also *convert* the value to
25- the required type. This allows you to add validation
26- as a transparent layer to access data stored as strings.
27- The validation checks that the data is correct *and*
28- converts it to the expected type.
29-
30- Some standard checks are provided for basic data types.
31- Additional checks are easy to write. They can be
32- provided when the ``Validator`` is instantiated or
33- added afterwards.
34-
35- The standard functions work with the following basic data types :
36-
37- * integers
38- * floats
39- * booleans
40- * strings
41- * ip_addr
42-
43- plus lists of these datatypes
44-
45- Adding additional checks is done through coding simple functions.
46-
47- The full set of standard checks are :
48-
49- * 'integer': matches integer values (including negative)
50- Takes optional 'min' and 'max' arguments : ::
51-
52- integer()
53- integer(3, 9) # any value from 3 to 9
54- integer(min=0) # any positive value
55- integer(max=9)
56-
57- * 'float': matches float values
58- Has the same parameters as the integer check.
59-
60- * 'boolean': matches boolean values - ``True`` or ``False``
61- Acceptable string values for True are :
62- true, on, yes, 1
63- Acceptable string values for False are :
64- false, off, no, 0
65-
66- Any other value raises an error.
67-
68- * 'ip_addr': matches an Internet Protocol address, v.4, represented
69- by a dotted-quad string, i.e. '1.2.3.4'.
70-
71- * 'string': matches any string.
72- Takes optional keyword args 'min' and 'max'
73- to specify min and max lengths of the string.
74-
75- * 'list': matches any list.
76- Takes optional keyword args 'min', and 'max' to specify min and
77- max sizes of the list. (Always returns a list.)
78-
79- * 'tuple': matches any tuple.
80- Takes optional keyword args 'min', and 'max' to specify min and
81- max sizes of the tuple. (Always returns a tuple.)
82-
83- * 'int_list': Matches a list of integers.
84- Takes the same arguments as list.
85-
86- * 'float_list': Matches a list of floats.
87- Takes the same arguments as list.
88-
89- * 'bool_list': Matches a list of boolean values.
90- Takes the same arguments as list.
91-
92- * 'ip_addr_list': Matches a list of IP addresses.
93- Takes the same arguments as list.
94-
95- * 'string_list': Matches a list of strings.
96- Takes the same arguments as list.
97-
98- * 'mixed_list': Matches a list with different types in
99- specific positions. List size must match
100- the number of arguments.
101-
102- Each position can be one of :
103- 'integer', 'float', 'ip_addr', 'string', 'boolean'
104-
105- So to specify a list with two strings followed
106- by two integers, you write the check as : ::
107-
108- mixed_list('string', 'string', 'integer', 'integer')
109-
110- * 'pass': This check matches everything ! It never fails
111- and the value is unchanged.
112-
113- It is also the default if no check is specified.
114-
115- * 'option': This check matches any from a list of options.
116- You specify this check with : ::
117-
118- option('option 1', 'option 2', 'option 3')
119-
120- You can supply a default value (returned if no value is supplied)
121- using the default keyword argument.
122-
123- You specify a list argument for default using a list constructor syntax in
124- the check : ::
125-
126- checkname(arg1, arg2, default=list('val 1', 'val 2', 'val 3'))
127-
128- A badly formatted set of arguments will raise a ``VdtParamError``.
129-"""
130-
131-__docformat__ = "restructuredtext en"
132-
133-__version__ = '0.3.2'
134-
135-__revision__ = '$Id: validate.py 123 2005-09-08 08:54:28Z fuzzyman $'
136-
137-__all__ = (
138- '__version__',
139- 'dottedQuadToNum',
140- 'numToDottedQuad',
141- 'ValidateError',
142- 'VdtUnknownCheckError',
143- 'VdtParamError',
144- 'VdtTypeError',
145- 'VdtValueError',
146- 'VdtValueTooSmallError',
147- 'VdtValueTooBigError',
148- 'VdtValueTooShortError',
149- 'VdtValueTooLongError',
150- 'VdtMissingValue',
151- 'Validator',
152- 'is_integer',
153- 'is_float',
154- 'is_boolean',
155- 'is_list',
156- 'is_tuple',
157- 'is_ip_addr',
158- 'is_string',
159- 'is_int_list',
160- 'is_bool_list',
161- 'is_float_list',
162- 'is_string_list',
163- 'is_ip_addr_list',
164- 'is_mixed_list',
165- 'is_option',
166- '__docformat__',
167-)
168-
169-
170-import sys
171-INTP_VER = sys.version_info[:2]
172-if INTP_VER < (2, 2):
173- raise RuntimeError("Python v.2.2 or later needed")
174-
175-import re
176-StringTypes = (str, unicode)
177-
178-
179-_list_arg = re.compile(r'''
180- (?:
181- ([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*list\(
182- (
183- (?:
184- \s*
185- (?:
186- (?:".*?")| # double quotes
187- (?:'.*?')| # single quotes
188- (?:[^'",\s\)][^,\)]*?) # unquoted
189- )
190- \s*,\s*
191- )*
192- (?:
193- (?:".*?")| # double quotes
194- (?:'.*?')| # single quotes
195- (?:[^'",\s\)][^,\)]*?) # unquoted
196- )? # last one
197- )
198- \)
199- )
200-''', re.VERBOSE) # two groups
201-
202-_list_members = re.compile(r'''
203- (
204- (?:".*?")| # double quotes
205- (?:'.*?')| # single quotes
206- (?:[^'",\s=][^,=]*?) # unquoted
207- )
208- (?:
209- (?:\s*,\s*)|(?:\s*$) # comma
210- )
211-''', re.VERBOSE) # one group
212-
213-_paramstring = r'''
214- (?:
215- (
216- (?:
217- [a-zA-Z_][a-zA-Z0-9_]*\s*=\s*list\(
218- (?:
219- \s*
220- (?:
221- (?:".*?")| # double quotes
222- (?:'.*?')| # single quotes
223- (?:[^'",\s\)][^,\)]*?) # unquoted
224- )
225- \s*,\s*
226- )*
227- (?:
228- (?:".*?")| # double quotes
229- (?:'.*?')| # single quotes
230- (?:[^'",\s\)][^,\)]*?) # unquoted
231- )? # last one
232- \)
233- )|
234- (?:
235- (?:".*?")| # double quotes
236- (?:'.*?')| # single quotes
237- (?:[^'",\s=][^,=]*?)| # unquoted
238- (?: # keyword argument
239- [a-zA-Z_][a-zA-Z0-9_]*\s*=\s*
240- (?:
241- (?:".*?")| # double quotes
242- (?:'.*?')| # single quotes
243- (?:[^'",\s=][^,=]*?) # unquoted
244- )
245- )
246- )
247- )
248- (?:
249- (?:\s*,\s*)|(?:\s*$) # comma
250- )
251- )
252- '''
253-
254-_matchstring = '^%s*' % _paramstring
255-
256-# Python pre 2.2.1 doesn't have bool
257-try:
258- bool
259-except NameError:
260- def bool(val):
261- """Simple boolean equivalent function. """
262- if val:
263- return 1
264- else:
265- return 0
266-
267-
268-def dottedQuadToNum(ip):
269- """
270- Convert decimal dotted quad string to long integer
271-
272- >>> dottedQuadToNum('1 ')
273- 1L
274- >>> dottedQuadToNum(' 1.2')
275- 16777218L
276- >>> dottedQuadToNum(' 1.2.3 ')
277- 16908291L
278- >>> dottedQuadToNum('1.2.3.4')
279- 16909060L
280- >>> dottedQuadToNum('1.2.3. 4')
281- Traceback (most recent call last):
282- ValueError: Not a good dotted-quad IP: 1.2.3. 4
283- >>> dottedQuadToNum('255.255.255.255')
284- 4294967295L
285- >>> dottedQuadToNum('255.255.255.256')
286- Traceback (most recent call last):
287- ValueError: Not a good dotted-quad IP: 255.255.255.256
288- """
289-
290- # import here to avoid it when ip_addr values are not used
291- import socket, struct
292-
293- try:
294- return struct.unpack('!L',
295- socket.inet_aton(ip.strip()))[0]
296- except socket.error:
297- # bug in inet_aton, corrected in Python 2.3
298- if ip.strip() == '255.255.255.255':
299- return 0xFFFFFFFFL
300- else:
301- raise ValueError('Not a good dotted-quad IP: %s' % ip)
302- return
303-
304-
305-def numToDottedQuad(num):
306- """
307- Convert long int to dotted quad string
308-
309- >>> numToDottedQuad(-1L)
310- Traceback (most recent call last):
311- ValueError: Not a good numeric IP: -1
312- >>> numToDottedQuad(1L)
313- '0.0.0.1'
314- >>> numToDottedQuad(16777218L)
315- '1.0.0.2'
316- >>> numToDottedQuad(16908291L)
317- '1.2.0.3'
318- >>> numToDottedQuad(16909060L)
319- '1.2.3.4'
320- >>> numToDottedQuad(4294967295L)
321- '255.255.255.255'
322- >>> numToDottedQuad(4294967296L)
323- Traceback (most recent call last):
324- ValueError: Not a good numeric IP: 4294967296
325- """
326-
327- # import here to avoid it when ip_addr values are not used
328- import socket, struct
329-
330- # no need to intercept here, 4294967295L is fine
331- try:
332- return socket.inet_ntoa(
333- struct.pack('!L', long(num)))
334- except (socket.error, struct.error, OverflowError):
335- raise ValueError('Not a good numeric IP: %s' % num)
336-
337-
338-class ValidateError(Exception):
339- """
340- This error indicates that the check failed.
341- It can be the base class for more specific errors.
342-
343- Any check function that fails ought to raise this error.
344- (or a subclass)
345-
346- >>> raise ValidateError
347- Traceback (most recent call last):
348- ValidateError
349- """
350-
351-
352-class VdtMissingValue(ValidateError):
353- """No value was supplied to a check that needed one."""
354-
355-
356-class VdtUnknownCheckError(ValidateError):
357- """An unknown check function was requested"""
358-
359- def __init__(self, value):
360- """
361- >>> raise VdtUnknownCheckError('yoda')
362- Traceback (most recent call last):
363- VdtUnknownCheckError: the check "yoda" is unknown.
364- """
365- ValidateError.__init__(self, 'the check "%s" is unknown.' % (value,))
366-
367-
368-class VdtParamError(SyntaxError):
369- """An incorrect parameter was passed"""
370-
371- def __init__(self, name, value):
372- """
373- >>> raise VdtParamError('yoda', 'jedi')
374- Traceback (most recent call last):
375- VdtParamError: passed an incorrect value "jedi" for parameter "yoda".
376- """
377- SyntaxError.__init__(self, 'passed an incorrect value "%s" for parameter "%s".' % (value, name))
378-
379-
380-class VdtTypeError(ValidateError):
381- """The value supplied was of the wrong type"""
382-
383- def __init__(self, value):
384- """
385- >>> raise VdtTypeError('jedi')
386- Traceback (most recent call last):
387- VdtTypeError: the value "jedi" is of the wrong type.
388- """
389- ValidateError.__init__(self, 'the value "%s" is of the wrong type.' % (value,))
390-
391-
392-class VdtValueError(ValidateError):
393- """The value supplied was of the correct type, but was not an allowed value."""
394-
395- def __init__(self, value):
396- """
397- >>> raise VdtValueError('jedi')
398- Traceback (most recent call last):
399- VdtValueError: the value "jedi" is unacceptable.
400- """
401- ValidateError.__init__(self, 'the value "%s" is unacceptable.' % (value,))
402-
403-
404-class VdtValueTooSmallError(VdtValueError):
405- """The value supplied was of the correct type, but was too small."""
406-
407- def __init__(self, value):
408- """
409- >>> raise VdtValueTooSmallError('0')
410- Traceback (most recent call last):
411- VdtValueTooSmallError: the value "0" is too small.
412- """
413- ValidateError.__init__(self, 'the value "%s" is too small.' % (value,))
414-
415-
416-class VdtValueTooBigError(VdtValueError):
417- """The value supplied was of the correct type, but was too big."""
418-
419- def __init__(self, value):
420- """
421- >>> raise VdtValueTooBigError('1')
422- Traceback (most recent call last):
423- VdtValueTooBigError: the value "1" is too big.
424- """
425- ValidateError.__init__(self, 'the value "%s" is too big.' % (value,))
426-
427-
428-class VdtValueTooShortError(VdtValueError):
429- """The value supplied was of the correct type, but was too short."""
430-
431- def __init__(self, value):
432- """
433- >>> raise VdtValueTooShortError('jed')
434- Traceback (most recent call last):
435- VdtValueTooShortError: the value "jed" is too short.
436- """
437- ValidateError.__init__(
438- self,
439- 'the value "%s" is too short.' % (value,))
440-
441-
442-class VdtValueTooLongError(VdtValueError):
443- """The value supplied was of the correct type, but was too long."""
444-
445- def __init__(self, value):
446- """
447- >>> raise VdtValueTooLongError('jedie')
448- Traceback (most recent call last):
449- VdtValueTooLongError: the value "jedie" is too long.
450- """
451- ValidateError.__init__(self, 'the value "%s" is too long.' % (value,))
452-
453-
454-class Validator(object):
455- """
456- Validator is an object that allows you to register a set of 'checks'.
457- These checks take input and test that it conforms to the check.
458-
459- This can also involve converting the value from a string into
460- the correct datatype.
461-
462- The ``check`` method takes an input string which configures which
463- check is to be used and applies that check to a supplied value.
464-
465- An example input string would be:
466- 'int_range(param1, param2)'
467-
468- You would then provide something like:
469-
470- >>> def int_range_check(value, min, max):
471- ... # turn min and max from strings to integers
472- ... min = int(min)
473- ... max = int(max)
474- ... # check that value is of the correct type.
475- ... # possible valid inputs are integers or strings
476- ... # that represent integers
477- ... if not isinstance(value, (int, long, StringTypes)):
478- ... raise VdtTypeError(value)
479- ... elif isinstance(value, StringTypes):
480- ... # if we are given a string
481- ... # attempt to convert to an integer
482- ... try:
483- ... value = int(value)
484- ... except ValueError:
485- ... raise VdtValueError(value)
486- ... # check the value is between our constraints
487- ... if not min <= value:
488- ... raise VdtValueTooSmallError(value)
489- ... if not value <= max:
490- ... raise VdtValueTooBigError(value)
491- ... return value
492-
493- >>> fdict = {'int_range': int_range_check}
494- >>> vtr1 = Validator(fdict)
495- >>> vtr1.check('int_range(20, 40)', '30')
496- 30
497- >>> vtr1.check('int_range(20, 40)', '60')
498- Traceback (most recent call last):
499- VdtValueTooBigError: the value "60" is too big.
500-
501- New functions can be added with : ::
502-
503- >>> vtr2 = Validator()
504- >>> vtr2.functions['int_range'] = int_range_check
505-
506- Or by passing in a dictionary of functions when Validator
507- is instantiated.
508-
509- Your functions *can* use keyword arguments,
510- but the first argument should always be 'value'.
511-
512- If the function doesn't take additional arguments,
513- the parentheses are optional in the check.
514- It can be written with either of : ::
515-
516- keyword = function_name
517- keyword = function_name()
518-
519- The first program to utilise Validator() was Michael Foord's
520- ConfigObj, an alternative to ConfigParser which supports lists and
521- can validate a config file using a config schema.
522- For more details on using Validator with ConfigObj see:
523- http://www.voidspace.org.uk/python/configobj.html
524- """
525-
526- # this regex does the initial parsing of the checks
527- _func_re = re.compile(r'(.+?)\((.*)\)')
528-
529- # this regex takes apart keyword arguments
530- _key_arg = re.compile(r'^([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.*)$')
531-
532-
533- # this regex finds keyword=list(....) type values
534- _list_arg = _list_arg
535-
536- # this regex takes individual values out of lists - in one pass
537- _list_members = _list_members
538-
539- # These regexes check a set of arguments for validity
540- # and then pull the members out
541- _paramfinder = re.compile(_paramstring, re.VERBOSE)
542- _matchfinder = re.compile(_matchstring, re.VERBOSE)
543-
544-
545- def __init__(self, functions=None):
546- """
547- >>> vtri = Validator()
548- """
549- self.functions = {
550- '': self._pass,
551- 'integer': is_integer,
552- 'float': is_float,
553- 'boolean': is_boolean,
554- 'ip_addr': is_ip_addr,
555- 'string': is_string,
556- 'list': is_list,
557- 'tuple': is_tuple,
558- 'int_list': is_int_list,
559- 'float_list': is_float_list,
560- 'bool_list': is_bool_list,
561- 'ip_addr_list': is_ip_addr_list,
562- 'string_list': is_string_list,
563- 'mixed_list': is_mixed_list,
564- 'pass': self._pass,
565- 'option': is_option,
566- }
567- if functions is not None:
568- self.functions.update(functions)
569- # tekNico: for use by ConfigObj
570- self.baseErrorClass = ValidateError
571- self._cache = {}
572-
573-
574- def check(self, check, value, missing=False):
575- """
576- Usage: check(check, value)
577-
578- Arguments:
579- check: string representing check to apply (including arguments)
580- value: object to be checked
581- Returns value, converted to correct type if necessary
582-
583- If the check fails, raises a ``ValidateError`` subclass.
584-
585- >>> vtor.check('yoda', '')
586- Traceback (most recent call last):
587- VdtUnknownCheckError: the check "yoda" is unknown.
588- >>> vtor.check('yoda()', '')
589- Traceback (most recent call last):
590- VdtUnknownCheckError: the check "yoda" is unknown.
591-
592- >>> vtor.check('string(default="")', '', missing=True)
593- ''
594- """
595- fun_name, fun_args, fun_kwargs, default = self._parse_with_caching(check)
596-
597- if missing:
598- if default is None:
599- # no information needed here - to be handled by caller
600- raise VdtMissingValue()
601- value = self._handle_none(default)
602-
603- if value is None:
604- return None
605-
606- return self._check_value(value, fun_name, fun_args, fun_kwargs)
607-
608-
609- def _handle_none(self, value):
610- if value == 'None':
611- value = None
612- elif value in ("'None'", '"None"'):
613- # Special case a quoted None
614- value = self._unquote(value)
615- return value
616-
617-
618- def _parse_with_caching(self, check):
619- if check in self._cache:
620- fun_name, fun_args, fun_kwargs, default = self._cache[check]
621- # We call list and dict below to work with *copies* of the data
622- # rather than the original (which are mutable of course)
623- fun_args = list(fun_args)
624- fun_kwargs = dict(fun_kwargs)
625- else:
626- fun_name, fun_args, fun_kwargs, default = self._parse_check(check)
627- fun_kwargs = dict((str(key), value) for (key, value) in fun_kwargs.items())
628- self._cache[check] = fun_name, list(fun_args), dict(fun_kwargs), default
629- return fun_name, fun_args, fun_kwargs, default
630-
631-
632- def _check_value(self, value, fun_name, fun_args, fun_kwargs):
633- try:
634- fun = self.functions[fun_name]
635- except KeyError:
636- raise VdtUnknownCheckError(fun_name)
637- else:
638- return fun(value, *fun_args, **fun_kwargs)
639-
640-
641- def _parse_check(self, check):
642- fun_match = self._func_re.match(check)
643- if fun_match:
644- fun_name = fun_match.group(1)
645- arg_string = fun_match.group(2)
646- arg_match = self._matchfinder.match(arg_string)
647- if arg_match is None:
648- # Bad syntax
649- raise VdtParamError('Bad syntax in check "%s".' % check)
650- fun_args = []
651- fun_kwargs = {}
652- # pull out args of group 2
653- for arg in self._paramfinder.findall(arg_string):
654- # args may need whitespace removing (before removing quotes)
655- arg = arg.strip()
656- listmatch = self._list_arg.match(arg)
657- if listmatch:
658- key, val = self._list_handle(listmatch)
659- fun_kwargs[key] = val
660- continue
661- keymatch = self._key_arg.match(arg)
662- if keymatch:
663- val = keymatch.group(2)
664- if not val in ("'None'", '"None"'):
665- # Special case a quoted None
666- val = self._unquote(val)
667- fun_kwargs[keymatch.group(1)] = val
668- continue
669-
670- fun_args.append(self._unquote(arg))
671- else:
672- # allows for function names without (args)
673- return check, (), {}, None
674-
675- # Default must be deleted if the value is specified too,
676- # otherwise the check function will get a spurious "default" keyword arg
677- try:
678- default = fun_kwargs.pop('default', None)
679- except AttributeError:
680- # Python 2.2 compatibility
681- default = None
682- try:
683- default = fun_kwargs['default']
684- del fun_kwargs['default']
685- except KeyError:
686- pass
687-
688- return fun_name, fun_args, fun_kwargs, default
689-
690-
691- def _unquote(self, val):
692- """Unquote a value if necessary."""
693- if (len(val) >= 2) and (val[0] in ("'", '"')) and (val[0] == val[-1]):
694- val = val[1:-1]
695- return val
696-
697-
698- def _list_handle(self, listmatch):
699- """Take apart a ``keyword=list('val, 'val')`` type string."""
700- out = []
701- name = listmatch.group(1)
702- args = listmatch.group(2)
703- for arg in self._list_members.findall(args):
704- out.append(self._unquote(arg))
705- return name, out
706-
707-
708- def _pass(self, value):
709- """
710- Dummy check that always passes
711-
712- >>> vtor.check('', 0)
713- 0
714- >>> vtor.check('', '0')
715- '0'
716- """
717- return value
718-
719-
720- def get_default_value(self, check):
721- """
722- Given a check, return the default value for the check
723- (converted to the right type).
724-
725- If the check doesn't specify a default value then a
726- ``KeyError`` will be raised.
727- """
728- fun_name, fun_args, fun_kwargs, default = self._parse_with_caching(check)
729- if default is None:
730- raise KeyError('Check "%s" has no default value.' % check)
731- value = self._handle_none(default)
732- if value is None:
733- return value
734- return self._check_value(value, fun_name, fun_args, fun_kwargs)
735-
736-
737-def _is_num_param(names, values, to_float=False):
738- """
739- Return numbers from inputs or raise VdtParamError.
740-
741- Lets ``None`` pass through.
742- Pass in keyword argument ``to_float=True`` to
743- use float for the conversion rather than int.
744-
745- >>> _is_num_param(('', ''), (0, 1.0))
746- [0, 1]
747- >>> _is_num_param(('', ''), (0, 1.0), to_float=True)
748- [0.0, 1.0]
749- >>> _is_num_param(('a'), ('a'))
750- Traceback (most recent call last):
751- VdtParamError: passed an incorrect value "a" for parameter "a".
752- """
753- fun = to_float and float or int
754- out_params = []
755- for (name, val) in zip(names, values):
756- if val is None:
757- out_params.append(val)
758- elif isinstance(val, (int, long, float, StringTypes)):
759- try:
760- out_params.append(fun(val))
761- except ValueError, e:
762- raise VdtParamError(name, val)
763- else:
764- raise VdtParamError(name, val)
765- return out_params
766-
767-
768-# built in checks
769-# you can override these by setting the appropriate name
770-# in Validator.functions
771-# note: if the params are specified wrongly in your input string,
772-# you will also raise errors.
773-
774-def is_integer(value, min=None, max=None):
775- """
776- A check that tests that a given value is an integer (int, or long)
777- and optionally, between bounds. A negative value is accepted, while
778- a float will fail.
779-
780- If the value is a string, then the conversion is done - if possible.
781- Otherwise a VdtError is raised.
782-
783- >>> vtor.check('integer', '-1')
784- -1
785- >>> vtor.check('integer', '0')
786- 0
787- >>> vtor.check('integer', 9)
788- 9
789- >>> vtor.check('integer', 'a')
790- Traceback (most recent call last):
791- VdtTypeError: the value "a" is of the wrong type.
792- >>> vtor.check('integer', '2.2')
793- Traceback (most recent call last):
794- VdtTypeError: the value "2.2" is of the wrong type.
795- >>> vtor.check('integer(10)', '20')
796- 20
797- >>> vtor.check('integer(max=20)', '15')
798- 15
799- >>> vtor.check('integer(10)', '9')
800- Traceback (most recent call last):
801- VdtValueTooSmallError: the value "9" is too small.
802- >>> vtor.check('integer(10)', 9)
803- Traceback (most recent call last):
804- VdtValueTooSmallError: the value "9" is too small.
805- >>> vtor.check('integer(max=20)', '35')
806- Traceback (most recent call last):
807- VdtValueTooBigError: the value "35" is too big.
808- >>> vtor.check('integer(max=20)', 35)
809- Traceback (most recent call last):
810- VdtValueTooBigError: the value "35" is too big.
811- >>> vtor.check('integer(0, 9)', False)
812- 0
813- """
814- (min_val, max_val) = _is_num_param(('min', 'max'), (min, max))
815- if not isinstance(value, (int, long, StringTypes)):
816- raise VdtTypeError(value)
817- if isinstance(value, StringTypes):
818- # if it's a string - does it represent an integer ?
819- try:
820- value = int(value)
821- except ValueError:
822- raise VdtTypeError(value)
823- if (min_val is not None) and (value < min_val):
824- raise VdtValueTooSmallError(value)
825- if (max_val is not None) and (value > max_val):
826- raise VdtValueTooBigError(value)
827- return value
828-
829-
830-def is_float(value, min=None, max=None):
831- """
832- A check that tests that a given value is a float
833- (an integer will be accepted), and optionally - that it is between bounds.
834-
835- If the value is a string, then the conversion is done - if possible.
836- Otherwise a VdtError is raised.
837-
838- This can accept negative values.
839-
840- >>> vtor.check('float', '2')
841- 2.0
842-
843- From now on we multiply the value to avoid comparing decimals
844-
845- >>> vtor.check('float', '-6.8') * 10
846- -68.0
847- >>> vtor.check('float', '12.2') * 10
848- 122.0
849- >>> vtor.check('float', 8.4) * 10
850- 84.0
851- >>> vtor.check('float', 'a')
852- Traceback (most recent call last):
853- VdtTypeError: the value "a" is of the wrong type.
854- >>> vtor.check('float(10.1)', '10.2') * 10
855- 102.0
856- >>> vtor.check('float(max=20.2)', '15.1') * 10
857- 151.0
858- >>> vtor.check('float(10.0)', '9.0')
859- Traceback (most recent call last):
860- VdtValueTooSmallError: the value "9.0" is too small.
861- >>> vtor.check('float(max=20.0)', '35.0')
862- Traceback (most recent call last):
863- VdtValueTooBigError: the value "35.0" is too big.
864- """
865- (min_val, max_val) = _is_num_param(
866- ('min', 'max'), (min, max), to_float=True)
867- if not isinstance(value, (int, long, float, StringTypes)):
868- raise VdtTypeError(value)
869- if not isinstance(value, float):
870- # if it's a string - does it represent a float ?
871- try:
872- value = float(value)
873- except ValueError:
874- raise VdtTypeError(value)
875- if (min_val is not None) and (value < min_val):
876- raise VdtValueTooSmallError(value)
877- if (max_val is not None) and (value > max_val):
878- raise VdtValueTooBigError(value)
879- return value
880-
881-
882-bool_dict = {
883- True: True, 'on': True, '1': True, 'true': True, 'yes': True,
884- False: False, 'off': False, '0': False, 'false': False, 'no': False,
885-}
886-
887-
888-def is_boolean(value):
889- """
890- Check if the value represents a boolean.
891-
892- >>> vtor.check('boolean', 0)
893- 0
894- >>> vtor.check('boolean', False)
895- 0
896- >>> vtor.check('boolean', '0')
897- 0
898- >>> vtor.check('boolean', 'off')
899- 0
900- >>> vtor.check('boolean', 'false')
901- 0
902- >>> vtor.check('boolean', 'no')
903- 0
904- >>> vtor.check('boolean', 'nO')
905- 0
906- >>> vtor.check('boolean', 'NO')
907- 0
908- >>> vtor.check('boolean', 1)
909- 1
910- >>> vtor.check('boolean', True)
911- 1
912- >>> vtor.check('boolean', '1')
913- 1
914- >>> vtor.check('boolean', 'on')
915- 1
916- >>> vtor.check('boolean', 'true')
917- 1
918- >>> vtor.check('boolean', 'yes')
919- 1
920- >>> vtor.check('boolean', 'Yes')
921- 1
922- >>> vtor.check('boolean', 'YES')
923- 1
924- >>> vtor.check('boolean', '')
925- Traceback (most recent call last):
926- VdtTypeError: the value "" is of the wrong type.
927- >>> vtor.check('boolean', 'up')
928- Traceback (most recent call last):
929- VdtTypeError: the value "up" is of the wrong type.
930-
931- """
932- if isinstance(value, StringTypes):
933- try:
934- return bool_dict[value.lower()]
935- except KeyError:
936- raise VdtTypeError(value)
937- # we do an equality test rather than an identity test
938- # this ensures Python 2.2 compatibilty
939- # and allows 0 and 1 to represent True and False
940- if value == False:
941- return False
942- elif value == True:
943- return True
944- else:
945- raise VdtTypeError(value)
946-
947-
948-def is_ip_addr(value):
949- """
950- Check that the supplied value is an Internet Protocol address, v.4,
951- represented by a dotted-quad string, i.e. '1.2.3.4'.
952-
953- >>> vtor.check('ip_addr', '1 ')
954- '1'
955- >>> vtor.check('ip_addr', ' 1.2')
956- '1.2'
957- >>> vtor.check('ip_addr', ' 1.2.3 ')
958- '1.2.3'
959- >>> vtor.check('ip_addr', '1.2.3.4')
960- '1.2.3.4'
961- >>> vtor.check('ip_addr', '0.0.0.0')
962- '0.0.0.0'
963- >>> vtor.check('ip_addr', '255.255.255.255')
964- '255.255.255.255'
965- >>> vtor.check('ip_addr', '255.255.255.256')
966- Traceback (most recent call last):
967- VdtValueError: the value "255.255.255.256" is unacceptable.
968- >>> vtor.check('ip_addr', '1.2.3.4.5')
969- Traceback (most recent call last):
970- VdtValueError: the value "1.2.3.4.5" is unacceptable.
971- >>> vtor.check('ip_addr', '1.2.3. 4')
972- Traceback (most recent call last):
973- VdtValueError: the value "1.2.3. 4" is unacceptable.
974- >>> vtor.check('ip_addr', 0)
975- Traceback (most recent call last):
976- VdtTypeError: the value "0" is of the wrong type.
977- """
978- if not isinstance(value, StringTypes):
979- raise VdtTypeError(value)
980- value = value.strip()
981- try:
982- dottedQuadToNum(value)
983- except ValueError:
984- raise VdtValueError(value)
985- return value
986-
987-
988-def is_list(value, min=None, max=None):
989- """
990- Check that the value is a list of values.
991-
992- You can optionally specify the minimum and maximum number of members.
993-
994- It does no check on list members.
995-
996- >>> vtor.check('list', ())
997- []
998- >>> vtor.check('list', [])
999- []
1000- >>> vtor.check('list', (1, 2))
1001- [1, 2]
1002- >>> vtor.check('list', [1, 2])
1003- [1, 2]
1004- >>> vtor.check('list(3)', (1, 2))
1005- Traceback (most recent call last):
1006- VdtValueTooShortError: the value "(1, 2)" is too short.
1007- >>> vtor.check('list(max=5)', (1, 2, 3, 4, 5, 6))
1008- Traceback (most recent call last):
1009- VdtValueTooLongError: the value "(1, 2, 3, 4, 5, 6)" is too long.
1010- >>> vtor.check('list(min=3, max=5)', (1, 2, 3, 4))
1011- [1, 2, 3, 4]
1012- >>> vtor.check('list', 0)
1013- Traceback (most recent call last):
1014- VdtTypeError: the value "0" is of the wrong type.
1015- >>> vtor.check('list', '12')
1016- Traceback (most recent call last):
1017- VdtTypeError: the value "12" is of the wrong type.
1018- """
1019- (min_len, max_len) = _is_num_param(('min', 'max'), (min, max))
1020- if isinstance(value, StringTypes):
1021- raise VdtTypeError(value)
1022- try:
1023- num_members = len(value)
1024- except TypeError:
1025- raise VdtTypeError(value)
1026- if min_len is not None and num_members < min_len:
1027- raise VdtValueTooShortError(value)
1028- if max_len is not None and num_members > max_len:
1029- raise VdtValueTooLongError(value)
1030- return list(value)
1031-
1032-
1033-def is_tuple(value, min=None, max=None):
1034- """
1035- Check that the value is a tuple of values.
1036-
1037- You can optionally specify the minimum and maximum number of members.
1038-
1039- It does no check on members.
1040-
1041- >>> vtor.check('tuple', ())
1042- ()
1043- >>> vtor.check('tuple', [])
1044- ()
1045- >>> vtor.check('tuple', (1, 2))
1046- (1, 2)
1047- >>> vtor.check('tuple', [1, 2])
1048- (1, 2)
1049- >>> vtor.check('tuple(3)', (1, 2))
1050- Traceback (most recent call last):
1051- VdtValueTooShortError: the value "(1, 2)" is too short.
1052- >>> vtor.check('tuple(max=5)', (1, 2, 3, 4, 5, 6))
1053- Traceback (most recent call last):
1054- VdtValueTooLongError: the value "(1, 2, 3, 4, 5, 6)" is too long.
1055- >>> vtor.check('tuple(min=3, max=5)', (1, 2, 3, 4))
1056- (1, 2, 3, 4)
1057- >>> vtor.check('tuple', 0)
1058- Traceback (most recent call last):
1059- VdtTypeError: the value "0" is of the wrong type.
1060- >>> vtor.check('tuple', '12')
1061- Traceback (most recent call last):
1062- VdtTypeError: the value "12" is of the wrong type.
1063- """
1064- return tuple(is_list(value, min, max))
1065-
1066-
1067-def is_string(value, min=None, max=None):
1068- """
1069- Check that the supplied value is a string.
1070-
1071- You can optionally specify the minimum and maximum number of members.
1072-
1073- >>> vtor.check('string', '0')
1074- '0'
1075- >>> vtor.check('string', 0)
1076- Traceback (most recent call last):
1077- VdtTypeError: the value "0" is of the wrong type.
1078- >>> vtor.check('string(2)', '12')
1079- '12'
1080- >>> vtor.check('string(2)', '1')
1081- Traceback (most recent call last):
1082- VdtValueTooShortError: the value "1" is too short.
1083- >>> vtor.check('string(min=2, max=3)', '123')
1084- '123'
1085- >>> vtor.check('string(min=2, max=3)', '1234')
1086- Traceback (most recent call last):
1087- VdtValueTooLongError: the value "1234" is too long.
1088- """
1089- if not isinstance(value, StringTypes):
1090- raise VdtTypeError(value)
1091- (min_len, max_len) = _is_num_param(('min', 'max'), (min, max))
1092- try:
1093- num_members = len(value)
1094- except TypeError:
1095- raise VdtTypeError(value)
1096- if min_len is not None and num_members < min_len:
1097- raise VdtValueTooShortError(value)
1098- if max_len is not None and num_members > max_len:
1099- raise VdtValueTooLongError(value)
1100- return value
1101-
1102-
1103-def is_int_list(value, min=None, max=None):
1104- """
1105- Check that the value is a list of integers.
1106-
1107- You can optionally specify the minimum and maximum number of members.
1108-
1109- Each list member is checked that it is an integer.
1110-
1111- >>> vtor.check('int_list', ())
1112- []
1113- >>> vtor.check('int_list', [])
1114- []
1115- >>> vtor.check('int_list', (1, 2))
1116- [1, 2]
1117- >>> vtor.check('int_list', [1, 2])
1118- [1, 2]
1119- >>> vtor.check('int_list', [1, 'a'])
1120- Traceback (most recent call last):
1121- VdtTypeError: the value "a" is of the wrong type.
1122- """
1123- return [is_integer(mem) for mem in is_list(value, min, max)]
1124-
1125-
1126-def is_bool_list(value, min=None, max=None):
1127- """
1128- Check that the value is a list of booleans.
1129-
1130- You can optionally specify the minimum and maximum number of members.
1131-
1132- Each list member is checked that it is a boolean.
1133-
1134- >>> vtor.check('bool_list', ())
1135- []
1136- >>> vtor.check('bool_list', [])
1137- []
1138- >>> check_res = vtor.check('bool_list', (True, False))
1139- >>> check_res == [True, False]
1140- 1
1141- >>> check_res = vtor.check('bool_list', [True, False])
1142- >>> check_res == [True, False]
1143- 1
1144- >>> vtor.check('bool_list', [True, 'a'])
1145- Traceback (most recent call last):
1146- VdtTypeError: the value "a" is of the wrong type.
1147- """
1148- return [is_boolean(mem) for mem in is_list(value, min, max)]
1149-
1150-
1151-def is_float_list(value, min=None, max=None):
1152- """
1153- Check that the value is a list of floats.
1154-
1155- You can optionally specify the minimum and maximum number of members.
1156-
1157- Each list member is checked that it is a float.
1158-
1159- >>> vtor.check('float_list', ())
1160- []
1161- >>> vtor.check('float_list', [])
1162- []
1163- >>> vtor.check('float_list', (1, 2.0))
1164- [1.0, 2.0]
1165- >>> vtor.check('float_list', [1, 2.0])
1166- [1.0, 2.0]
1167- >>> vtor.check('float_list', [1, 'a'])
1168- Traceback (most recent call last):
1169- VdtTypeError: the value "a" is of the wrong type.
1170- """
1171- return [is_float(mem) for mem in is_list(value, min, max)]
1172-
1173-
1174-def is_string_list(value, min=None, max=None):
1175- """
1176- Check that the value is a list of strings.
1177-
1178- You can optionally specify the minimum and maximum number of members.
1179-
1180- Each list member is checked that it is a string.
1181-
1182- >>> vtor.check('string_list', ())
1183- []
1184- >>> vtor.check('string_list', [])
1185- []
1186- >>> vtor.check('string_list', ('a', 'b'))
1187- ['a', 'b']
1188- >>> vtor.check('string_list', ['a', 1])
1189- Traceback (most recent call last):
1190- VdtTypeError: the value "1" is of the wrong type.
1191- >>> vtor.check('string_list', 'hello')
1192- Traceback (most recent call last):
1193- VdtTypeError: the value "hello" is of the wrong type.
1194- """
1195- if isinstance(value, StringTypes):
1196- raise VdtTypeError(value)
1197- return [is_string(mem) for mem in is_list(value, min, max)]
1198-
1199-
1200-def is_ip_addr_list(value, min=None, max=None):
1201- """
1202- Check that the value is a list of IP addresses.
1203-
1204- You can optionally specify the minimum and maximum number of members.
1205-
1206- Each list member is checked that it is an IP address.
1207-
1208- >>> vtor.check('ip_addr_list', ())
1209- []
1210- >>> vtor.check('ip_addr_list', [])
1211- []
1212- >>> vtor.check('ip_addr_list', ('1.2.3.4', '5.6.7.8'))
1213- ['1.2.3.4', '5.6.7.8']
1214- >>> vtor.check('ip_addr_list', ['a'])
1215- Traceback (most recent call last):
1216- VdtValueError: the value "a" is unacceptable.
1217- """
1218- return [is_ip_addr(mem) for mem in is_list(value, min, max)]
1219-
1220-
1221-fun_dict = {
1222- 'integer': is_integer,
1223- 'float': is_float,
1224- 'ip_addr': is_ip_addr,
1225- 'string': is_string,
1226- 'boolean': is_boolean,
1227-}
1228-
1229-
1230-def is_mixed_list(value, *args):
1231- """
1232- Check that the value is a list.
1233- Allow specifying the type of each member.
1234- Work on lists of specific lengths.
1235-
1236- You specify each member as a positional argument specifying type
1237-
1238- Each type should be one of the following strings :
1239- 'integer', 'float', 'ip_addr', 'string', 'boolean'
1240-
1241- So you can specify a list of two strings, followed by
1242- two integers as :
1243-
1244- mixed_list('string', 'string', 'integer', 'integer')
1245-
1246- The length of the list must match the number of positional
1247- arguments you supply.
1248-
1249- >>> mix_str = "mixed_list('integer', 'float', 'ip_addr', 'string', 'boolean')"
1250- >>> check_res = vtor.check(mix_str, (1, 2.0, '1.2.3.4', 'a', True))
1251- >>> check_res == [1, 2.0, '1.2.3.4', 'a', True]
1252- 1
1253- >>> check_res = vtor.check(mix_str, ('1', '2.0', '1.2.3.4', 'a', 'True'))
1254- >>> check_res == [1, 2.0, '1.2.3.4', 'a', True]
1255- 1
1256- >>> vtor.check(mix_str, ('b', 2.0, '1.2.3.4', 'a', True))
1257- Traceback (most recent call last):
1258- VdtTypeError: the value "b" is of the wrong type.
1259- >>> vtor.check(mix_str, (1, 2.0, '1.2.3.4', 'a'))
1260- Traceback (most recent call last):
1261- VdtValueTooShortError: the value "(1, 2.0, '1.2.3.4', 'a')" is too short.
1262- >>> vtor.check(mix_str, (1, 2.0, '1.2.3.4', 'a', 1, 'b'))
1263- Traceback (most recent call last):
1264- VdtValueTooLongError: the value "(1, 2.0, '1.2.3.4', 'a', 1, 'b')" is too long.
1265- >>> vtor.check(mix_str, 0)
1266- Traceback (most recent call last):
1267- VdtTypeError: the value "0" is of the wrong type.
1268-
1269- This test requires an elaborate setup, because of a change in error string
1270- output from the interpreter between Python 2.2 and 2.3 .
1271-
1272- >>> res_seq = (
1273- ... 'passed an incorrect value "',
1274- ... 'yoda',
1275- ... '" for parameter "mixed_list".',
1276- ... )
1277- >>> if INTP_VER == (2, 2):
1278- ... res_str = "".join(res_seq)
1279- ... else:
1280- ... res_str = "'".join(res_seq)
1281- >>> try:
1282- ... vtor.check('mixed_list("yoda")', ('a'))
1283- ... except VdtParamError, err:
1284- ... str(err) == res_str
1285- 1
1286- """
1287- try:
1288- length = len(value)
1289- except TypeError:
1290- raise VdtTypeError(value)
1291- if length < len(args):
1292- raise VdtValueTooShortError(value)
1293- elif length > len(args):
1294- raise VdtValueTooLongError(value)
1295- try:
1296- return [fun_dict[arg](val) for arg, val in zip(args, value)]
1297- except KeyError, e:
1298- raise VdtParamError('mixed_list', e)
1299-
1300-
1301-def is_option(value, *options):
1302- """
1303- This check matches the value to any of a set of options.
1304-
1305- >>> vtor.check('option("yoda", "jedi")', 'yoda')
1306- 'yoda'
1307- >>> vtor.check('option("yoda", "jedi")', 'jed')
1308- Traceback (most recent call last):
1309- VdtValueError: the value "jed" is unacceptable.
1310- >>> vtor.check('option("yoda", "jedi")', 0)
1311- Traceback (most recent call last):
1312- VdtTypeError: the value "0" is of the wrong type.
1313- """
1314- if not isinstance(value, StringTypes):
1315- raise VdtTypeError(value)
1316- if not value in options:
1317- raise VdtValueError(value)
1318- return value
1319-
1320-
1321-def _test(value, *args, **keywargs):
1322- """
1323- A function that exists for test purposes.
1324-
1325- >>> checks = [
1326- ... '3, 6, min=1, max=3, test=list(a, b, c)',
1327- ... '3',
1328- ... '3, 6',
1329- ... '3,',
1330- ... 'min=1, test="a b c"',
1331- ... 'min=5, test="a, b, c"',
1332- ... 'min=1, max=3, test="a, b, c"',
1333- ... 'min=-100, test=-99',
1334- ... 'min=1, max=3',
1335- ... '3, 6, test="36"',
1336- ... '3, 6, test="a, b, c"',
1337- ... '3, max=3, test=list("a", "b", "c")',
1338- ... '''3, max=3, test=list("'a'", 'b', "x=(c)")''',
1339- ... "test='x=fish(3)'",
1340- ... ]
1341- >>> v = Validator({'test': _test})
1342- >>> for entry in checks:
1343- ... print v.check(('test(%s)' % entry), 3)
1344- (3, ('3', '6'), {'test': ['a', 'b', 'c'], 'max': '3', 'min': '1'})
1345- (3, ('3',), {})
1346- (3, ('3', '6'), {})
1347- (3, ('3',), {})
1348- (3, (), {'test': 'a b c', 'min': '1'})
1349- (3, (), {'test': 'a, b, c', 'min': '5'})
1350- (3, (), {'test': 'a, b, c', 'max': '3', 'min': '1'})
1351- (3, (), {'test': '-99', 'min': '-100'})
1352- (3, (), {'max': '3', 'min': '1'})
1353- (3, ('3', '6'), {'test': '36'})
1354- (3, ('3', '6'), {'test': 'a, b, c'})
1355- (3, ('3',), {'test': ['a', 'b', 'c'], 'max': '3'})
1356- (3, ('3',), {'test': ["'a'", 'b', 'x=(c)'], 'max': '3'})
1357- (3, (), {'test': 'x=fish(3)'})
1358-
1359- >>> v = Validator()
1360- >>> v.check('integer(default=6)', '3')
1361- 3
1362- >>> v.check('integer(default=6)', None, True)
1363- 6
1364- >>> v.get_default_value('integer(default=6)')
1365- 6
1366- >>> v.get_default_value('float(default=6)')
1367- 6.0
1368- >>> v.get_default_value('pass(default=None)')
1369- >>> v.get_default_value("string(default='None')")
1370- 'None'
1371- >>> v.get_default_value('pass')
1372- Traceback (most recent call last):
1373- KeyError: 'Check "pass" has no default value.'
1374- >>> v.get_default_value('pass(default=list(1, 2, 3, 4))')
1375- ['1', '2', '3', '4']
1376-
1377- >>> v = Validator()
1378- >>> v.check("pass(default=None)", None, True)
1379- >>> v.check("pass(default='None')", None, True)
1380- 'None'
1381- >>> v.check('pass(default="None")', None, True)
1382- 'None'
1383- >>> v.check('pass(default=list(1, 2, 3, 4))', None, True)
1384- ['1', '2', '3', '4']
1385-
1386- Bug test for unicode arguments
1387- >>> v = Validator()
1388- >>> v.check(u'string(min=4)', u'test')
1389- u'test'
1390-
1391- >>> v = Validator()
1392- >>> v.get_default_value(u'string(min=4, default="1234")')
1393- u'1234'
1394- >>> v.check(u'string(min=4, default="1234")', u'test')
1395- u'test'
1396-
1397- >>> v = Validator()
1398- >>> default = v.get_default_value('string(default=None)')
1399- >>> default == None
1400- 1
1401- """
1402- return (value, args, keywargs)
1403-
1404-
1405-if __name__ == '__main__':
1406- # run the code tests in doctest format
1407- import doctest
1408- m = sys.modules.get('__main__')
1409- globs = m.__dict__.copy()
1410- globs.update({
1411- 'INTP_VER': INTP_VER,
1412- 'vtor': Validator(),
1413- })
1414- doctest.testmod(m, globs=globs)
1+# validate.py
2+# A Validator object
3+# Copyright (C) 2005-2010 Michael Foord, Mark Andrews, Nicola Larosa
4+# E-mail: fuzzyman AT voidspace DOT org DOT uk
5+# mark AT la-la DOT com
6+# nico AT tekNico DOT net
7+
8+# This software is licensed under the terms of the BSD license.
9+# http://www.voidspace.org.uk/python/license.shtml
10+# Basically you're free to copy, modify, distribute and relicense it,
11+# So long as you keep a copy of the license with it.
12+
13+# Scripts maintained at http://www.voidspace.org.uk/python/index.shtml
14+# For information about bugfixes, updates and support, please join the
15+# ConfigObj mailing list:
16+# http://lists.sourceforge.net/lists/listinfo/configobj-develop
17+# Comments, suggestions and bug reports welcome.
18+
19+"""
20+ The Validator object is used to check that supplied values
21+ conform to a specification.
22+
23+ The value can be supplied as a string - e.g. from a config file.
24+ In this case the check will also *convert* the value to
25+ the required type. This allows you to add validation
26+ as a transparent layer to access data stored as strings.
27+ The validation checks that the data is correct *and*
28+ converts it to the expected type.
29+
30+ Some standard checks are provided for basic data types.
31+ Additional checks are easy to write. They can be
32+ provided when the ``Validator`` is instantiated or
33+ added afterwards.
34+
35+ The standard functions work with the following basic data types :
36+
37+ * integers
38+ * floats
39+ * booleans
40+ * strings
41+ * ip_addr
42+
43+ plus lists of these datatypes
44+
45+ Adding additional checks is done through coding simple functions.
46+
47+ The full set of standard checks are :
48+
49+ * 'integer': matches integer values (including negative)
50+ Takes optional 'min' and 'max' arguments : ::
51+
52+ integer()
53+ integer(3, 9) # any value from 3 to 9
54+ integer(min=0) # any positive value
55+ integer(max=9)
56+
57+ * 'float': matches float values
58+ Has the same parameters as the integer check.
59+
60+ * 'boolean': matches boolean values - ``True`` or ``False``
61+ Acceptable string values for True are :
62+ true, on, yes, 1
63+ Acceptable string values for False are :
64+ false, off, no, 0
65+
66+ Any other value raises an error.
67+
68+ * 'ip_addr': matches an Internet Protocol address, v.4, represented
69+ by a dotted-quad string, i.e. '1.2.3.4'.
70+
71+ * 'string': matches any string.
72+ Takes optional keyword args 'min' and 'max'
73+ to specify min and max lengths of the string.
74+
75+ * 'list': matches any list.
76+ Takes optional keyword args 'min', and 'max' to specify min and
77+ max sizes of the list. (Always returns a list.)
78+
79+ * 'tuple': matches any tuple.
80+ Takes optional keyword args 'min', and 'max' to specify min and
81+ max sizes of the tuple. (Always returns a tuple.)
82+
83+ * 'int_list': Matches a list of integers.
84+ Takes the same arguments as list.
85+
86+ * 'float_list': Matches a list of floats.
87+ Takes the same arguments as list.
88+
89+ * 'bool_list': Matches a list of boolean values.
90+ Takes the same arguments as list.
91+
92+ * 'ip_addr_list': Matches a list of IP addresses.
93+ Takes the same arguments as list.
94+
95+ * 'string_list': Matches a list of strings.
96+ Takes the same arguments as list.
97+
98+ * 'mixed_list': Matches a list with different types in
99+ specific positions. List size must match
100+ the number of arguments.
101+
102+ Each position can be one of :
103+ 'integer', 'float', 'ip_addr', 'string', 'boolean'
104+
105+ So to specify a list with two strings followed
106+ by two integers, you write the check as : ::
107+
108+ mixed_list('string', 'string', 'integer', 'integer')
109+
110+ * 'pass': This check matches everything ! It never fails
111+ and the value is unchanged.
112+
113+ It is also the default if no check is specified.
114+
115+ * 'option': This check matches any from a list of options.
116+ You specify this check with : ::
117+
118+ option('option 1', 'option 2', 'option 3')
119+
120+ You can supply a default value (returned if no value is supplied)
121+ using the default keyword argument.
122+
123+ You specify a list argument for default using a list constructor syntax in
124+ the check : ::
125+
126+ checkname(arg1, arg2, default=list('val 1', 'val 2', 'val 3'))
127+
128+ A badly formatted set of arguments will raise a ``VdtParamError``.
129+"""
130+
131+__version__ = '1.0.1'
132+
133+
134+__all__ = (
135+ '__version__',
136+ 'dottedQuadToNum',
137+ 'numToDottedQuad',
138+ 'ValidateError',
139+ 'VdtUnknownCheckError',
140+ 'VdtParamError',
141+ 'VdtTypeError',
142+ 'VdtValueError',
143+ 'VdtValueTooSmallError',
144+ 'VdtValueTooBigError',
145+ 'VdtValueTooShortError',
146+ 'VdtValueTooLongError',
147+ 'VdtMissingValue',
148+ 'Validator',
149+ 'is_integer',
150+ 'is_float',
151+ 'is_boolean',
152+ 'is_list',
153+ 'is_tuple',
154+ 'is_ip_addr',
155+ 'is_string',
156+ 'is_int_list',
157+ 'is_bool_list',
158+ 'is_float_list',
159+ 'is_string_list',
160+ 'is_ip_addr_list',
161+ 'is_mixed_list',
162+ 'is_option',
163+ '__docformat__',
164+)
165+
166+
167+import re
168+
169+
170+_list_arg = re.compile(r'''
171+ (?:
172+ ([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*list\(
173+ (
174+ (?:
175+ \s*
176+ (?:
177+ (?:".*?")| # double quotes
178+ (?:'.*?')| # single quotes
179+ (?:[^'",\s\)][^,\)]*?) # unquoted
180+ )
181+ \s*,\s*
182+ )*
183+ (?:
184+ (?:".*?")| # double quotes
185+ (?:'.*?')| # single quotes
186+ (?:[^'",\s\)][^,\)]*?) # unquoted
187+ )? # last one
188+ )
189+ \)
190+ )
191+''', re.VERBOSE | re.DOTALL) # two groups
192+
193+_list_members = re.compile(r'''
194+ (
195+ (?:".*?")| # double quotes
196+ (?:'.*?')| # single quotes
197+ (?:[^'",\s=][^,=]*?) # unquoted
198+ )
199+ (?:
200+ (?:\s*,\s*)|(?:\s*$) # comma
201+ )
202+''', re.VERBOSE | re.DOTALL) # one group
203+
204+_paramstring = r'''
205+ (?:
206+ (
207+ (?:
208+ [a-zA-Z_][a-zA-Z0-9_]*\s*=\s*list\(
209+ (?:
210+ \s*
211+ (?:
212+ (?:".*?")| # double quotes
213+ (?:'.*?')| # single quotes
214+ (?:[^'",\s\)][^,\)]*?) # unquoted
215+ )
216+ \s*,\s*
217+ )*
218+ (?:
219+ (?:".*?")| # double quotes
220+ (?:'.*?')| # single quotes
221+ (?:[^'",\s\)][^,\)]*?) # unquoted
222+ )? # last one
223+ \)
224+ )|
225+ (?:
226+ (?:".*?")| # double quotes
227+ (?:'.*?')| # single quotes
228+ (?:[^'",\s=][^,=]*?)| # unquoted
229+ (?: # keyword argument
230+ [a-zA-Z_][a-zA-Z0-9_]*\s*=\s*
231+ (?:
232+ (?:".*?")| # double quotes
233+ (?:'.*?')| # single quotes
234+ (?:[^'",\s=][^,=]*?) # unquoted
235+ )
236+ )
237+ )
238+ )
239+ (?:
240+ (?:\s*,\s*)|(?:\s*$) # comma
241+ )
242+ )
243+ '''
244+
245+_matchstring = '^%s*' % _paramstring
246+
247+# Python pre 2.2.1 doesn't have bool
248+try:
249+ bool
250+except NameError:
251+ def bool(val):
252+ """Simple boolean equivalent function. """
253+ if val:
254+ return 1
255+ else:
256+ return 0
257+
258+
259+def dottedQuadToNum(ip):
260+ """
261+ Convert decimal dotted quad string to long integer
262+
263+ >>> int(dottedQuadToNum('1 '))
264+ 1
265+ >>> int(dottedQuadToNum(' 1.2'))
266+ 16777218
267+ >>> int(dottedQuadToNum(' 1.2.3 '))
268+ 16908291
269+ >>> int(dottedQuadToNum('1.2.3.4'))
270+ 16909060
271+ >>> dottedQuadToNum('255.255.255.255')
272+ 4294967295L
273+ >>> dottedQuadToNum('255.255.255.256')
274+ Traceback (most recent call last):
275+ ValueError: Not a good dotted-quad IP: 255.255.255.256
276+ """
277+
278+ # import here to avoid it when ip_addr values are not used
279+ import socket, struct
280+
281+ try:
282+ return struct.unpack('!L',
283+ socket.inet_aton(ip.strip()))[0]
284+ except socket.error:
285+ # bug in inet_aton, corrected in Python 2.4
286+ if ip.strip() == '255.255.255.255':
287+ return 0xFFFFFFFFL
288+ else:
289+ raise ValueError('Not a good dotted-quad IP: %s' % ip)
290+ return
291+
292+
293+def numToDottedQuad(num):
294+ """
295+ Convert long int to dotted quad string
296+
297+ >>> numToDottedQuad(-1L)
298+ Traceback (most recent call last):
299+ ValueError: Not a good numeric IP: -1
300+ >>> numToDottedQuad(1L)
301+ '0.0.0.1'
302+ >>> numToDottedQuad(16777218L)
303+ '1.0.0.2'
304+ >>> numToDottedQuad(16908291L)
305+ '1.2.0.3'
306+ >>> numToDottedQuad(16909060L)
307+ '1.2.3.4'
308+ >>> numToDottedQuad(4294967295L)
309+ '255.255.255.255'
310+ >>> numToDottedQuad(4294967296L)
311+ Traceback (most recent call last):
312+ ValueError: Not a good numeric IP: 4294967296
313+ """
314+
315+ # import here to avoid it when ip_addr values are not used
316+ import socket, struct
317+
318+ # no need to intercept here, 4294967295L is fine
319+ if num > 4294967295L or num < 0:
320+ raise ValueError('Not a good numeric IP: %s' % num)
321+ try:
322+ return socket.inet_ntoa(
323+ struct.pack('!L', long(num)))
324+ except (socket.error, struct.error, OverflowError):
325+ raise ValueError('Not a good numeric IP: %s' % num)
326+
327+
328+class ValidateError(Exception):
329+ """
330+ This error indicates that the check failed.
331+ It can be the base class for more specific errors.
332+
333+ Any check function that fails ought to raise this error.
334+ (or a subclass)
335+
336+ >>> raise ValidateError
337+ Traceback (most recent call last):
338+ ValidateError
339+ """
340+
341+
342+class VdtMissingValue(ValidateError):
343+ """No value was supplied to a check that needed one."""
344+
345+
346+class VdtUnknownCheckError(ValidateError):
347+ """An unknown check function was requested"""
348+
349+ def __init__(self, value):
350+ """
351+ >>> raise VdtUnknownCheckError('yoda')
352+ Traceback (most recent call last):
353+ VdtUnknownCheckError: the check "yoda" is unknown.
354+ """
355+ ValidateError.__init__(self, 'the check "%s" is unknown.' % (value,))
356+
357+
358+class VdtParamError(SyntaxError):
359+ """An incorrect parameter was passed"""
360+
361+ def __init__(self, name, value):
362+ """
363+ >>> raise VdtParamError('yoda', 'jedi')
364+ Traceback (most recent call last):
365+ VdtParamError: passed an incorrect value "jedi" for parameter "yoda".
366+ """
367+ SyntaxError.__init__(self, 'passed an incorrect value "%s" for parameter "%s".' % (value, name))
368+
369+
370+class VdtTypeError(ValidateError):
371+ """The value supplied was of the wrong type"""
372+
373+ def __init__(self, value):
374+ """
375+ >>> raise VdtTypeError('jedi')
376+ Traceback (most recent call last):
377+ VdtTypeError: the value "jedi" is of the wrong type.
378+ """
379+ ValidateError.__init__(self, 'the value "%s" is of the wrong type.' % (value,))
380+
381+
382+class VdtValueError(ValidateError):
383+ """The value supplied was of the correct type, but was not an allowed value."""
384+
385+ def __init__(self, value):
386+ """
387+ >>> raise VdtValueError('jedi')
388+ Traceback (most recent call last):
389+ VdtValueError: the value "jedi" is unacceptable.
390+ """
391+ ValidateError.__init__(self, 'the value "%s" is unacceptable.' % (value,))
392+
393+
394+class VdtValueTooSmallError(VdtValueError):
395+ """The value supplied was of the correct type, but was too small."""
396+
397+ def __init__(self, value):
398+ """
399+ >>> raise VdtValueTooSmallError('0')
400+ Traceback (most recent call last):
401+ VdtValueTooSmallError: the value "0" is too small.
402+ """
403+ ValidateError.__init__(self, 'the value "%s" is too small.' % (value,))
404+
405+
406+class VdtValueTooBigError(VdtValueError):
407+ """The value supplied was of the correct type, but was too big."""
408+
409+ def __init__(self, value):
410+ """
411+ >>> raise VdtValueTooBigError('1')
412+ Traceback (most recent call last):
413+ VdtValueTooBigError: the value "1" is too big.
414+ """
415+ ValidateError.__init__(self, 'the value "%s" is too big.' % (value,))
416+
417+
418+class VdtValueTooShortError(VdtValueError):
419+ """The value supplied was of the correct type, but was too short."""
420+
421+ def __init__(self, value):
422+ """
423+ >>> raise VdtValueTooShortError('jed')
424+ Traceback (most recent call last):
425+ VdtValueTooShortError: the value "jed" is too short.
426+ """
427+ ValidateError.__init__(
428+ self,
429+ 'the value "%s" is too short.' % (value,))
430+
431+
432+class VdtValueTooLongError(VdtValueError):
433+ """The value supplied was of the correct type, but was too long."""
434+
435+ def __init__(self, value):
436+ """
437+ >>> raise VdtValueTooLongError('jedie')
438+ Traceback (most recent call last):
439+ VdtValueTooLongError: the value "jedie" is too long.
440+ """
441+ ValidateError.__init__(self, 'the value "%s" is too long.' % (value,))
442+
443+
444+class Validator(object):
445+ """
446+ Validator is an object that allows you to register a set of 'checks'.
447+ These checks take input and test that it conforms to the check.
448+
449+ This can also involve converting the value from a string into
450+ the correct datatype.
451+
452+ The ``check`` method takes an input string which configures which
453+ check is to be used and applies that check to a supplied value.
454+
455+ An example input string would be:
456+ 'int_range(param1, param2)'
457+
458+ You would then provide something like:
459+
460+ >>> def int_range_check(value, min, max):
461+ ... # turn min and max from strings to integers
462+ ... min = int(min)
463+ ... max = int(max)
464+ ... # check that value is of the correct type.
465+ ... # possible valid inputs are integers or strings
466+ ... # that represent integers
467+ ... if not isinstance(value, (int, long, basestring)):
468+ ... raise VdtTypeError(value)
469+ ... elif isinstance(value, basestring):
470+ ... # if we are given a string
471+ ... # attempt to convert to an integer
472+ ... try:
473+ ... value = int(value)
474+ ... except ValueError:
475+ ... raise VdtValueError(value)
476+ ... # check the value is between our constraints
477+ ... if not min <= value:
478+ ... raise VdtValueTooSmallError(value)
479+ ... if not value <= max:
480+ ... raise VdtValueTooBigError(value)
481+ ... return value
482+
483+ >>> fdict = {'int_range': int_range_check}
484+ >>> vtr1 = Validator(fdict)
485+ >>> vtr1.check('int_range(20, 40)', '30')
486+ 30
487+ >>> vtr1.check('int_range(20, 40)', '60')
488+ Traceback (most recent call last):
489+ VdtValueTooBigError: the value "60" is too big.
490+
491+ New functions can be added with : ::
492+
493+ >>> vtr2 = Validator()
494+ >>> vtr2.functions['int_range'] = int_range_check
495+
496+ Or by passing in a dictionary of functions when Validator
497+ is instantiated.
498+
499+ Your functions *can* use keyword arguments,
500+ but the first argument should always be 'value'.
501+
502+ If the function doesn't take additional arguments,
503+ the parentheses are optional in the check.
504+ It can be written with either of : ::
505+
506+ keyword = function_name
507+ keyword = function_name()
508+
509+ The first program to utilise Validator() was Michael Foord's
510+ ConfigObj, an alternative to ConfigParser which supports lists and
511+ can validate a config file using a config schema.
512+ For more details on using Validator with ConfigObj see:
513+ http://www.voidspace.org.uk/python/configobj.html
514+ """
515+
516+ # this regex does the initial parsing of the checks
517+ _func_re = re.compile(r'(.+?)\((.*)\)', re.DOTALL)
518+
519+ # this regex takes apart keyword arguments
520+ _key_arg = re.compile(r'^([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.*)$', re.DOTALL)
521+
522+
523+ # this regex finds keyword=list(....) type values
524+ _list_arg = _list_arg
525+
526+ # this regex takes individual values out of lists - in one pass
527+ _list_members = _list_members
528+
529+ # These regexes check a set of arguments for validity
530+ # and then pull the members out
531+ _paramfinder = re.compile(_paramstring, re.VERBOSE | re.DOTALL)
532+ _matchfinder = re.compile(_matchstring, re.VERBOSE | re.DOTALL)
533+
534+
535+ def __init__(self, functions=None):
536+ """
537+ >>> vtri = Validator()
538+ """
539+ self.functions = {
540+ '': self._pass,
541+ 'integer': is_integer,
542+ 'float': is_float,
543+ 'boolean': is_boolean,
544+ 'ip_addr': is_ip_addr,
545+ 'string': is_string,
546+ 'list': is_list,
547+ 'tuple': is_tuple,
548+ 'int_list': is_int_list,
549+ 'float_list': is_float_list,
550+ 'bool_list': is_bool_list,
551+ 'ip_addr_list': is_ip_addr_list,
552+ 'string_list': is_string_list,
553+ 'mixed_list': is_mixed_list,
554+ 'pass': self._pass,
555+ 'option': is_option,
556+ 'force_list': force_list,
557+ }
558+ if functions is not None:
559+ self.functions.update(functions)
560+ # tekNico: for use by ConfigObj
561+ self.baseErrorClass = ValidateError
562+ self._cache = {}
563+
564+
565+ def check(self, check, value, missing=False):
566+ """
567+ Usage: check(check, value)
568+
569+ Arguments:
570+ check: string representing check to apply (including arguments)
571+ value: object to be checked
572+ Returns value, converted to correct type if necessary
573+
574+ If the check fails, raises a ``ValidateError`` subclass.
575+
576+ >>> vtor.check('yoda', '')
577+ Traceback (most recent call last):
578+ VdtUnknownCheckError: the check "yoda" is unknown.
579+ >>> vtor.check('yoda()', '')
580+ Traceback (most recent call last):
581+ VdtUnknownCheckError: the check "yoda" is unknown.
582+
583+ >>> vtor.check('string(default="")', '', missing=True)
584+ ''
585+ """
586+ fun_name, fun_args, fun_kwargs, default = self._parse_with_caching(check)
587+
588+ if missing:
589+ if default is None:
590+ # no information needed here - to be handled by caller
591+ raise VdtMissingValue()
592+ value = self._handle_none(default)
593+
594+ if value is None:
595+ return None
596+
597+ return self._check_value(value, fun_name, fun_args, fun_kwargs)
598+
599+
600+ def _handle_none(self, value):
601+ if value == 'None':
602+ return None
603+ elif value in ("'None'", '"None"'):
604+ # Special case a quoted None
605+ value = self._unquote(value)
606+ return value
607+
608+
609+ def _parse_with_caching(self, check):
610+ if check in self._cache:
611+ fun_name, fun_args, fun_kwargs, default = self._cache[check]
612+ # We call list and dict below to work with *copies* of the data
613+ # rather than the original (which are mutable of course)
614+ fun_args = list(fun_args)
615+ fun_kwargs = dict(fun_kwargs)
616+ else:
617+ fun_name, fun_args, fun_kwargs, default = self._parse_check(check)
618+ fun_kwargs = dict([(str(key), value) for (key, value) in fun_kwargs.items()])
619+ self._cache[check] = fun_name, list(fun_args), dict(fun_kwargs), default
620+ return fun_name, fun_args, fun_kwargs, default
621+
622+
623+ def _check_value(self, value, fun_name, fun_args, fun_kwargs):
624+ try:
625+ fun = self.functions[fun_name]
626+ except KeyError:
627+ raise VdtUnknownCheckError(fun_name)
628+ else:
629+ return fun(value, *fun_args, **fun_kwargs)
630+
631+
632+ def _parse_check(self, check):
633+ fun_match = self._func_re.match(check)
634+ if fun_match:
635+ fun_name = fun_match.group(1)
636+ arg_string = fun_match.group(2)
637+ arg_match = self._matchfinder.match(arg_string)
638+ if arg_match is None:
639+ # Bad syntax
640+ raise VdtParamError('Bad syntax in check "%s".' % check)
641+ fun_args = []
642+ fun_kwargs = {}
643+ # pull out args of group 2
644+ for arg in self._paramfinder.findall(arg_string):
645+ # args may need whitespace removing (before removing quotes)
646+ arg = arg.strip()
647+ listmatch = self._list_arg.match(arg)
648+ if listmatch:
649+ key, val = self._list_handle(listmatch)
650+ fun_kwargs[key] = val
651+ continue
652+ keymatch = self._key_arg.match(arg)
653+ if keymatch:
654+ val = keymatch.group(2)
655+ if not val in ("'None'", '"None"'):
656+ # Special case a quoted None
657+ val = self._unquote(val)
658+ fun_kwargs[keymatch.group(1)] = val
659+ continue
660+
661+ fun_args.append(self._unquote(arg))
662+ else:
663+ # allows for function names without (args)
664+ return check, (), {}, None
665+
666+ # Default must be deleted if the value is specified too,
667+ # otherwise the check function will get a spurious "default" keyword arg
668+ default = fun_kwargs.pop('default', None)
669+ return fun_name, fun_args, fun_kwargs, default
670+
671+
672+ def _unquote(self, val):
673+ """Unquote a value if necessary."""
674+ if (len(val) >= 2) and (val[0] in ("'", '"')) and (val[0] == val[-1]):
675+ val = val[1:-1]
676+ return val
677+
678+
679+ def _list_handle(self, listmatch):
680+ """Take apart a ``keyword=list('val, 'val')`` type string."""
681+ out = []
682+ name = listmatch.group(1)
683+ args = listmatch.group(2)
684+ for arg in self._list_members.findall(args):
685+ out.append(self._unquote(arg))
686+ return name, out
687+
688+
689+ def _pass(self, value):
690+ """
691+ Dummy check that always passes
692+
693+ >>> vtor.check('', 0)
694+ 0
695+ >>> vtor.check('', '0')
696+ '0'
697+ """
698+ return value
699+
700+
701+ def get_default_value(self, check):
702+ """
703+ Given a check, return the default value for the check
704+ (converted to the right type).
705+
706+ If the check doesn't specify a default value then a
707+ ``KeyError`` will be raised.
708+ """
709+ fun_name, fun_args, fun_kwargs, default = self._parse_with_caching(check)
710+ if default is None:
711+ raise KeyError('Check "%s" has no default value.' % check)
712+ value = self._handle_none(default)
713+ if value is None:
714+ return value
715+ return self._check_value(value, fun_name, fun_args, fun_kwargs)
716+
717+
718+def _is_num_param(names, values, to_float=False):
719+ """
720+ Return numbers from inputs or raise VdtParamError.
721+
722+ Lets ``None`` pass through.
723+ Pass in keyword argument ``to_float=True`` to
724+ use float for the conversion rather than int.
725+
726+ >>> _is_num_param(('', ''), (0, 1.0))
727+ [0, 1]
728+ >>> _is_num_param(('', ''), (0, 1.0), to_float=True)
729+ [0.0, 1.0]
730+ >>> _is_num_param(('a'), ('a'))
731+ Traceback (most recent call last):
732+ VdtParamError: passed an incorrect value "a" for parameter "a".
733+ """
734+ fun = to_float and float or int
735+ out_params = []
736+ for (name, val) in zip(names, values):
737+ if val is None:
738+ out_params.append(val)
739+ elif isinstance(val, (int, long, float, basestring)):
740+ try:
741+ out_params.append(fun(val))
742+ except ValueError, e:
743+ raise VdtParamError(name, val)
744+ else:
745+ raise VdtParamError(name, val)
746+ return out_params
747+
748+
749+# built in checks
750+# you can override these by setting the appropriate name
751+# in Validator.functions
752+# note: if the params are specified wrongly in your input string,
753+# you will also raise errors.
754+
755+def is_integer(value, min=None, max=None):
756+ """
757+ A check that tests that a given value is an integer (int, or long)
758+ and optionally, between bounds. A negative value is accepted, while
759+ a float will fail.
760+
761+ If the value is a string, then the conversion is done - if possible.
762+ Otherwise a VdtError is raised.
763+
764+ >>> vtor.check('integer', '-1')
765+ -1
766+ >>> vtor.check('integer', '0')
767+ 0
768+ >>> vtor.check('integer', 9)
769+ 9
770+ >>> vtor.check('integer', 'a')
771+ Traceback (most recent call last):
772+ VdtTypeError: the value "a" is of the wrong type.
773+ >>> vtor.check('integer', '2.2')
774+ Traceback (most recent call last):
775+ VdtTypeError: the value "2.2" is of the wrong type.
776+ >>> vtor.check('integer(10)', '20')
777+ 20
778+ >>> vtor.check('integer(max=20)', '15')
779+ 15
780+ >>> vtor.check('integer(10)', '9')
781+ Traceback (most recent call last):
782+ VdtValueTooSmallError: the value "9" is too small.
783+ >>> vtor.check('integer(10)', 9)
784+ Traceback (most recent call last):
785+ VdtValueTooSmallError: the value "9" is too small.
786+ >>> vtor.check('integer(max=20)', '35')
787+ Traceback (most recent call last):
788+ VdtValueTooBigError: the value "35" is too big.
789+ >>> vtor.check('integer(max=20)', 35)
790+ Traceback (most recent call last):
791+ VdtValueTooBigError: the value "35" is too big.
792+ >>> vtor.check('integer(0, 9)', False)
793+ 0
794+ """
795+ (min_val, max_val) = _is_num_param(('min', 'max'), (min, max))
796+ if not isinstance(value, (int, long, basestring)):
797+ raise VdtTypeError(value)
798+ if isinstance(value, basestring):
799+ # if it's a string - does it represent an integer ?
800+ try:
801+ value = int(value)
802+ except ValueError:
803+ raise VdtTypeError(value)
804+ if (min_val is not None) and (value < min_val):
805+ raise VdtValueTooSmallError(value)
806+ if (max_val is not None) and (value > max_val):
807+ raise VdtValueTooBigError(value)
808+ return value
809+
810+
811+def is_float(value, min=None, max=None):
812+ """
813+ A check that tests that a given value is a float
814+ (an integer will be accepted), and optionally - that it is between bounds.
815+
816+ If the value is a string, then the conversion is done - if possible.
817+ Otherwise a VdtError is raised.
818+
819+ This can accept negative values.
820+
821+ >>> vtor.check('float', '2')
822+ 2.0
823+
824+ From now on we multiply the value to avoid comparing decimals
825+
826+ >>> vtor.check('float', '-6.8') * 10
827+ -68.0
828+ >>> vtor.check('float', '12.2') * 10
829+ 122.0
830+ >>> vtor.check('float', 8.4) * 10
831+ 84.0
832+ >>> vtor.check('float', 'a')
833+ Traceback (most recent call last):
834+ VdtTypeError: the value "a" is of the wrong type.
835+ >>> vtor.check('float(10.1)', '10.2') * 10
836+ 102.0
837+ >>> vtor.check('float(max=20.2)', '15.1') * 10
838+ 151.0
839+ >>> vtor.check('float(10.0)', '9.0')
840+ Traceback (most recent call last):
841+ VdtValueTooSmallError: the value "9.0" is too small.
842+ >>> vtor.check('float(max=20.0)', '35.0')
843+ Traceback (most recent call last):
844+ VdtValueTooBigError: the value "35.0" is too big.
845+ """
846+ (min_val, max_val) = _is_num_param(
847+ ('min', 'max'), (min, max), to_float=True)
848+ if not isinstance(value, (int, long, float, basestring)):
849+ raise VdtTypeError(value)
850+ if not isinstance(value, float):
851+ # if it's a string - does it represent a float ?
852+ try:
853+ value = float(value)
854+ except ValueError:
855+ raise VdtTypeError(value)
856+ if (min_val is not None) and (value < min_val):
857+ raise VdtValueTooSmallError(value)
858+ if (max_val is not None) and (value > max_val):
859+ raise VdtValueTooBigError(value)
860+ return value
861+
862+
863+bool_dict = {
864+ True: True, 'on': True, '1': True, 'true': True, 'yes': True,
865+ False: False, 'off': False, '0': False, 'false': False, 'no': False,
866+}
867+
868+
869+def is_boolean(value):
870+ """
871+ Check if the value represents a boolean.
872+
873+ >>> vtor.check('boolean', 0)
874+ 0
875+ >>> vtor.check('boolean', False)
876+ 0
877+ >>> vtor.check('boolean', '0')
878+ 0
879+ >>> vtor.check('boolean', 'off')
880+ 0
881+ >>> vtor.check('boolean', 'false')
882+ 0
883+ >>> vtor.check('boolean', 'no')
884+ 0
885+ >>> vtor.check('boolean', 'nO')
886+ 0
887+ >>> vtor.check('boolean', 'NO')
888+ 0
889+ >>> vtor.check('boolean', 1)
890+ 1
891+ >>> vtor.check('boolean', True)
892+ 1
893+ >>> vtor.check('boolean', '1')
894+ 1
895+ >>> vtor.check('boolean', 'on')
896+ 1
897+ >>> vtor.check('boolean', 'true')
898+ 1
899+ >>> vtor.check('boolean', 'yes')
900+ 1
901+ >>> vtor.check('boolean', 'Yes')
902+ 1
903+ >>> vtor.check('boolean', 'YES')
904+ 1
905+ >>> vtor.check('boolean', '')
906+ Traceback (most recent call last):
907+ VdtTypeError: the value "" is of the wrong type.
908+ >>> vtor.check('boolean', 'up')
909+ Traceback (most recent call last):
910+ VdtTypeError: the value "up" is of the wrong type.
911+
912+ """
913+ if isinstance(value, basestring):
914+ try:
915+ return bool_dict[value.lower()]
916+ except KeyError:
917+ raise VdtTypeError(value)
918+ # we do an equality test rather than an identity test
919+ # this ensures Python 2.2 compatibilty
920+ # and allows 0 and 1 to represent True and False
921+ if value == False:
922+ return False
923+ elif value == True:
924+ return True
925+ else:
926+ raise VdtTypeError(value)
927+
928+
929+def is_ip_addr(value):
930+ """
931+ Check that the supplied value is an Internet Protocol address, v.4,
932+ represented by a dotted-quad string, i.e. '1.2.3.4'.
933+
934+ >>> vtor.check('ip_addr', '1 ')
935+ '1'
936+ >>> vtor.check('ip_addr', ' 1.2')
937+ '1.2'
938+ >>> vtor.check('ip_addr', ' 1.2.3 ')
939+ '1.2.3'
940+ >>> vtor.check('ip_addr', '1.2.3.4')
941+ '1.2.3.4'
942+ >>> vtor.check('ip_addr', '0.0.0.0')
943+ '0.0.0.0'
944+ >>> vtor.check('ip_addr', '255.255.255.255')
945+ '255.255.255.255'
946+ >>> vtor.check('ip_addr', '255.255.255.256')
947+ Traceback (most recent call last):
948+ VdtValueError: the value "255.255.255.256" is unacceptable.
949+ >>> vtor.check('ip_addr', '1.2.3.4.5')
950+ Traceback (most recent call last):
951+ VdtValueError: the value "1.2.3.4.5" is unacceptable.
952+ >>> vtor.check('ip_addr', 0)
953+ Traceback (most recent call last):
954+ VdtTypeError: the value "0" is of the wrong type.
955+ """
956+ if not isinstance(value, basestring):
957+ raise VdtTypeError(value)
958+ value = value.strip()
959+ try:
960+ dottedQuadToNum(value)
961+ except ValueError:
962+ raise VdtValueError(value)
963+ return value
964+
965+
966+def is_list(value, min=None, max=None):
967+ """
968+ Check that the value is a list of values.
969+
970+ You can optionally specify the minimum and maximum number of members.
971+
972+ It does no check on list members.
973+
974+ >>> vtor.check('list', ())
975+ []
976+ >>> vtor.check('list', [])
977+ []
978+ >>> vtor.check('list', (1, 2))
979+ [1, 2]
980+ >>> vtor.check('list', [1, 2])
981+ [1, 2]
982+ >>> vtor.check('list(3)', (1, 2))
983+ Traceback (most recent call last):
984+ VdtValueTooShortError: the value "(1, 2)" is too short.
985+ >>> vtor.check('list(max=5)', (1, 2, 3, 4, 5, 6))
986+ Traceback (most recent call last):
987+ VdtValueTooLongError: the value "(1, 2, 3, 4, 5, 6)" is too long.
988+ >>> vtor.check('list(min=3, max=5)', (1, 2, 3, 4))
989+ [1, 2, 3, 4]
990+ >>> vtor.check('list', 0)
991+ Traceback (most recent call last):
992+ VdtTypeError: the value "0" is of the wrong type.
993+ >>> vtor.check('list', '12')
994+ Traceback (most recent call last):
995+ VdtTypeError: the value "12" is of the wrong type.
996+ """
997+ (min_len, max_len) = _is_num_param(('min', 'max'), (min, max))
998+ if isinstance(value, basestring):
999+ raise VdtTypeError(value)
1000+ try:
1001+ num_members = len(value)
1002+ except TypeError:
1003+ raise VdtTypeError(value)
1004+ if min_len is not None and num_members < min_len:
1005+ raise VdtValueTooShortError(value)
1006+ if max_len is not None and num_members > max_len:
1007+ raise VdtValueTooLongError(value)
1008+ return list(value)
1009+
1010+
1011+def is_tuple(value, min=None, max=None):
1012+ """
1013+ Check that the value is a tuple of values.
1014+
1015+ You can optionally specify the minimum and maximum number of members.
1016+
1017+ It does no check on members.
1018+
1019+ >>> vtor.check('tuple', ())
1020+ ()
1021+ >>> vtor.check('tuple', [])
1022+ ()
1023+ >>> vtor.check('tuple', (1, 2))
1024+ (1, 2)
1025+ >>> vtor.check('tuple', [1, 2])
1026+ (1, 2)
1027+ >>> vtor.check('tuple(3)', (1, 2))
1028+ Traceback (most recent call last):
1029+ VdtValueTooShortError: the value "(1, 2)" is too short.
1030+ >>> vtor.check('tuple(max=5)', (1, 2, 3, 4, 5, 6))
1031+ Traceback (most recent call last):
1032+ VdtValueTooLongError: the value "(1, 2, 3, 4, 5, 6)" is too long.
1033+ >>> vtor.check('tuple(min=3, max=5)', (1, 2, 3, 4))
1034+ (1, 2, 3, 4)
1035+ >>> vtor.check('tuple', 0)
1036+ Traceback (most recent call last):
1037+ VdtTypeError: the value "0" is of the wrong type.
1038+ >>> vtor.check('tuple', '12')
1039+ Traceback (most recent call last):
1040+ VdtTypeError: the value "12" is of the wrong type.
1041+ """
1042+ return tuple(is_list(value, min, max))
1043+
1044+
1045+def is_string(value, min=None, max=None):
1046+ """
1047+ Check that the supplied value is a string.
1048+
1049+ You can optionally specify the minimum and maximum number of members.
1050+
1051+ >>> vtor.check('string', '0')
1052+ '0'
1053+ >>> vtor.check('string', 0)
1054+ Traceback (most recent call last):
1055+ VdtTypeError: the value "0" is of the wrong type.
1056+ >>> vtor.check('string(2)', '12')
1057+ '12'
1058+ >>> vtor.check('string(2)', '1')
1059+ Traceback (most recent call last):
1060+ VdtValueTooShortError: the value "1" is too short.
1061+ >>> vtor.check('string(min=2, max=3)', '123')
1062+ '123'
1063+ >>> vtor.check('string(min=2, max=3)', '1234')
1064+ Traceback (most recent call last):
1065+ VdtValueTooLongError: the value "1234" is too long.
1066+ """
1067+ if not isinstance(value, basestring):
1068+ raise VdtTypeError(value)
1069+ (min_len, max_len) = _is_num_param(('min', 'max'), (min, max))
1070+ try:
1071+ num_members = len(value)
1072+ except TypeError:
1073+ raise VdtTypeError(value)
1074+ if min_len is not None and num_members < min_len:
1075+ raise VdtValueTooShortError(value)
1076+ if max_len is not None and num_members > max_len:
1077+ raise VdtValueTooLongError(value)
1078+ return value
1079+
1080+
1081+def is_int_list(value, min=None, max=None):
1082+ """
1083+ Check that the value is a list of integers.
1084+
1085+ You can optionally specify the minimum and maximum number of members.
1086+
1087+ Each list member is checked that it is an integer.
1088+
1089+ >>> vtor.check('int_list', ())
1090+ []
1091+ >>> vtor.check('int_list', [])
1092+ []
1093+ >>> vtor.check('int_list', (1, 2))
1094+ [1, 2]
1095+ >>> vtor.check('int_list', [1, 2])
1096+ [1, 2]
1097+ >>> vtor.check('int_list', [1, 'a'])
1098+ Traceback (most recent call last):
1099+ VdtTypeError: the value "a" is of the wrong type.
1100+ """
1101+ return [is_integer(mem) for mem in is_list(value, min, max)]
1102+
1103+
1104+def is_bool_list(value, min=None, max=None):
1105+ """
1106+ Check that the value is a list of booleans.
1107+
1108+ You can optionally specify the minimum and maximum number of members.
1109+
1110+ Each list member is checked that it is a boolean.
1111+
1112+ >>> vtor.check('bool_list', ())
1113+ []
1114+ >>> vtor.check('bool_list', [])
1115+ []
1116+ >>> check_res = vtor.check('bool_list', (True, False))
1117+ >>> check_res == [True, False]
1118+ 1
1119+ >>> check_res = vtor.check('bool_list', [True, False])
1120+ >>> check_res == [True, False]
1121+ 1
1122+ >>> vtor.check('bool_list', [True, 'a'])
1123+ Traceback (most recent call last):
1124+ VdtTypeError: the value "a" is of the wrong type.
1125+ """
1126+ return [is_boolean(mem) for mem in is_list(value, min, max)]
1127+
1128+
1129+def is_float_list(value, min=None, max=None):
1130+ """
1131+ Check that the value is a list of floats.
1132+
1133+ You can optionally specify the minimum and maximum number of members.
1134+
1135+ Each list member is checked that it is a float.
1136+
1137+ >>> vtor.check('float_list', ())
1138+ []
1139+ >>> vtor.check('float_list', [])
1140+ []
1141+ >>> vtor.check('float_list', (1, 2.0))
1142+ [1.0, 2.0]
1143+ >>> vtor.check('float_list', [1, 2.0])
1144+ [1.0, 2.0]
1145+ >>> vtor.check('float_list', [1, 'a'])
1146+ Traceback (most recent call last):
1147+ VdtTypeError: the value "a" is of the wrong type.
1148+ """
1149+ return [is_float(mem) for mem in is_list(value, min, max)]
1150+
1151+
1152+def is_string_list(value, min=None, max=None):
1153+ """
1154+ Check that the value is a list of strings.
1155+
1156+ You can optionally specify the minimum and maximum number of members.
1157+
1158+ Each list member is checked that it is a string.
1159+
1160+ >>> vtor.check('string_list', ())
1161+ []
1162+ >>> vtor.check('string_list', [])
1163+ []
1164+ >>> vtor.check('string_list', ('a', 'b'))
1165+ ['a', 'b']
1166+ >>> vtor.check('string_list', ['a', 1])
1167+ Traceback (most recent call last):
1168+ VdtTypeError: the value "1" is of the wrong type.
1169+ >>> vtor.check('string_list', 'hello')
1170+ Traceback (most recent call last):
1171+ VdtTypeError: the value "hello" is of the wrong type.
1172+ """
1173+ if isinstance(value, basestring):
1174+ raise VdtTypeError(value)
1175+ return [is_string(mem) for mem in is_list(value, min, max)]
1176+
1177+
1178+def is_ip_addr_list(value, min=None, max=None):
1179+ """
1180+ Check that the value is a list of IP addresses.
1181+
1182+ You can optionally specify the minimum and maximum number of members.
1183+
1184+ Each list member is checked that it is an IP address.
1185+
1186+ >>> vtor.check('ip_addr_list', ())
1187+ []
1188+ >>> vtor.check('ip_addr_list', [])
1189+ []
1190+ >>> vtor.check('ip_addr_list', ('1.2.3.4', '5.6.7.8'))
1191+ ['1.2.3.4', '5.6.7.8']
1192+ >>> vtor.check('ip_addr_list', ['a'])
1193+ Traceback (most recent call last):
1194+ VdtValueError: the value "a" is unacceptable.
1195+ """
1196+ return [is_ip_addr(mem) for mem in is_list(value, min, max)]
1197+
1198+
1199+def force_list(value, min=None, max=None):
1200+ """
1201+ Check that a value is a list, coercing strings into
1202+ a list with one member. Useful where users forget the
1203+ trailing comma that turns a single value into a list.
1204+
1205+ You can optionally specify the minimum and maximum number of members.
1206+ A minumum of greater than one will fail if the user only supplies a
1207+ string.
1208+
1209+ >>> vtor.check('force_list', ())
1210+ []
1211+ >>> vtor.check('force_list', [])
1212+ []
1213+ >>> vtor.check('force_list', 'hello')
1214+ ['hello']
1215+ """
1216+ if not isinstance(value, (list, tuple)):
1217+ value = [value]
1218+ return is_list(value, min, max)
1219+
1220+
1221+
1222+fun_dict = {
1223+ 'integer': is_integer,
1224+ 'float': is_float,
1225+ 'ip_addr': is_ip_addr,
1226+ 'string': is_string,
1227+ 'boolean': is_boolean,
1228+}
1229+
1230+
1231+def is_mixed_list(value, *args):
1232+ """
1233+ Check that the value is a list.
1234+ Allow specifying the type of each member.
1235+ Work on lists of specific lengths.
1236+
1237+ You specify each member as a positional argument specifying type
1238+
1239+ Each type should be one of the following strings :
1240+ 'integer', 'float', 'ip_addr', 'string', 'boolean'
1241+
1242+ So you can specify a list of two strings, followed by
1243+ two integers as :
1244+
1245+ mixed_list('string', 'string', 'integer', 'integer')
1246+
1247+ The length of the list must match the number of positional
1248+ arguments you supply.
1249+
1250+ >>> mix_str = "mixed_list('integer', 'float', 'ip_addr', 'string', 'boolean')"
1251+ >>> check_res = vtor.check(mix_str, (1, 2.0, '1.2.3.4', 'a', True))
1252+ >>> check_res == [1, 2.0, '1.2.3.4', 'a', True]
1253+ 1
1254+ >>> check_res = vtor.check(mix_str, ('1', '2.0', '1.2.3.4', 'a', 'True'))
1255+ >>> check_res == [1, 2.0, '1.2.3.4', 'a', True]
1256+ 1
1257+ >>> vtor.check(mix_str, ('b', 2.0, '1.2.3.4', 'a', True))
1258+ Traceback (most recent call last):
1259+ VdtTypeError: the value "b" is of the wrong type.
1260+ >>> vtor.check(mix_str, (1, 2.0, '1.2.3.4', 'a'))
1261+ Traceback (most recent call last):
1262+ VdtValueTooShortError: the value "(1, 2.0, '1.2.3.4', 'a')" is too short.
1263+ >>> vtor.check(mix_str, (1, 2.0, '1.2.3.4', 'a', 1, 'b'))
1264+ Traceback (most recent call last):
1265+ VdtValueTooLongError: the value "(1, 2.0, '1.2.3.4', 'a', 1, 'b')" is too long.
1266+ >>> vtor.check(mix_str, 0)
1267+ Traceback (most recent call last):
1268+ VdtTypeError: the value "0" is of the wrong type.
1269+
1270+ This test requires an elaborate setup, because of a change in error string
1271+ output from the interpreter between Python 2.2 and 2.3 .
1272+
1273+ >>> res_seq = (
1274+ ... 'passed an incorrect value "',
1275+ ... 'yoda',
1276+ ... '" for parameter "mixed_list".',
1277+ ... )
1278+ >>> res_str = "'".join(res_seq)
1279+ >>> try:
1280+ ... vtor.check('mixed_list("yoda")', ('a'))
1281+ ... except VdtParamError, err:
1282+ ... str(err) == res_str
1283+ 1
1284+ """
1285+ try:
1286+ length = len(value)
1287+ except TypeError:
1288+ raise VdtTypeError(value)
1289+ if length < len(args):
1290+ raise VdtValueTooShortError(value)
1291+ elif length > len(args):
1292+ raise VdtValueTooLongError(value)
1293+ try:
1294+ return [fun_dict[arg](val) for arg, val in zip(args, value)]
1295+ except KeyError, e:
1296+ raise VdtParamError('mixed_list', e)
1297+
1298+
1299+def is_option(value, *options):
1300+ """
1301+ This check matches the value to any of a set of options.
1302+
1303+ >>> vtor.check('option("yoda", "jedi")', 'yoda')
1304+ 'yoda'
1305+ >>> vtor.check('option("yoda", "jedi")', 'jed')
1306+ Traceback (most recent call last):
1307+ VdtValueError: the value "jed" is unacceptable.
1308+ >>> vtor.check('option("yoda", "jedi")', 0)
1309+ Traceback (most recent call last):
1310+ VdtTypeError: the value "0" is of the wrong type.
1311+ """
1312+ if not isinstance(value, basestring):
1313+ raise VdtTypeError(value)
1314+ if not value in options:
1315+ raise VdtValueError(value)
1316+ return value
1317+
1318+
1319+def _test(value, *args, **keywargs):
1320+ """
1321+ A function that exists for test purposes.
1322+
1323+ >>> checks = [
1324+ ... '3, 6, min=1, max=3, test=list(a, b, c)',
1325+ ... '3',
1326+ ... '3, 6',
1327+ ... '3,',
1328+ ... 'min=1, test="a b c"',
1329+ ... 'min=5, test="a, b, c"',
1330+ ... 'min=1, max=3, test="a, b, c"',
1331+ ... 'min=-100, test=-99',
1332+ ... 'min=1, max=3',
1333+ ... '3, 6, test="36"',
1334+ ... '3, 6, test="a, b, c"',
1335+ ... '3, max=3, test=list("a", "b", "c")',
1336+ ... '''3, max=3, test=list("'a'", 'b', "x=(c)")''',
1337+ ... "test='x=fish(3)'",
1338+ ... ]
1339+ >>> v = Validator({'test': _test})
1340+ >>> for entry in checks:
1341+ ... print v.check(('test(%s)' % entry), 3)
1342+ (3, ('3', '6'), {'test': ['a', 'b', 'c'], 'max': '3', 'min': '1'})
1343+ (3, ('3',), {})
1344+ (3, ('3', '6'), {})
1345+ (3, ('3',), {})
1346+ (3, (), {'test': 'a b c', 'min': '1'})
1347+ (3, (), {'test': 'a, b, c', 'min': '5'})
1348+ (3, (), {'test': 'a, b, c', 'max': '3', 'min': '1'})
1349+ (3, (), {'test': '-99', 'min': '-100'})
1350+ (3, (), {'max': '3', 'min': '1'})
1351+ (3, ('3', '6'), {'test': '36'})
1352+ (3, ('3', '6'), {'test': 'a, b, c'})
1353+ (3, ('3',), {'test': ['a', 'b', 'c'], 'max': '3'})
1354+ (3, ('3',), {'test': ["'a'", 'b', 'x=(c)'], 'max': '3'})
1355+ (3, (), {'test': 'x=fish(3)'})
1356+
1357+ >>> v = Validator()
1358+ >>> v.check('integer(default=6)', '3')
1359+ 3
1360+ >>> v.check('integer(default=6)', None, True)
1361+ 6
1362+ >>> v.get_default_value('integer(default=6)')
1363+ 6
1364+ >>> v.get_default_value('float(default=6)')
1365+ 6.0
1366+ >>> v.get_default_value('pass(default=None)')
1367+ >>> v.get_default_value("string(default='None')")
1368+ 'None'
1369+ >>> v.get_default_value('pass')
1370+ Traceback (most recent call last):
1371+ KeyError: 'Check "pass" has no default value.'
1372+ >>> v.get_default_value('pass(default=list(1, 2, 3, 4))')
1373+ ['1', '2', '3', '4']
1374+
1375+ >>> v = Validator()
1376+ >>> v.check("pass(default=None)", None, True)
1377+ >>> v.check("pass(default='None')", None, True)
1378+ 'None'
1379+ >>> v.check('pass(default="None")', None, True)
1380+ 'None'
1381+ >>> v.check('pass(default=list(1, 2, 3, 4))', None, True)
1382+ ['1', '2', '3', '4']
1383+
1384+ Bug test for unicode arguments
1385+ >>> v = Validator()
1386+ >>> v.check(u'string(min=4)', u'test')
1387+ u'test'
1388+
1389+ >>> v = Validator()
1390+ >>> v.get_default_value(u'string(min=4, default="1234")')
1391+ u'1234'
1392+ >>> v.check(u'string(min=4, default="1234")', u'test')
1393+ u'test'
1394+
1395+ >>> v = Validator()
1396+ >>> default = v.get_default_value('string(default=None)')
1397+ >>> default == None
1398+ 1
1399+ """
1400+ return (value, args, keywargs)
1401+
1402+
1403+def _test2():
1404+ """
1405+ >>>
1406+ >>> v = Validator()
1407+ >>> v.get_default_value('string(default="#ff00dd")')
1408+ '#ff00dd'
1409+ >>> v.get_default_value('integer(default=3) # comment')
1410+ 3
1411+ """
1412+
1413+def _test3():
1414+ r"""
1415+ >>> vtor.check('string(default="")', '', missing=True)
1416+ ''
1417+ >>> vtor.check('string(default="\n")', '', missing=True)
1418+ '\n'
1419+ >>> print vtor.check('string(default="\n")', '', missing=True),
1420+ <BLANKLINE>
1421+ >>> vtor.check('string()', '\n')
1422+ '\n'
1423+ >>> vtor.check('string(default="\n\n\n")', '', missing=True)
1424+ '\n\n\n'
1425+ >>> vtor.check('string()', 'random \n text goes here\n\n')
1426+ 'random \n text goes here\n\n'
1427+ >>> vtor.check('string(default=" \nrandom text\ngoes \n here\n\n ")',
1428+ ... '', missing=True)
1429+ ' \nrandom text\ngoes \n here\n\n '
1430+ >>> vtor.check("string(default='\n\n\n')", '', missing=True)
1431+ '\n\n\n'
1432+ >>> vtor.check("option('\n','a','b',default='\n')", '', missing=True)
1433+ '\n'
1434+ >>> vtor.check("string_list()", ['foo', '\n', 'bar'])
1435+ ['foo', '\n', 'bar']
1436+ >>> vtor.check("string_list(default=list('\n'))", '', missing=True)
1437+ ['\n']
1438+ """
1439+
1440+
1441+if __name__ == '__main__':
1442+ # run the code tests in doctest format
1443+ import sys
1444+ import doctest
1445+ m = sys.modules.get('__main__')
1446+ globs = m.__dict__.copy()
1447+ globs.update({
1448+ 'vtor': Validator(),
1449+ })
1450+ doctest.testmod(m, globs=globs)
--- branches/tl3_0/python-lib/configobj/tests/functionaltests/conf.ini (nonexistent)
+++ branches/tl3_0/python-lib/configobj/tests/functionaltests/conf.ini (revision 40)
@@ -0,0 +1,10 @@
1+
2+extra = 3
3+
4+[extra-section]
5+
6+[section]
7+ [[sub-section]]
8+ extra = 3
9+ [[extra-sub-section]]
10+ extra = 3
--- branches/tl3_0/python-lib/configobj/tests/functionaltests/test_configobj.py (nonexistent)
+++ branches/tl3_0/python-lib/configobj/tests/functionaltests/test_configobj.py (revision 40)
@@ -0,0 +1,101 @@
1+from __future__ import with_statement
2+
3+import os
4+try:
5+ import unittest2 as unittest
6+except ImportError:
7+ import unittest
8+
9+from configobj import ConfigObj
10+
11+try:
12+ # Python 2.6 only
13+ from warnings import catch_warnings
14+except ImportError:
15+ # this will cause an error, but at least the other tests
16+ # will run on Python 2.5
17+ catch_warnings = None
18+
19+class TestConfigObj(unittest.TestCase):
20+
21+ def test_order_preserved(self):
22+ c = ConfigObj()
23+ c['a'] = 1
24+ c['b'] = 2
25+ c['c'] = 3
26+ c['section'] = {}
27+ c['section']['a'] = 1
28+ c['section']['b'] = 2
29+ c['section']['c'] = 3
30+ c['section']['section'] = {}
31+ c['section']['section2'] = {}
32+ c['section']['section3'] = {}
33+ c['section2'] = {}
34+ c['section3'] = {}
35+
36+ c2 = ConfigObj(c)
37+ self.assertEqual(c2.scalars, ['a', 'b', 'c'])
38+ self.assertEqual(c2.sections, ['section', 'section2', 'section3'])
39+ self.assertEqual(c2['section'].scalars, ['a', 'b', 'c'])
40+ self.assertEqual(c2['section'].sections, ['section', 'section2', 'section3'])
41+
42+ self.assertFalse(c['section'] is c2['section'])
43+ self.assertFalse(c['section']['section'] is c2['section']['section'])
44+
45+ if catch_warnings is not None:
46+ # poor man's skipTest
47+ def test_options_deprecation(self):
48+ with catch_warnings(record=True) as log:
49+ ConfigObj(options={})
50+
51+ # unpack the only member of log
52+ warning, = log
53+ self.assertEqual(warning.category, DeprecationWarning)
54+
55+ def test_list_members(self):
56+ c = ConfigObj()
57+ c['a'] = []
58+ c['a'].append('foo')
59+ self.assertEqual(c['a'], ['foo'])
60+
61+ def test_list_interpolation_with_pop(self):
62+ c = ConfigObj()
63+ c['a'] = []
64+ c['a'].append('%(b)s')
65+ c['b'] = 'bar'
66+ self.assertEqual(c.pop('a'), ['bar'])
67+
68+ def test_with_default(self):
69+ c = ConfigObj()
70+ c['a'] = 3
71+
72+ self.assertEqual(c.pop('a'), 3)
73+ self.assertEqual(c.pop('b', 3), 3)
74+ self.assertRaises(KeyError, c.pop, 'c')
75+
76+
77+ def test_interpolation_with_section_names(self):
78+ cfg = """
79+item1 = 1234
80+[section]
81+ [[item1]]
82+ foo='bar'
83+ [[DEFAULT]]
84+ [[[item1]]]
85+ why = would you do this?
86+ [[other-subsection]]
87+ item2 = '$item1'""".splitlines()
88+ c = ConfigObj(cfg, interpolation='Template')
89+
90+ # This raises an exception in 4.7.1 and earlier due to the section
91+ # being found as the interpolation value
92+ repr(c)
93+
94+ def test_interoplation_repr(self):
95+ c = ConfigObj(['foo = $bar'], interpolation='Template')
96+ c['baz'] = {}
97+ c['baz']['spam'] = '%(bar)s'
98+
99+ # This raises a MissingInterpolationOption exception in 4.7.1 and earlier
100+ repr(c)
101+
--- branches/tl3_0/python-lib/configobj/tests/functionaltests/test_validate_errors.py (nonexistent)
+++ branches/tl3_0/python-lib/configobj/tests/functionaltests/test_validate_errors.py (revision 40)
@@ -0,0 +1,66 @@
1+import os
2+try:
3+ import unittest2 as unittest
4+except ImportError:
5+ import unittest
6+
7+from configobj import ConfigObj, get_extra_values
8+from validate import Validator
9+
10+thisdir = os.path.dirname(os.path.join(os.getcwd(), __file__))
11+inipath = os.path.join(thisdir, 'conf.ini')
12+specpath = os.path.join(thisdir, 'conf.spec')
13+
14+
15+class TestValidateErrors(unittest.TestCase):
16+
17+ def test_validate_no_valid_entries(self):
18+ conf = ConfigObj(inipath, configspec=specpath)
19+
20+ validator = Validator()
21+ result = conf.validate(validator)
22+ self.assertFalse(result)
23+
24+
25+ def test_validate_preserve_errors(self):
26+ conf = ConfigObj(inipath, configspec=specpath)
27+
28+ validator = Validator()
29+ result = conf.validate(validator, preserve_errors=True)
30+
31+ self.assertFalse(result['value'])
32+ self.assertFalse(result['missing-section'])
33+
34+ section = result['section']
35+ self.assertFalse(section['value'])
36+ self.assertFalse(section['sub-section']['value'])
37+ self.assertFalse(section['missing-subsection'])
38+
39+
40+ def test_validate_extra_values(self):
41+ conf = ConfigObj(inipath, configspec=specpath)
42+ conf.validate(Validator(), preserve_errors=True)
43+
44+ self.assertEqual(conf.extra_values, ['extra', 'extra-section'])
45+
46+ self.assertEqual(conf['section'].extra_values, ['extra-sub-section'])
47+ self.assertEqual(conf['section']['sub-section'].extra_values,
48+ ['extra'])
49+
50+
51+ def test_get_extra_values(self):
52+ conf = ConfigObj(inipath, configspec=specpath)
53+
54+ conf.validate(Validator(), preserve_errors=True)
55+ extra_values = get_extra_values(conf)
56+
57+ expected = sorted([
58+ ((), 'extra'),
59+ ((), 'extra-section'),
60+ (('section', 'sub-section'), 'extra'),
61+ (('section',), 'extra-sub-section'),
62+ ])
63+ self.assertEqual(sorted(extra_values), expected)
64+
65+if __name__ == '__main__':
66+ unittest.main()
--- branches/tl3_0/python-lib/configobj/tests/functionaltests/conf.spec (nonexistent)
+++ branches/tl3_0/python-lib/configobj/tests/functionaltests/conf.spec (revision 40)
@@ -0,0 +1,13 @@
1+
2+value = integer
3+
4+[section]
5+ value = integer
6+
7+ [[sub-section]]
8+ value = integer
9+ [[missing-subsection]]
10+ value = integer
11+
12+[missing-section]
13+ value = integer
--- branches/tl3_0/python-lib/configobj/tests/test_configobj.py (nonexistent)
+++ branches/tl3_0/python-lib/configobj/tests/test_configobj.py (revision 40)
@@ -0,0 +1,2221 @@
1+# configobj_test.py
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
7+
8+# ConfigObj 4
9+# http://www.voidspace.org.uk/python/configobj.html
10+
11+# Released subject to the BSD License
12+# Please see http://www.voidspace.org.uk/python/license.shtml
13+
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.
19+
20+
21+from __future__ import generators
22+from StringIO import StringIO
23+
24+import os
25+import sys
26+INTP_VER = sys.version_info[:2]
27+if INTP_VER < (2, 2):
28+ raise RuntimeError("Python v.2.2 or later needed")
29+
30+try:
31+ from codecs import BOM_UTF8
32+except ImportError:
33+ # Python 2.2 does not have this
34+ # UTF-8
35+ BOM_UTF8 = '\xef\xbb\xbf'
36+
37+from configobj import *
38+from validate import Validator, VdtValueTooSmallError
39+
40+
41+"""
42+ >>> z = ConfigObj()
43+ >>> z['a'] = 'a'
44+ >>> z['sect'] = {
45+ ... 'subsect': {
46+ ... 'a': 'fish',
47+ ... 'b': 'wobble',
48+ ... },
49+ ... 'member': 'value',
50+ ... }
51+ >>> x = ConfigObj(z.write())
52+ >>> z == x
53+ 1
54+"""
55+
56+
57+def _error_test():
58+ """
59+ Testing the error classes.
60+
61+ >>> raise ConfigObjError
62+ Traceback (most recent call last):
63+ ConfigObjError
64+
65+ >>> raise NestingError
66+ Traceback (most recent call last):
67+ NestingError
68+
69+ >>> raise ParseError
70+ Traceback (most recent call last):
71+ ParseError
72+
73+ >>> raise DuplicateError
74+ Traceback (most recent call last):
75+ DuplicateError
76+
77+ >>> raise ConfigspecError
78+ Traceback (most recent call last):
79+ ConfigspecError
80+
81+ >>> raise InterpolationLoopError('yoda')
82+ Traceback (most recent call last):
83+ InterpolationLoopError: interpolation loop detected in value "yoda".
84+
85+ >>> raise RepeatSectionError
86+ Traceback (most recent call last):
87+ RepeatSectionError
88+
89+ >>> raise MissingInterpolationOption('yoda')
90+ Traceback (most recent call last):
91+ MissingInterpolationOption: missing option "yoda" in interpolation.
92+
93+
94+ >>> raise ReloadError()
95+ Traceback (most recent call last):
96+ ReloadError: reload failed, filename is not set.
97+ >>> try:
98+ ... raise ReloadError()
99+ ... except IOError:
100+ ... pass
101+ ... else:
102+ ... raise Exception('We should catch a ReloadError as an IOError')
103+ >>>
104+
105+ """
106+
107+
108+def _section_test():
109+ """
110+ Tests from Section methods.
111+
112+ >>> n = a.dict()
113+ >>> n == a
114+ 1
115+ >>> n is a
116+ 0
117+
118+ >>> a = '''[section1]
119+ ... option1 = True
120+ ... [[subsection]]
121+ ... more_options = False
122+ ... # end of file'''.splitlines()
123+ >>> b = '''# File is user.ini
124+ ... [section1]
125+ ... option1 = False
126+ ... # end of file'''.splitlines()
127+ >>> c1 = ConfigObj(b)
128+ >>> c2 = ConfigObj(a)
129+ >>> c2.merge(c1)
130+ >>> c2
131+ ConfigObj({'section1': {'option1': 'False', 'subsection': {'more_options': 'False'}}})
132+
133+ >>> config = '''[XXXXsection]
134+ ... XXXXkey = XXXXvalue'''.splitlines()
135+ >>> cfg = ConfigObj(config)
136+ >>> cfg
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)):
143+ ... pass
144+ ... else:
145+ ... val = val.replace('XXXX', 'CLIENT1')
146+ ... section[newkey] = val
147+ >>> cfg.walk(transform, call_on_sections=True)
148+ {'CLIENT1section': {'CLIENT1key': None}}
149+ >>> cfg
150+ ConfigObj({'CLIENT1section': {'CLIENT1key': 'CLIENT1value'}})
151+ """
152+
153+
154+def _test_reset():
155+ """
156+ >>> something = object()
157+ >>> c = ConfigObj()
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
180+ >>> c.reset()
181+ >>>
182+ >>> c.filename
183+ >>> c.raise_errors
184+ False
185+ >>> c.list_values
186+ True
187+ >>> c.create_empty
188+ False
189+ >>> c.file_error
190+ False
191+ >>> c.interpolation
192+ True
193+ >>> c.configspec
194+ >>> c.stringify
195+ True
196+ >>> c.indent_type
197+ >>> c.encoding
198+ >>> c.default_encoding
199+ >>> c.unrepr
200+ False
201+ >>> c.write_empty_values
202+ False
203+ >>> c.inline_comments
204+ {}
205+ >>> c.comments
206+ {}
207+ >>> c.defaults
208+ []
209+ >>> c.default_values
210+ {}
211+ >>> c == ConfigObj()
212+ True
213+ >>> c
214+ ConfigObj({})
215+ """
216+
217+
218+def _test_reload():
219+ """
220+ >>> c = ConfigObj(StringIO())
221+ >>> c.reload()
222+ Traceback (most recent call last):
223+ ReloadError: reload failed, filename is not set.
224+ >>> c = ConfigObj()
225+ >>> c.reload()
226+ Traceback (most recent call last):
227+ ReloadError: reload failed, filename is not set.
228+ >>> c = ConfigObj([])
229+ >>> c.reload()
230+ Traceback (most recent call last):
231+ ReloadError: reload failed, filename is not set.
232+
233+ We need to use a real file as reload is only for files loaded from
234+ the filesystem.
235+ >>> h = open('temp', 'w')
236+ >>> h.write('''
237+ ... test1=40
238+ ... test2=hello
239+ ... test3=3
240+ ... test4=5.0
241+ ... [section]
242+ ... test1=40
243+ ... test2=hello
244+ ... test3=3
245+ ... test4=5.0
246+ ... [[sub section]]
247+ ... test1=40
248+ ... test2=hello
249+ ... test3=3
250+ ... test4=5.0
251+ ... [section2]
252+ ... test1=40
253+ ... test2=hello
254+ ... test3=3
255+ ... test4=5.0
256+ ... ''')
257+ >>> h.close()
258+ >>> configspec = '''
259+ ... test1= integer(30,50)
260+ ... test2= string
261+ ... test3=integer
262+ ... test4=float(4.5)
263+ ... [section]
264+ ... test1=integer(30,50)
265+ ... test2=string
266+ ... test3=integer
267+ ... test4=float(4.5)
268+ ... [[sub section]]
269+ ... test1=integer(30,50)
270+ ... test2=string
271+ ... test3=integer
272+ ... test4=float(4.5)
273+ ... [section2]
274+ ... test1=integer(30,50)
275+ ... test2=string
276+ ... test3=integer
277+ ... test4=float(4.5)
278+ ... '''.split('\\n')
279+ >>> c = ConfigObj('temp', configspec=configspec)
280+ >>> c.configspec['test1'] = 'integer(50,60)'
281+ >>> backup = ConfigObj('temp')
282+ >>> del c['section']
283+ >>> del c['test1']
284+ >>> c['extra'] = '3'
285+ >>> c['section2']['extra'] = '3'
286+ >>> c.reload()
287+ >>> c == backup
288+ True
289+ >>> c.validate(Validator())
290+ True
291+ >>> os.remove('temp')
292+ """
293+
294+
295+def _doctest():
296+ """
297+ Dummy function to hold some of the doctests.
298+
299+ >>> a.depth
300+ 0
301+ >>> a == {
302+ ... 'key2': 'val',
303+ ... 'key1': 'val',
304+ ... 'lev1c': {
305+ ... 'lev2c': {
306+ ... 'lev3c': {
307+ ... 'key1': 'val',
308+ ... },
309+ ... },
310+ ... },
311+ ... 'lev1b': {
312+ ... 'key2': 'val',
313+ ... 'key1': 'val',
314+ ... 'lev2ba': {
315+ ... 'key1': 'val',
316+ ... },
317+ ... 'lev2bb': {
318+ ... 'key1': 'val',
319+ ... },
320+ ... },
321+ ... 'lev1a': {
322+ ... 'key2': 'val',
323+ ... 'key1': 'val',
324+ ... },
325+ ... }
326+ 1
327+ >>> b.depth
328+ 0
329+ >>> b == {
330+ ... 'key3': 'val3',
331+ ... 'key2': 'val2',
332+ ... 'key1': 'val1',
333+ ... 'section 1': {
334+ ... 'keys11': 'val1',
335+ ... 'keys13': 'val3',
336+ ... 'keys12': 'val2',
337+ ... },
338+ ... 'section 2': {
339+ ... 'section 2 sub 1': {
340+ ... 'fish': '3',
341+ ... },
342+ ... 'keys21': 'val1',
343+ ... 'keys22': 'val2',
344+ ... 'keys23': 'val3',
345+ ... },
346+ ... }
347+ 1
348+ >>> t = '''
349+ ... 'a' = b # !"$%^&*(),::;'@~#= 33
350+ ... "b" = b #= 6, 33
351+ ... ''' .split('\\n')
352+ >>> t2 = ConfigObj(t)
353+ >>> assert t2 == {'a': 'b', 'b': 'b'}
354+ >>> t2.inline_comments['b'] = ''
355+ >>> del t2['a']
356+ >>> assert t2.write() == ['','b = b', '']
357+
358+ # Test ``list_values=False`` stuff
359+ >>> c = '''
360+ ... key1 = no quotes
361+ ... key2 = 'single quotes'
362+ ... key3 = "double quotes"
363+ ... key4 = "list", 'with', several, "quotes"
364+ ... '''
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"'
369+ ... }
370+ 1
371+ >>> cfg = ConfigObj(list_values=False)
372+ >>> cfg['key1'] = 'Multiline\\nValue'
373+ >>> cfg['key2'] = '''"Value" with 'quotes' !'''
374+ >>> cfg.write()
375+ ["key1 = '''Multiline\\nValue'''", 'key2 = "Value" with \\'quotes\\' !']
376+ >>> cfg.list_values = True
377+ >>> cfg.write() == ["key1 = '''Multiline\\nValue'''",
378+ ... 'key2 = \\'\\'\\'"Value" with \\'quotes\\' !\\'\\'\\'']
379+ 1
380+
381+ Test flatten_errors:
382+
383+ >>> config = '''
384+ ... test1=40
385+ ... test2=hello
386+ ... test3=3
387+ ... test4=5.0
388+ ... [section]
389+ ... test1=40
390+ ... test2=hello
391+ ... test3=3
392+ ... test4=5.0
393+ ... [[sub section]]
394+ ... test1=40
395+ ... test2=hello
396+ ... test3=3
397+ ... test4=5.0
398+ ... '''.split('\\n')
399+ >>> configspec = '''
400+ ... test1= integer(30,50)
401+ ... test2= string
402+ ... test3=integer
403+ ... test4=float(6.0)
404+ ... [section]
405+ ... test1=integer(30,50)
406+ ... test2=string
407+ ... test3=integer
408+ ... test4=float(6.0)
409+ ... [[sub section]]
410+ ... test1=integer(30,50)
411+ ... test2=string
412+ ... test3=integer
413+ ... test4=float(6.0)
414+ ... '''.split('\\n')
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)]
420+ True
421+ >>> res = c1.validate(val, preserve_errors=True)
422+ >>> check = flatten_errors(c1, res)
423+ >>> check[0][:2]
424+ ([], 'test4')
425+ >>> check[1][:2]
426+ (['section', 'sub section'], 'test4')
427+ >>> check[2][:2]
428+ (['section'], 'test4')
429+ >>> for entry in check:
430+ ... isinstance(entry[2], VdtValueTooSmallError)
431+ ... print str(entry[2])
432+ True
433+ the value "5.0" is too small.
434+ True
435+ the value "5.0" is too small.
436+ True
437+ the value "5.0" is too small.
438+
439+ Test unicode handling, BOM, write witha file like object and line endings :
440+ >>> u_base = '''
441+ ... # initial comment
442+ ... # inital comment 2
443+ ...
444+ ... test1 = some value
445+ ... # comment
446+ ... test2 = another value # inline comment
447+ ... # section comment
448+ ... [section] # inline comment
449+ ... test = test # another inline comment
450+ ... test2 = test2
451+ ...
452+ ... # final comment
453+ ... # final comment2
454+ ... '''
455+ >>> u = u_base.encode('utf_8').splitlines(True)
456+ >>> u[0] = BOM_UTF8 + u[0]
457+ >>> uc = ConfigObj(u)
458+ >>> uc.encoding = None
459+ >>> uc.BOM == True
460+ 1
461+ >>> uc == {'test1': 'some value', 'test2': 'another value',
462+ ... 'section': {'test': 'test', 'test2': 'test2'}}
463+ 1
464+ >>> uc = ConfigObj(u, encoding='utf_8', default_encoding='latin-1')
465+ >>> uc.BOM
466+ 1
467+ >>> isinstance(uc['test1'], unicode)
468+ 1
469+ >>> uc.encoding
470+ 'utf_8'
471+ >>> uc.newlines
472+ '\\n'
473+ >>> uc['latin1'] = "This costs lot's of "
474+ >>> a_list = uc.write()
475+ >>> len(a_list)
476+ 15
477+ >>> isinstance(a_list[0], str)
478+ 1
479+ >>> a_list[0].startswith(BOM_UTF8)
480+ 1
481+ >>> u = u_base.replace('\\n', '\\r\\n').encode('utf_8').splitlines(True)
482+ >>> uc = ConfigObj(u)
483+ >>> uc.newlines
484+ '\\r\\n'
485+ >>> uc.newlines = '\\r'
486+ >>> file_like = StringIO()
487+ >>> uc.write(file_like)
488+ >>> file_like.seek(0)
489+ >>> uc2 = ConfigObj(file_like)
490+ >>> uc2 == uc
491+ 1
492+ >>> uc2.filename == None
493+ 1
494+ >>> uc2.newlines == '\\r'
495+ 1
496+
497+ Test validate in copy mode
498+ >>> a = '''
499+ ... # Initial Comment
500+ ...
501+ ... key1 = string(default=Hello)
502+ ...
503+ ... # section comment
504+ ... [section] # inline comment
505+ ... # key1 comment
506+ ... key1 = integer(default=6)
507+ ... # key2 comment
508+ ... key2 = boolean(default=True)
509+ ...
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)
516+ 1
517+ >>> b.write() == ['',
518+ ... '# Initial Comment',
519+ ... '',
520+ ... 'key1 = Hello',
521+ ... '',
522+ ... '# section comment',
523+ ... '[section] # inline comment',
524+ ... ' # key1 comment',
525+ ... ' key1 = 6',
526+ ... ' # key2 comment',
527+ ... ' key2 = True',
528+ ... ' ',
529+ ... ' # subsection comment',
530+ ... ' [[sub-section]] # inline comment',
531+ ... ' # another key1 comment',
532+ ... ' key1 = 3.0']
533+ 1
534+
535+ Test Writing Empty Values
536+ >>> a = '''
537+ ... key1 =
538+ ... key2 =# a comment'''
539+ >>> b = ConfigObj(a.splitlines())
540+ >>> b.write()
541+ ['', 'key1 = ""', 'key2 = "" # a comment']
542+ >>> b.write_empty_values = True
543+ >>> b.write()
544+ ['', 'key1 = ', 'key2 = # a comment']
545+
546+ Test unrepr when reading
547+ >>> a = '''
548+ ... key1 = (1, 2, 3) # comment
549+ ... key2 = True
550+ ... key3 = 'a string'
551+ ... key4 = [1, 2, 3, 'a mixed list']
552+ ... '''.splitlines()
553+ >>> b = ConfigObj(a, unrepr=True)
554+ >>> b == {'key1': (1, 2, 3),
555+ ... 'key2': True,
556+ ... 'key3': 'a string',
557+ ... 'key4': [1, 2, 3, 'a mixed list']}
558+ 1
559+
560+ Test unrepr when writing
561+ >>> c = ConfigObj(b.write(), unrepr=True)
562+ >>> c == b
563+ 1
564+
565+ Test unrepr with multiline values
566+ >>> a = '''k = \"""{
567+ ... 'k1': 3,
568+ ... 'k2': 6.0}\"""
569+ ... '''.splitlines()
570+ >>> c = ConfigObj(a, unrepr=True)
571+ >>> c == {'k': {'k1': 3, 'k2': 6.0}}
572+ 1
573+
574+ Test unrepr with a dictionary
575+ >>> a = 'k = {"a": 1}'.splitlines()
576+ >>> c = ConfigObj(a, unrepr=True)
577+ >>> type(c['k']) == dict
578+ 1
579+
580+ >>> a = ConfigObj()
581+ >>> a['a'] = 'fish'
582+ >>> a.as_bool('a')
583+ Traceback (most recent call last):
584+ ValueError: Value "fish" is neither True nor False
585+ >>> a['b'] = 'True'
586+ >>> a.as_bool('b')
587+ 1
588+ >>> a['b'] = 'off'
589+ >>> a.as_bool('b')
590+ 0
591+
592+ >>> a = ConfigObj()
593+ >>> a['a'] = 'fish'
594+ >>> try:
595+ ... a.as_int('a') #doctest: +ELLIPSIS
596+ ... except ValueError, e:
597+ ... err_mess = str(e)
598+ >>> err_mess.startswith('invalid literal for int()')
599+ 1
600+ >>> a['b'] = '1'
601+ >>> a.as_int('b')
602+ 1
603+ >>> a['b'] = '3.2'
604+ >>> try:
605+ ... a.as_int('b') #doctest: +ELLIPSIS
606+ ... except ValueError, e:
607+ ... err_mess = str(e)
608+ >>> err_mess.startswith('invalid literal for int()')
609+ 1
610+
611+ >>> a = ConfigObj()
612+ >>> a['a'] = 'fish'
613+ >>> a.as_float('a')
614+ Traceback (most recent call last):
615+ ValueError: invalid literal for float(): fish
616+ >>> a['b'] = '1'
617+ >>> a.as_float('b')
618+ 1.0
619+ >>> a['b'] = '3.2'
620+ >>> a.as_float('b')
621+ 3.2000000000000002
622+
623+ Test # with unrepr
624+ >>> a = '''
625+ ... key1 = (1, 2, 3) # comment
626+ ... key2 = True
627+ ... key3 = 'a string'
628+ ... key4 = [1, 2, 3, 'a mixed list#']
629+ ... '''.splitlines()
630+ >>> b = ConfigObj(a, unrepr=True)
631+ >>> b == {'key1': (1, 2, 3),
632+ ... 'key2': True,
633+ ... 'key3': 'a string',
634+ ... 'key4': [1, 2, 3, 'a mixed list#']}
635+ 1
636+ """
637+
638+ # Comments are no longer parsed from values in configspecs
639+ # so the following test fails and is disabled
640+ untested = """
641+ Test validate in copy mode
642+ >>> a = '''
643+ ... # Initial Comment
644+ ...
645+ ... key1 = string(default=Hello) # comment 1
646+ ...
647+ ... # section comment
648+ ... [section] # inline comment
649+ ... # key1 comment
650+ ... key1 = integer(default=6) # an integer value
651+ ... # key2 comment
652+ ... key2 = boolean(default=True) # a boolean
653+ ...
654+ ... # subsection comment
655+ ... [[sub-section]] # inline comment
656+ ... # another key1 comment
657+ ... key1 = float(default=3.0) # a float'''.splitlines()
658+ >>> b = ConfigObj(configspec=a)
659+ >>> b.validate(val, copy=True)
660+ 1
661+ >>> b.write()
662+ >>> b.write() == ['',
663+ ... '# Initial Comment',
664+ ... '',
665+ ... 'key1 = Hello # comment 1',
666+ ... '',
667+ ... '# section comment',
668+ ... '[section] # inline comment',
669+ ... ' # key1 comment',
670+ ... ' key1 = 6 # an integer value',
671+ ... ' # key2 comment',
672+ ... ' key2 = True # a boolean',
673+ ... ' ',
674+ ... ' # subsection comment',
675+ ... ' [[sub-section]] # inline comment',
676+ ... ' # another key1 comment',
677+ ... ' key1 = 3.0 # a float']
678+ 1
679+ """
680+
681+
682+def _test_configobj():
683+ """
684+ Testing ConfigObj
685+ Testing with duplicate keys and sections.
686+
687+ >>> c = '''
688+ ... [hello]
689+ ... member = value
690+ ... [hello again]
691+ ... member = value
692+ ... [ "hello" ]
693+ ... member = value
694+ ... '''
695+ >>> ConfigObj(c.split('\\n'), raise_errors = True)
696+ Traceback (most recent call last):
697+ DuplicateError: Duplicate section name at line 6.
698+
699+ >>> d = '''
700+ ... [hello]
701+ ... member = value
702+ ... [hello again]
703+ ... member1 = value
704+ ... member2 = value
705+ ... 'member1' = value
706+ ... [ "and again" ]
707+ ... member = value
708+ ... '''
709+ >>> ConfigObj(d.split('\\n'), raise_errors = True)
710+ Traceback (most recent call last):
711+ DuplicateError: Duplicate keyword name at line 7.
712+
713+ Testing ConfigParser-style interpolation
714+
715+ >>> c = ConfigObj()
716+ >>> c['DEFAULT'] = {
717+ ... 'b': 'goodbye',
718+ ... 'userdir': 'c:\\\\home',
719+ ... 'c': '%(d)s',
720+ ... 'd': '%(c)s'
721+ ... }
722+ >>> c['section'] = {
723+ ... 'a': '%(datadir)s\\\\some path\\\\file.py',
724+ ... 'b': '%(userdir)s\\\\some path\\\\file.py',
725+ ... 'c': 'Yo %(a)s',
726+ ... 'd': '%(not_here)s',
727+ ... 'e': '%(e)s',
728+ ... }
729+ >>> c['section']['DEFAULT'] = {
730+ ... 'datadir': 'c:\\\\silly_test',
731+ ... 'a': 'hello - %(b)s',
732+ ... }
733+ >>> c['section']['a'] == 'c:\\\\silly_test\\\\some path\\\\file.py'
734+ 1
735+ >>> c['section']['b'] == 'c:\\\\home\\\\some path\\\\file.py'
736+ 1
737+ >>> c['section']['c'] == 'Yo c:\\\\silly_test\\\\some path\\\\file.py'
738+ 1
739+
740+ Switching Interpolation Off
741+
742+ >>> c.interpolation = False
743+ >>> c['section']['a'] == '%(datadir)s\\\\some path\\\\file.py'
744+ 1
745+ >>> c['section']['b'] == '%(userdir)s\\\\some path\\\\file.py'
746+ 1
747+ >>> c['section']['c'] == 'Yo %(a)s'
748+ 1
749+
750+ Testing the interpolation errors.
751+
752+ >>> c.interpolation = True
753+ >>> c['section']['d']
754+ Traceback (most recent call last):
755+ MissingInterpolationOption: missing option "not_here" in interpolation.
756+ >>> c['section']['e']
757+ Traceback (most recent call last):
758+ InterpolationLoopError: interpolation loop detected in value "e".
759+
760+ Testing Template-style interpolation
761+
762+ >>> interp_cfg = '''
763+ ... [DEFAULT]
764+ ... keyword1 = value1
765+ ... 'keyword 2' = 'value 2'
766+ ... reference = ${keyword1}
767+ ... foo = 123
768+ ...
769+ ... [ section ]
770+ ... templatebare = $keyword1/foo
771+ ... bar = $$foo
772+ ... dollar = $$300.00
773+ ... stophere = $$notinterpolated
774+ ... with_braces = ${keyword1}s (plural)
775+ ... with_spaces = ${keyword 2}!!!
776+ ... with_several = $keyword1/$reference/$keyword1
777+ ... configparsersample = %(keyword 2)sconfig
778+ ... deep = ${reference}
779+ ...
780+ ... [[DEFAULT]]
781+ ... baz = $foo
782+ ...
783+ ... [[ sub-section ]]
784+ ... quux = '$baz + $bar + $foo'
785+ ...
786+ ... [[[ sub-sub-section ]]]
787+ ... convoluted = "$bar + $baz + $quux + $bar"
788+ ... '''
789+ >>> c = ConfigObj(interp_cfg.split('\\n'), interpolation='Template')
790+ >>> c['section']['templatebare']
791+ 'value1/foo'
792+ >>> c['section']['dollar']
793+ '$300.00'
794+ >>> c['section']['stophere']
795+ '$notinterpolated'
796+ >>> c['section']['with_braces']
797+ 'value1s (plural)'
798+ >>> c['section']['with_spaces']
799+ 'value 2!!!'
800+ >>> c['section']['with_several']
801+ 'value1/value1/value1'
802+ >>> c['section']['configparsersample']
803+ '%(keyword 2)sconfig'
804+ >>> c['section']['deep']
805+ 'value1'
806+ >>> c['section']['sub-section']['quux']
807+ '123 + $foo + 123'
808+ >>> c['section']['sub-section']['sub-sub-section']['convoluted']
809+ '$foo + 123 + 123 + $foo + 123 + $foo'
810+
811+ Testing our quoting.
812+
813+ >>> i._quote('\"""\\'\\'\\'')
814+ Traceback (most recent call last):
815+ ConfigObjError: Value \"\"""'''" cannot be safely quoted.
816+ >>> try:
817+ ... i._quote('\\n', multiline=False)
818+ ... except ConfigObjError, e:
819+ ... e.msg
820+ 'Value "\\n" cannot be safely quoted.'
821+ >>> i._quote(' "\\' ', multiline=False)
822+ Traceback (most recent call last):
823+ ConfigObjError: Value " "' " cannot be safely quoted.
824+
825+ Testing with "stringify" off.
826+ >>> c.stringify = False
827+ >>> c['test'] = 1
828+ Traceback (most recent call last):
829+ TypeError: Value is not a string "1".
830+
831+ Testing Empty values.
832+ >>> cfg_with_empty = '''
833+ ... k =
834+ ... k2 =# comment test
835+ ... val = test
836+ ... val2 = ,
837+ ... val3 = 1,
838+ ... val4 = 1, 2
839+ ... val5 = 1, 2, \'''.splitlines()
840+ >>> cwe = ConfigObj(cfg_with_empty)
841+ >>> cwe == {'k': '', 'k2': '', 'val': 'test', 'val2': [],
842+ ... 'val3': ['1'], 'val4': ['1', '2'], 'val5': ['1', '2']}
843+ 1
844+ >>> cwe = ConfigObj(cfg_with_empty, list_values=False)
845+ >>> cwe == {'k': '', 'k2': '', 'val': 'test', 'val2': ',',
846+ ... 'val3': '1,', 'val4': '1, 2', 'val5': '1, 2,'}
847+ 1
848+
849+ Testing list values.
850+ >>> testconfig3 = \'''
851+ ... a = ,
852+ ... b = test,
853+ ... c = test1, test2 , test3
854+ ... d = test1, test2, test3,
855+ ... \'''
856+ >>> d = ConfigObj(testconfig3.split('\\n'), raise_errors=True)
857+ >>> d['a'] == []
858+ 1
859+ >>> d['b'] == ['test']
860+ 1
861+ >>> d['c'] == ['test1', 'test2', 'test3']
862+ 1
863+ >>> d['d'] == ['test1', 'test2', 'test3']
864+ 1
865+
866+ Testing with list values off.
867+
868+ >>> e = ConfigObj(
869+ ... testconfig3.split('\\n'),
870+ ... raise_errors=True,
871+ ... list_values=False)
872+ >>> e['a'] == ','
873+ 1
874+ >>> e['b'] == 'test,'
875+ 1
876+ >>> e['c'] == 'test1, test2 , test3'
877+ 1
878+ >>> e['d'] == 'test1, test2, test3,'
879+ 1
880+
881+ Testing creating from a dictionary.
882+
883+ >>> f = {
884+ ... 'key1': 'val1',
885+ ... 'key2': 'val2',
886+ ... 'section 1': {
887+ ... 'key1': 'val1',
888+ ... 'key2': 'val2',
889+ ... 'section 1b': {
890+ ... 'key1': 'val1',
891+ ... 'key2': 'val2',
892+ ... },
893+ ... },
894+ ... 'section 2': {
895+ ... 'key1': 'val1',
896+ ... 'key2': 'val2',
897+ ... 'section 2b': {
898+ ... 'key1': 'val1',
899+ ... 'key2': 'val2',
900+ ... },
901+ ... },
902+ ... 'key3': 'val3',
903+ ... }
904+ >>> g = ConfigObj(f)
905+ >>> f == g
906+ 1
907+
908+ Testing we correctly detect badly built list values (4 of them).
909+
910+ >>> testconfig4 = '''
911+ ... config = 3,4,,
912+ ... test = 3,,4
913+ ... fish = ,,
914+ ... dummy = ,,hello, goodbye
915+ ... '''
916+ >>> try:
917+ ... ConfigObj(testconfig4.split('\\n'))
918+ ... except ConfigObjError, e:
919+ ... len(e.errors)
920+ 4
921+
922+ Testing we correctly detect badly quoted values (4 of them).
923+
924+ >>> testconfig5 = '''
925+ ... config = "hello # comment
926+ ... test = 'goodbye
927+ ... fish = 'goodbye # comment
928+ ... dummy = "hello again
929+ ... '''
930+ >>> try:
931+ ... ConfigObj(testconfig5.split('\\n'))
932+ ... except ConfigObjError, e:
933+ ... len(e.errors)
934+ 4
935+
936+ Test Multiline Comments
937+ >>> i == {
938+ ... 'name4': ' another single line value ',
939+ ... 'multi section': {
940+ ... 'name4': '\\n Well, this is a\\n multiline '
941+ ... 'value\\n ',
942+ ... 'name2': '\\n Well, this is a\\n multiline '
943+ ... 'value\\n ',
944+ ... 'name3': '\\n Well, this is a\\n multiline '
945+ ... 'value\\n ',
946+ ... 'name1': '\\n Well, this is a\\n multiline '
947+ ... 'value\\n ',
948+ ... },
949+ ... 'name2': ' another single line value ',
950+ ... 'name3': ' a single line value ',
951+ ... 'name1': ' a single line value ',
952+ ... }
953+ 1
954+
955+ >>> filename = a.filename
956+ >>> a.filename = None
957+ >>> values = a.write()
958+ >>> index = 0
959+ >>> while index < 23:
960+ ... index += 1
961+ ... line = values[index-1]
962+ ... assert line.endswith('# comment ' + str(index))
963+ >>> a.filename = filename
964+
965+ >>> start_comment = ['# Initial Comment', '', '#']
966+ >>> end_comment = ['', '#', '# Final Comment']
967+ >>> newconfig = start_comment + testconfig1.split('\\n') + end_comment
968+ >>> nc = ConfigObj(newconfig)
969+ >>> nc.initial_comment
970+ ['# Initial Comment', '', '#']
971+ >>> nc.final_comment
972+ ['', '#', '# Final Comment']
973+ >>> nc.initial_comment == start_comment
974+ 1
975+ >>> nc.final_comment == end_comment
976+ 1
977+
978+ Test the _handle_comment method
979+
980+ >>> c = ConfigObj()
981+ >>> c['foo'] = 'bar'
982+ >>> c.inline_comments['foo'] = 'Nice bar'
983+ >>> c.write()
984+ ['foo = bar # Nice bar']
985+
986+ tekNico: FIXME: use StringIO instead of real files
987+
988+ >>> filename = a.filename
989+ >>> a.filename = 'test.ini'
990+ >>> a.write()
991+ >>> a.filename = filename
992+ >>> a == ConfigObj('test.ini', raise_errors=True)
993+ 1
994+ >>> os.remove('test.ini')
995+ >>> b.filename = 'test.ini'
996+ >>> b.write()
997+ >>> b == ConfigObj('test.ini', raise_errors=True)
998+ 1
999+ >>> os.remove('test.ini')
1000+ >>> i.filename = 'test.ini'
1001+ >>> i.write()
1002+ >>> i == ConfigObj('test.ini', raise_errors=True)
1003+ 1
1004+ >>> os.remove('test.ini')
1005+ >>> a = ConfigObj()
1006+ >>> a['DEFAULT'] = {'a' : 'fish'}
1007+ >>> a['a'] = '%(a)s'
1008+ >>> a.write()
1009+ ['a = %(a)s', '[DEFAULT]', 'a = fish']
1010+
1011+ Test indentation handling
1012+
1013+ >>> ConfigObj({'sect': {'sect': {'foo': 'bar'}}}).write()
1014+ ['[sect]', ' [[sect]]', ' foo = bar']
1015+ >>> cfg = ['[sect]', '[[sect]]', 'foo = bar']
1016+ >>> ConfigObj(cfg).write() == cfg
1017+ 1
1018+ >>> cfg = ['[sect]', ' [[sect]]', ' foo = bar']
1019+ >>> ConfigObj(cfg).write() == cfg
1020+ 1
1021+ >>> cfg = ['[sect]', ' [[sect]]', ' foo = bar']
1022+ >>> ConfigObj(cfg).write() == cfg
1023+ 1
1024+ >>> ConfigObj(oneTabCfg).write() == oneTabCfg
1025+ 1
1026+ >>> ConfigObj(twoTabsCfg).write() == twoTabsCfg
1027+ 1
1028+ >>> ConfigObj(tabsAndSpacesCfg).write() == tabsAndSpacesCfg
1029+ 1
1030+ >>> ConfigObj(cfg, indent_type=chr(9)).write() == oneTabCfg
1031+ 1
1032+ >>> ConfigObj(oneTabCfg, indent_type=' ').write() == cfg
1033+ 1
1034+ """
1035+
1036+
1037+def _test_validate():
1038+ """
1039+ >>> config = '''
1040+ ... test1=40
1041+ ... test2=hello
1042+ ... test3=3
1043+ ... test4=5.0
1044+ ... [section]
1045+ ... test1=40
1046+ ... test2=hello
1047+ ... test3=3
1048+ ... test4=5.0
1049+ ... [[sub section]]
1050+ ... test1=40
1051+ ... test2=hello
1052+ ... test3=3
1053+ ... test4=5.0
1054+ ... '''.split('\\n')
1055+ >>> configspec = '''
1056+ ... test1= integer(30,50)
1057+ ... test2= string
1058+ ... test3=integer
1059+ ... test4=float(6.0)
1060+ ... [section ]
1061+ ... test1=integer(30,50)
1062+ ... test2=string
1063+ ... test3=integer
1064+ ... test4=float(6.0)
1065+ ... [[sub section]]
1066+ ... test1=integer(30,50)
1067+ ... test2=string
1068+ ... test3=integer
1069+ ... test4=float(6.0)
1070+ ... '''.split('\\n')
1071+ >>> val = Validator()
1072+ >>> c1 = ConfigObj(config, configspec=configspec)
1073+ >>> test = c1.validate(val)
1074+ >>> test == {
1075+ ... 'test1': True,
1076+ ... 'test2': True,
1077+ ... 'test3': True,
1078+ ... 'test4': False,
1079+ ... 'section': {
1080+ ... 'test1': True,
1081+ ... 'test2': True,
1082+ ... 'test3': True,
1083+ ... 'test4': False,
1084+ ... 'sub section': {
1085+ ... 'test1': True,
1086+ ... 'test2': True,
1087+ ... 'test3': True,
1088+ ... 'test4': False,
1089+ ... },
1090+ ... },
1091+ ... }
1092+ 1
1093+ >>> val.check(c1.configspec['test4'], c1['test4'])
1094+ Traceback (most recent call last):
1095+ VdtValueTooSmallError: the value "5.0" is too small.
1096+
1097+ >>> val_test_config = '''
1098+ ... key = 0
1099+ ... key2 = 1.1
1100+ ... [section]
1101+ ... key = some text
1102+ ... key2 = 1.1, 3.0, 17, 6.8
1103+ ... [[sub-section]]
1104+ ... key = option1
1105+ ... key2 = True'''.split('\\n')
1106+ >>> val_test_configspec = '''
1107+ ... key = integer
1108+ ... key2 = float
1109+ ... [section]
1110+ ... key = string
1111+ ... key2 = float_list(4)
1112+ ... [[sub-section]]
1113+ ... key = option(option1, option2)
1114+ ... key2 = boolean'''.split('\\n')
1115+ >>> val_test = ConfigObj(val_test_config, configspec=val_test_configspec)
1116+ >>> val_test.validate(val)
1117+ 1
1118+ >>> val_test['key'] = 'text not a digit'
1119+ >>> val_res = val_test.validate(val)
1120+ >>> val_res == {'key2': True, 'section': True, 'key': False}
1121+ 1
1122+ >>> configspec = '''
1123+ ... test1=integer(30,50, default=40)
1124+ ... test2=string(default="hello")
1125+ ... test3=integer(default=3)
1126+ ... test4=float(6.0, default=6.0)
1127+ ... [section ]
1128+ ... test1=integer(30,50, default=40)
1129+ ... test2=string(default="hello")
1130+ ... test3=integer(default=3)
1131+ ... test4=float(6.0, default=6.0)
1132+ ... [[sub section]]
1133+ ... test1=integer(30,50, default=40)
1134+ ... test2=string(default="hello")
1135+ ... test3=integer(default=3)
1136+ ... test4=float(6.0, default=6.0)
1137+ ... '''.split('\\n')
1138+ >>> default_test = ConfigObj(['test1=30'], configspec=configspec)
1139+ >>> default_test
1140+ ConfigObj({'test1': '30'})
1141+ >>> default_test.defaults
1142+ []
1143+ >>> default_test.default_values
1144+ {}
1145+ >>> default_test.validate(val)
1146+ 1
1147+ >>> default_test == {
1148+ ... 'test1': 30,
1149+ ... 'test2': 'hello',
1150+ ... 'test3': 3,
1151+ ... 'test4': 6.0,
1152+ ... 'section': {
1153+ ... 'test1': 40,
1154+ ... 'test2': 'hello',
1155+ ... 'test3': 3,
1156+ ... 'test4': 6.0,
1157+ ... 'sub section': {
1158+ ... 'test1': 40,
1159+ ... 'test3': 3,
1160+ ... 'test2': 'hello',
1161+ ... 'test4': 6.0,
1162+ ... },
1163+ ... },
1164+ ... }
1165+ 1
1166+ >>> default_test.defaults
1167+ ['test2', 'test3', 'test4']
1168+ >>> default_test.default_values == {'test1': 40, 'test2': 'hello',
1169+ ... 'test3': 3, 'test4': 6.0}
1170+ 1
1171+ >>> default_test.restore_default('test1')
1172+ 40
1173+ >>> default_test['test1']
1174+ 40
1175+ >>> 'test1' in default_test.defaults
1176+ 1
1177+ >>> def change(section, key):
1178+ ... section[key] = 3
1179+ >>> _ = default_test.walk(change)
1180+ >>> default_test['section']['sub section']['test4']
1181+ 3
1182+ >>> default_test.restore_defaults()
1183+ >>> default_test == {
1184+ ... 'test1': 40,
1185+ ... 'test2': "hello",
1186+ ... 'test3': 3,
1187+ ... 'test4': 6.0,
1188+ ... 'section': {
1189+ ... 'test1': 40,
1190+ ... 'test2': "hello",
1191+ ... 'test3': 3,
1192+ ... 'test4': 6.0,
1193+ ... 'sub section': {
1194+ ... 'test1': 40,
1195+ ... 'test2': "hello",
1196+ ... 'test3': 3,
1197+ ... 'test4': 6.0
1198+ ... }}}
1199+ 1
1200+ >>> a = ['foo = fish']
1201+ >>> b = ['foo = integer(default=3)']
1202+ >>> c = ConfigObj(a, configspec=b)
1203+ >>> c
1204+ ConfigObj({'foo': 'fish'})
1205+ >>> from validate import Validator
1206+ >>> v = Validator()
1207+ >>> c.validate(v)
1208+ 0
1209+ >>> c.default_values
1210+ {'foo': 3}
1211+ >>> c.restore_default('foo')
1212+ 3
1213+
1214+ Now testing with repeated sections : BIG TEST
1215+
1216+ >>> repeated_1 = '''
1217+ ... [dogs]
1218+ ... [[__many__]] # spec for a dog
1219+ ... fleas = boolean(default=True)
1220+ ... tail = option(long, short, default=long)
1221+ ... name = string(default=rover)
1222+ ... [[[__many__]]] # spec for a puppy
1223+ ... name = string(default="son of rover")
1224+ ... age = float(default=0.0)
1225+ ... [cats]
1226+ ... [[__many__]] # spec for a cat
1227+ ... fleas = boolean(default=True)
1228+ ... tail = option(long, short, default=short)
1229+ ... name = string(default=pussy)
1230+ ... [[[__many__]]] # spec for a kitten
1231+ ... name = string(default="son of pussy")
1232+ ... age = float(default=0.0)
1233+ ... '''.split('\\n')
1234+ >>> repeated_2 = '''
1235+ ... [dogs]
1236+ ...
1237+ ... # blank dogs with puppies
1238+ ... # should be filled in by the configspec
1239+ ... [[dog1]]
1240+ ... [[[puppy1]]]
1241+ ... [[[puppy2]]]
1242+ ... [[[puppy3]]]
1243+ ... [[dog2]]
1244+ ... [[[puppy1]]]
1245+ ... [[[puppy2]]]
1246+ ... [[[puppy3]]]
1247+ ... [[dog3]]
1248+ ... [[[puppy1]]]
1249+ ... [[[puppy2]]]
1250+ ... [[[puppy3]]]
1251+ ... [cats]
1252+ ...
1253+ ... # blank cats with kittens
1254+ ... # should be filled in by the configspec
1255+ ... [[cat1]]
1256+ ... [[[kitten1]]]
1257+ ... [[[kitten2]]]
1258+ ... [[[kitten3]]]
1259+ ... [[cat2]]
1260+ ... [[[kitten1]]]
1261+ ... [[[kitten2]]]
1262+ ... [[[kitten3]]]
1263+ ... [[cat3]]
1264+ ... [[[kitten1]]]
1265+ ... [[[kitten2]]]
1266+ ... [[[kitten3]]]
1267+ ... '''.split('\\n')
1268+ >>> repeated_3 = '''
1269+ ... [dogs]
1270+ ...
1271+ ... [[dog1]]
1272+ ... [[dog2]]
1273+ ... [[dog3]]
1274+ ... [cats]
1275+ ...
1276+ ... [[cat1]]
1277+ ... [[cat2]]
1278+ ... [[cat3]]
1279+ ... '''.split('\\n')
1280+ >>> repeated_4 = '''
1281+ ... [__many__]
1282+ ...
1283+ ... name = string(default=Michael)
1284+ ... age = float(default=0.0)
1285+ ... sex = option(m, f, default=m)
1286+ ... '''.split('\\n')
1287+ >>> repeated_5 = '''
1288+ ... [cats]
1289+ ... [[__many__]]
1290+ ... fleas = boolean(default=True)
1291+ ... tail = option(long, short, default=short)
1292+ ... name = string(default=pussy)
1293+ ... [[[description]]]
1294+ ... height = float(default=3.3)
1295+ ... weight = float(default=6)
1296+ ... [[[[coat]]]]
1297+ ... fur = option(black, grey, brown, "tortoise shell", default=black)
1298+ ... condition = integer(0,10, default=5)
1299+ ... '''.split('\\n')
1300+ >>> val= Validator()
1301+ >>> repeater = ConfigObj(repeated_2, configspec=repeated_1)
1302+ >>> repeater.validate(val)
1303+ 1
1304+ >>> repeater == {
1305+ ... 'dogs': {
1306+ ... 'dog1': {
1307+ ... 'fleas': True,
1308+ ... 'tail': 'long',
1309+ ... 'name': 'rover',
1310+ ... 'puppy1': {'name': 'son of rover', 'age': 0.0},
1311+ ... 'puppy2': {'name': 'son of rover', 'age': 0.0},
1312+ ... 'puppy3': {'name': 'son of rover', 'age': 0.0},
1313+ ... },
1314+ ... 'dog2': {
1315+ ... 'fleas': True,
1316+ ... 'tail': 'long',
1317+ ... 'name': 'rover',
1318+ ... 'puppy1': {'name': 'son of rover', 'age': 0.0},
1319+ ... 'puppy2': {'name': 'son of rover', 'age': 0.0},
1320+ ... 'puppy3': {'name': 'son of rover', 'age': 0.0},
1321+ ... },
1322+ ... 'dog3': {
1323+ ... 'fleas': True,
1324+ ... 'tail': 'long',
1325+ ... 'name': 'rover',
1326+ ... 'puppy1': {'name': 'son of rover', 'age': 0.0},
1327+ ... 'puppy2': {'name': 'son of rover', 'age': 0.0},
1328+ ... 'puppy3': {'name': 'son of rover', 'age': 0.0},
1329+ ... },
1330+ ... },
1331+ ... 'cats': {
1332+ ... 'cat1': {
1333+ ... 'fleas': True,
1334+ ... 'tail': 'short',
1335+ ... 'name': 'pussy',
1336+ ... 'kitten1': {'name': 'son of pussy', 'age': 0.0},
1337+ ... 'kitten2': {'name': 'son of pussy', 'age': 0.0},
1338+ ... 'kitten3': {'name': 'son of pussy', 'age': 0.0},
1339+ ... },
1340+ ... 'cat2': {
1341+ ... 'fleas': True,
1342+ ... 'tail': 'short',
1343+ ... 'name': 'pussy',
1344+ ... 'kitten1': {'name': 'son of pussy', 'age': 0.0},
1345+ ... 'kitten2': {'name': 'son of pussy', 'age': 0.0},
1346+ ... 'kitten3': {'name': 'son of pussy', 'age': 0.0},
1347+ ... },
1348+ ... 'cat3': {
1349+ ... 'fleas': True,
1350+ ... 'tail': 'short',
1351+ ... 'name': 'pussy',
1352+ ... 'kitten1': {'name': 'son of pussy', 'age': 0.0},
1353+ ... 'kitten2': {'name': 'son of pussy', 'age': 0.0},
1354+ ... 'kitten3': {'name': 'son of pussy', 'age': 0.0},
1355+ ... },
1356+ ... },
1357+ ... }
1358+ 1
1359+ >>> repeater = ConfigObj(repeated_3, configspec=repeated_1)
1360+ >>> repeater.validate(val)
1361+ 1
1362+ >>> repeater == {
1363+ ... 'cats': {
1364+ ... 'cat1': {'fleas': True, 'tail': 'short', 'name': 'pussy'},
1365+ ... 'cat2': {'fleas': True, 'tail': 'short', 'name': 'pussy'},
1366+ ... 'cat3': {'fleas': True, 'tail': 'short', 'name': 'pussy'},
1367+ ... },
1368+ ... 'dogs': {
1369+ ... 'dog1': {'fleas': True, 'tail': 'long', 'name': 'rover'},
1370+ ... 'dog2': {'fleas': True, 'tail': 'long', 'name': 'rover'},
1371+ ... 'dog3': {'fleas': True, 'tail': 'long', 'name': 'rover'},
1372+ ... },
1373+ ... }
1374+ 1
1375+ >>> repeater = ConfigObj(configspec=repeated_4)
1376+ >>> repeater['Michael'] = {}
1377+ >>> repeater.validate(val)
1378+ 1
1379+ >>> repeater == {
1380+ ... 'Michael': {'age': 0.0, 'name': 'Michael', 'sex': 'm'},
1381+ ... }
1382+ 1
1383+ >>> repeater = ConfigObj(repeated_3, configspec=repeated_5)
1384+ >>> repeater == {
1385+ ... 'dogs': {'dog1': {}, 'dog2': {}, 'dog3': {}},
1386+ ... 'cats': {'cat1': {}, 'cat2': {}, 'cat3': {}},
1387+ ... }
1388+ 1
1389+ >>> repeater.validate(val)
1390+ 1
1391+ >>> repeater == {
1392+ ... 'dogs': {'dog1': {}, 'dog2': {}, 'dog3': {}},
1393+ ... 'cats': {
1394+ ... 'cat1': {
1395+ ... 'fleas': True,
1396+ ... 'tail': 'short',
1397+ ... 'name': 'pussy',
1398+ ... 'description': {
1399+ ... 'weight': 6.0,
1400+ ... 'height': 3.2999999999999998,
1401+ ... 'coat': {'fur': 'black', 'condition': 5},
1402+ ... },
1403+ ... },
1404+ ... 'cat2': {
1405+ ... 'fleas': True,
1406+ ... 'tail': 'short',
1407+ ... 'name': 'pussy',
1408+ ... 'description': {
1409+ ... 'weight': 6.0,
1410+ ... 'height': 3.2999999999999998,
1411+ ... 'coat': {'fur': 'black', 'condition': 5},
1412+ ... },
1413+ ... },
1414+ ... 'cat3': {
1415+ ... 'fleas': True,
1416+ ... 'tail': 'short',
1417+ ... 'name': 'pussy',
1418+ ... 'description': {
1419+ ... 'weight': 6.0,
1420+ ... 'height': 3.2999999999999998,
1421+ ... 'coat': {'fur': 'black', 'condition': 5},
1422+ ... },
1423+ ... },
1424+ ... },
1425+ ... }
1426+ 1
1427+
1428+ Test that interpolation is preserved for validated string values.
1429+ Also check that interpolation works in configspecs.
1430+ >>> t = ConfigObj(configspec=['test = string'])
1431+ >>> t['DEFAULT'] = {}
1432+ >>> t['DEFAULT']['def_test'] = 'a'
1433+ >>> t['test'] = '%(def_test)s'
1434+ >>> t['test']
1435+ 'a'
1436+ >>> v = Validator()
1437+ >>> t.validate(v)
1438+ 1
1439+ >>> t.interpolation = False
1440+ >>> t
1441+ ConfigObj({'test': '%(def_test)s', 'DEFAULT': {'def_test': 'a'}})
1442+ >>> specs = [
1443+ ... 'interpolated string = string(default="fuzzy-%(man)s")',
1444+ ... '[DEFAULT]',
1445+ ... 'man = wuzzy',
1446+ ... ]
1447+ >>> c = ConfigObj(configspec=specs)
1448+ >>> c.validate(v)
1449+ 1
1450+ >>> c['interpolated string']
1451+ 'fuzzy-wuzzy'
1452+
1453+ Test SimpleVal
1454+ >>> val = SimpleVal()
1455+ >>> config = '''
1456+ ... test1=40
1457+ ... test2=hello
1458+ ... test3=3
1459+ ... test4=5.0
1460+ ... [section]
1461+ ... test1=40
1462+ ... test2=hello
1463+ ... test3=3
1464+ ... test4=5.0
1465+ ... [[sub section]]
1466+ ... test1=40
1467+ ... test2=hello
1468+ ... test3=3
1469+ ... test4=5.0
1470+ ... '''.split('\\n')
1471+ >>> configspec = '''
1472+ ... test1=''
1473+ ... test2=''
1474+ ... test3=''
1475+ ... test4=''
1476+ ... [section]
1477+ ... test1=''
1478+ ... test2=''
1479+ ... test3=''
1480+ ... test4=''
1481+ ... [[sub section]]
1482+ ... test1=''
1483+ ... test2=''
1484+ ... test3=''
1485+ ... test4=''
1486+ ... '''.split('\\n')
1487+ >>> o = ConfigObj(config, configspec=configspec)
1488+ >>> o.validate(val)
1489+ 1
1490+ >>> o = ConfigObj(configspec=configspec)
1491+ >>> o.validate(val)
1492+ 0
1493+
1494+ Test Flatten Errors
1495+ >>> vtor = Validator()
1496+ >>> my_ini = '''
1497+ ... option1 = True
1498+ ... [section1]
1499+ ... option1 = True
1500+ ... [section2]
1501+ ... another_option = Probably
1502+ ... [section3]
1503+ ... another_option = True
1504+ ... [[section3b]]
1505+ ... value = 3
1506+ ... value2 = a
1507+ ... value3 = 11
1508+ ... '''
1509+ >>> my_cfg = '''
1510+ ... option1 = boolean()
1511+ ... option2 = boolean()
1512+ ... option3 = boolean(default=Bad_value)
1513+ ... [section1]
1514+ ... option1 = boolean()
1515+ ... option2 = boolean()
1516+ ... option3 = boolean(default=Bad_value)
1517+ ... [section2]
1518+ ... another_option = boolean()
1519+ ... [section3]
1520+ ... another_option = boolean()
1521+ ... [[section3b]]
1522+ ... value = integer
1523+ ... value2 = integer
1524+ ... value3 = integer(0, 10)
1525+ ... [[[section3b-sub]]]
1526+ ... value = string
1527+ ... [section4]
1528+ ... another_option = boolean()
1529+ ... '''
1530+ >>> cs = my_cfg.split('\\n')
1531+ >>> ini = my_ini.split('\\n')
1532+ >>> cfg = ConfigObj(ini, configspec=cs)
1533+ >>> res = cfg.validate(vtor, preserve_errors=True)
1534+ >>> errors = []
1535+ >>> for entry in flatten_errors(cfg, res):
1536+ ... section_list, key, error = entry
1537+ ... section_list.insert(0, '[root]')
1538+ ... if key is not None:
1539+ ... section_list.append(key)
1540+ ... section_string = ', '.join(section_list)
1541+ ... errors.append('%s%s%s' % (section_string, ' = ', error or 'missing'))
1542+ >>> errors.sort()
1543+ >>> for entry in errors:
1544+ ... print entry
1545+ [root], option2 = missing
1546+ [root], option3 = the value "Bad_value" is of the wrong type.
1547+ [root], section1, option2 = missing
1548+ [root], section1, option3 = the value "Bad_value" is of the wrong type.
1549+ [root], section2, another_option = the value "Probably" is of the wrong type.
1550+ [root], section3, section3b, section3b-sub = missing
1551+ [root], section3, section3b, value2 = the value "a" is of the wrong type.
1552+ [root], section3, section3b, value3 = the value "11" is too big.
1553+ [root], section4 = missing
1554+ """
1555+
1556+
1557+def _test_errors():
1558+ """
1559+ Test the error messages and objects, in normal mode and unrepr mode.
1560+ >>> bad_syntax = '''
1561+ ... key = "value"
1562+ ... key2 = "value
1563+ ... '''.splitlines()
1564+ >>> c = ConfigObj(bad_syntax)
1565+ Traceback (most recent call last):
1566+ ParseError: Parse error in value at line 3.
1567+ >>> c = ConfigObj(bad_syntax, raise_errors=True)
1568+ Traceback (most recent call last):
1569+ ParseError: Parse error in value at line 3.
1570+ >>> c = ConfigObj(bad_syntax, raise_errors=True, unrepr=True)
1571+ Traceback (most recent call last):
1572+ UnreprError: Parse error in value at line 3.
1573+ >>> try:
1574+ ... c = ConfigObj(bad_syntax)
1575+ ... except Exception, e:
1576+ ... pass
1577+ >>> assert(isinstance(e, ConfigObjError))
1578+ >>> print e
1579+ Parse error in value at line 3.
1580+ >>> len(e.errors) == 1
1581+ 1
1582+ >>> try:
1583+ ... c = ConfigObj(bad_syntax, unrepr=True)
1584+ ... except Exception, e:
1585+ ... pass
1586+ >>> assert(isinstance(e, ConfigObjError))
1587+ >>> print e
1588+ Parse error in value at line 3.
1589+ >>> len(e.errors) == 1
1590+ 1
1591+ >>> the_error = e.errors[0]
1592+ >>> assert(isinstance(the_error, UnreprError))
1593+
1594+ >>> multiple_bad_syntax = '''
1595+ ... key = "value"
1596+ ... key2 = "value
1597+ ... key3 = "value2
1598+ ... '''.splitlines()
1599+ >>> try:
1600+ ... c = ConfigObj(multiple_bad_syntax)
1601+ ... except ConfigObjError, e:
1602+ ... str(e)
1603+ 'Parsing failed with several errors.\\nFirst error at line 3.'
1604+ >>> c = ConfigObj(multiple_bad_syntax, raise_errors=True)
1605+ Traceback (most recent call last):
1606+ ParseError: Parse error in value at line 3.
1607+ >>> c = ConfigObj(multiple_bad_syntax, raise_errors=True, unrepr=True)
1608+ Traceback (most recent call last):
1609+ UnreprError: Parse error in value at line 3.
1610+ >>> try:
1611+ ... c = ConfigObj(multiple_bad_syntax)
1612+ ... except Exception, e:
1613+ ... pass
1614+ >>> assert(isinstance(e, ConfigObjError))
1615+ >>> print e
1616+ Parsing failed with several errors.
1617+ First error at line 3.
1618+ >>> len(e.errors) == 2
1619+ 1
1620+ >>> try:
1621+ ... c = ConfigObj(multiple_bad_syntax, unrepr=True)
1622+ ... except Exception, e:
1623+ ... pass
1624+ >>> assert(isinstance(e, ConfigObjError))
1625+ >>> print e
1626+ Parsing failed with several errors.
1627+ First error at line 3.
1628+ >>> len(e.errors) == 2
1629+ 1
1630+ >>> the_error = e.errors[1]
1631+ >>> assert(isinstance(the_error, UnreprError))
1632+
1633+ >>> unknown_name = '''
1634+ ... key = "value"
1635+ ... key2 = value
1636+ ... '''.splitlines()
1637+ >>> c = ConfigObj(unknown_name)
1638+ >>> c = ConfigObj(unknown_name, unrepr=True)
1639+ Traceback (most recent call last):
1640+ UnreprError: Unknown name or type in value at line 3.
1641+ >>> c = ConfigObj(unknown_name, raise_errors=True, unrepr=True)
1642+ Traceback (most recent call last):
1643+ UnreprError: Unknown name or type in value at line 3.
1644+ """
1645+
1646+
1647+def _test_unrepr_comments():
1648+ """
1649+ >>> config = '''
1650+ ... # initial comments
1651+ ... # with two lines
1652+ ... key = "value"
1653+ ... # section comment
1654+ ... [section] # inline section comment
1655+ ... # key comment
1656+ ... key = "value"
1657+ ... # final comment
1658+ ... # with two lines
1659+ ... '''.splitlines()
1660+ >>> c = ConfigObj(config, unrepr=True)
1661+ >>> c == { 'key': 'value',
1662+ ... 'section': { 'key': 'value'}}
1663+ 1
1664+ >>> c.initial_comment == ['', '# initial comments', '# with two lines']
1665+ 1
1666+ >>> c.comments == {'section': ['# section comment'], 'key': []}
1667+ 1
1668+ >>> c.inline_comments == {'section': '# inline section comment', 'key': ''}
1669+ 1
1670+ >>> c['section'].comments == { 'key': ['# key comment']}
1671+ 1
1672+ >>> c.final_comment == ['# final comment', '# with two lines']
1673+ 1
1674+ """
1675+
1676+
1677+def _test_newline_terminated():
1678+ """
1679+ >>> c = ConfigObj()
1680+ >>> c.newlines = '\\n'
1681+ >>> c['a'] = 'b'
1682+ >>> collector = StringIO()
1683+ >>> c.write(collector)
1684+ >>> collector.getvalue()
1685+ 'a = b\\n'
1686+ """
1687+
1688+
1689+def _test_hash_escaping():
1690+ """
1691+ >>> c = ConfigObj()
1692+ >>> c.newlines = '\\n'
1693+ >>> c['#a'] = 'b # something'
1694+ >>> collector = StringIO()
1695+ >>> c.write(collector)
1696+ >>> collector.getvalue()
1697+ '"#a" = "b # something"\\n'
1698+
1699+ >>> c = ConfigObj()
1700+ >>> c.newlines = '\\n'
1701+ >>> c['a'] = 'b # something', 'c # something'
1702+ >>> collector = StringIO()
1703+ >>> c.write(collector)
1704+ >>> collector.getvalue()
1705+ 'a = "b # something", "c # something"\\n'
1706+ """
1707+
1708+
1709+def _test_lineendings():
1710+ """
1711+ NOTE: Need to use a real file because this code is only
1712+ exercised when reading from the filesystem.
1713+
1714+ >>> h = open('temp', 'wb')
1715+ >>> h.write('\\r\\n')
1716+ >>> h.close()
1717+ >>> c = ConfigObj('temp')
1718+ >>> c.newlines
1719+ '\\r\\n'
1720+ >>> h = open('temp', 'wb')
1721+ >>> h.write('\\n')
1722+ >>> h.close()
1723+ >>> c = ConfigObj('temp')
1724+ >>> c.newlines
1725+ '\\n'
1726+ >>> os.remove('temp')
1727+ """
1728+
1729+
1730+def _test_validate_with_copy_and_many():
1731+ """
1732+ >>> spec = '''
1733+ ... [section]
1734+ ... [[__many__]]
1735+ ... value = string(default='nothing')
1736+ ... '''
1737+ >>> config = '''
1738+ ... [section]
1739+ ... [[something]]
1740+ ... '''
1741+ >>> c = ConfigObj(StringIO(config), configspec=StringIO(spec))
1742+ >>> v = Validator()
1743+ >>> r = c.validate(v, copy=True)
1744+ >>> c['section']['something']['value']
1745+ 'nothing'
1746+ """
1747+
1748+def _test_configspec_with_hash():
1749+ """
1750+ >>> spec = ['stuff = string(default="#ff00dd")']
1751+ >>> c = ConfigObj(spec, _inspec=True)
1752+ >>> c['stuff']
1753+ 'string(default="#ff00dd")'
1754+ >>> c = ConfigObj(configspec=spec)
1755+ >>> v = Validator()
1756+ >>> c.validate(v)
1757+ 1
1758+ >>> c['stuff']
1759+ '#ff00dd'
1760+
1761+
1762+ >>> spec = ['stuff = string(default="fish") # wooble']
1763+ >>> c = ConfigObj(spec, _inspec=True)
1764+ >>> c['stuff']
1765+ 'string(default="fish") # wooble'
1766+ """
1767+
1768+def _test_many_check():
1769+ """
1770+ >>> spec = ['__many__ = integer()']
1771+ >>> config = ['a = 6', 'b = 7']
1772+ >>> c = ConfigObj(config, configspec=spec)
1773+ >>> v = Validator()
1774+ >>> c.validate(v)
1775+ 1
1776+ >>> type(c['a'])
1777+ <type 'int'>
1778+ >>> type(c['b'])
1779+ <type 'int'>
1780+
1781+
1782+ >>> spec = ['[name]', '__many__ = integer()']
1783+ >>> config = ['[name]', 'a = 6', 'b = 7']
1784+ >>> c = ConfigObj(config, configspec=spec)
1785+ >>> v = Validator()
1786+ >>> c.validate(v)
1787+ 1
1788+ >>> type(c['name']['a'])
1789+ <type 'int'>
1790+ >>> type(c['name']['b'])
1791+ <type 'int'>
1792+
1793+
1794+ >>> spec = ['[__many__]', '__many__ = integer()']
1795+ >>> config = ['[name]', 'hello = 7', '[thing]', 'fish = 0']
1796+ >>> c = ConfigObj(config, configspec=spec)
1797+ >>> v = Validator()
1798+ >>> c.validate(v)
1799+ 1
1800+ >>> type(c['name']['hello'])
1801+ <type 'int'>
1802+ >>> type(c['thing']['fish'])
1803+ <type 'int'>
1804+
1805+
1806+ >>> spec = '''
1807+ ... ___many___ = integer
1808+ ... [__many__]
1809+ ... ___many___ = boolean
1810+ ... [[__many__]]
1811+ ... __many__ = float
1812+ ... '''.splitlines()
1813+ >>> config = '''
1814+ ... fish = 8
1815+ ... buggle = 4
1816+ ... [hi]
1817+ ... one = true
1818+ ... two = false
1819+ ... [[bye]]
1820+ ... odd = 3
1821+ ... whoops = 9.0
1822+ ... [bye]
1823+ ... one = true
1824+ ... two = true
1825+ ... [[lots]]
1826+ ... odd = 3
1827+ ... whoops = 9.0
1828+ ... '''.splitlines()
1829+ >>> c = ConfigObj(config, configspec=spec)
1830+ >>> v = Validator()
1831+ >>> c.validate(v)
1832+ 1
1833+ >>> type(c['fish'])
1834+ <type 'int'>
1835+ >>> type(c['buggle'])
1836+ <type 'int'>
1837+ >>> c['hi']['one']
1838+ 1
1839+ >>> c['hi']['two']
1840+ 0
1841+ >>> type(c['hi']['bye']['odd'])
1842+ <type 'float'>
1843+ >>> type(c['hi']['bye']['whoops'])
1844+ <type 'float'>
1845+ >>> c['bye']['one']
1846+ 1
1847+ >>> c['bye']['two']
1848+ 1
1849+ >>> type(c['bye']['lots']['odd'])
1850+ <type 'float'>
1851+ >>> type(c['bye']['lots']['whoops'])
1852+ <type 'float'>
1853+
1854+
1855+ >>> spec = ['___many___ = integer()']
1856+ >>> config = ['a = 6', 'b = 7']
1857+ >>> c = ConfigObj(config, configspec=spec)
1858+ >>> v = Validator()
1859+ >>> c.validate(v)
1860+ 1
1861+ >>> type(c['a'])
1862+ <type 'int'>
1863+ >>> type(c['b'])
1864+ <type 'int'>
1865+
1866+
1867+ >>> spec = '''
1868+ ... [__many__]
1869+ ... [[__many__]]
1870+ ... __many__ = float
1871+ ... '''.splitlines()
1872+ >>> config = '''
1873+ ... [hi]
1874+ ... [[bye]]
1875+ ... odd = 3
1876+ ... whoops = 9.0
1877+ ... [bye]
1878+ ... [[lots]]
1879+ ... odd = 3
1880+ ... whoops = 9.0
1881+ ... '''.splitlines()
1882+ >>> c = ConfigObj(config, configspec=spec)
1883+ >>> v = Validator()
1884+ >>> c.validate(v)
1885+ 1
1886+ >>> type(c['hi']['bye']['odd'])
1887+ <type 'float'>
1888+ >>> type(c['hi']['bye']['whoops'])
1889+ <type 'float'>
1890+ >>> type(c['bye']['lots']['odd'])
1891+ <type 'float'>
1892+ >>> type(c['bye']['lots']['whoops'])
1893+ <type 'float'>
1894+
1895+ >>> s = ['[dog]', '[[cow]]', 'something = boolean', '[[__many__]]',
1896+ ... 'fish = integer']
1897+ >>> c = ['[dog]', '[[cow]]', 'something = true', '[[ob]]',
1898+ ... 'fish = 3', '[[bo]]', 'fish = 6']
1899+ >>> ini = ConfigObj(c, configspec=s)
1900+ >>> v = Validator()
1901+ >>> ini.validate(v)
1902+ 1
1903+ >>> ini['dog']['cow']['something']
1904+ 1
1905+ >>> ini['dog']['ob']['fish']
1906+ 3
1907+ >>> ini['dog']['bo']['fish']
1908+ 6
1909+
1910+
1911+ >>> s = ['[cow]', 'something = boolean', '[__many__]',
1912+ ... 'fish = integer']
1913+ >>> c = ['[cow]', 'something = true', '[ob]',
1914+ ... 'fish = 3', '[bo]', 'fish = 6']
1915+ >>> ini = ConfigObj(c, configspec=s)
1916+ >>> v = Validator()
1917+ >>> ini.validate(v)
1918+ 1
1919+ >>> ini['cow']['something']
1920+ 1
1921+ >>> ini['ob']['fish']
1922+ 3
1923+ >>> ini['bo']['fish']
1924+ 6
1925+ """
1926+
1927+
1928+def _unexpected_validation_errors():
1929+ """
1930+ Although the input is nonsensical we should not crash but correctly
1931+ report the failure to validate
1932+
1933+ # section specified, got scalar
1934+ >>> from validate import ValidateError
1935+ >>> s = ['[cow]', 'something = boolean']
1936+ >>> c = ['cow = true']
1937+ >>> ini = ConfigObj(c, configspec=s)
1938+ >>> v = Validator()
1939+ >>> ini.validate(v)
1940+ 0
1941+
1942+ >>> ini = ConfigObj(c, configspec=s)
1943+ >>> res = ini.validate(v, preserve_errors=True)
1944+ >>> check = flatten_errors(ini, res)
1945+ >>> for entry in check:
1946+ ... isinstance(entry[2], ValidateError)
1947+ ... print str(entry[2])
1948+ True
1949+ Section 'cow' was provided as a single value
1950+
1951+
1952+ # scalar specified, got section
1953+ >>> s = ['something = boolean']
1954+ >>> c = ['[something]', 'cow = true']
1955+ >>> ini = ConfigObj(c, configspec=s)
1956+ >>> v = Validator()
1957+ >>> ini.validate(v)
1958+ 0
1959+
1960+ >>> ini = ConfigObj(c, configspec=s)
1961+ >>> res = ini.validate(v, preserve_errors=True)
1962+ >>> check = flatten_errors(ini, res)
1963+ >>> for entry in check:
1964+ ... isinstance(entry[2], ValidateError)
1965+ ... print str(entry[2])
1966+ True
1967+ Value 'something' was provided as a section
1968+
1969+ # unexpected section
1970+ >>> s = []
1971+ >>> c = ['[cow]', 'dog = true']
1972+ >>> ini = ConfigObj(c, configspec=s)
1973+ >>> v = Validator()
1974+ >>> ini.validate(v)
1975+ 1
1976+
1977+
1978+ >>> s = ['[cow]', 'dog = boolean']
1979+ >>> c = ['[cow]', 'dog = true']
1980+ >>> ini = ConfigObj(c, configspec=s)
1981+ >>> v = Validator()
1982+ >>> ini.validate(v, preserve_errors=True)
1983+ 1
1984+ """
1985+
1986+def _test_pickle():
1987+ """
1988+ >>> import pickle
1989+ >>> s = ['[cow]', 'dog = boolean']
1990+ >>> c = ['[cow]', 'dog = true']
1991+ >>> ini = ConfigObj(c, configspec=s)
1992+ >>> v = Validator()
1993+ >>> string = pickle.dumps(ini)
1994+ >>> new = pickle.loads(string)
1995+ >>> new.validate(v)
1996+ 1
1997+ """
1998+
1999+def _test_as_list():
2000+ """
2001+ >>> a = ConfigObj()
2002+ >>> a['a'] = 1
2003+ >>> a.as_list('a')
2004+ [1]
2005+ >>> a['a'] = (1,)
2006+ >>> a.as_list('a')
2007+ [1]
2008+ >>> a['a'] = [1]
2009+ >>> a.as_list('a')
2010+ [1]
2011+ """
2012+
2013+def _test_list_interpolation():
2014+ """
2015+ >>> c = ConfigObj()
2016+ >>> c['x'] = 'foo'
2017+ >>> c['list'] = ['%(x)s', 3]
2018+ >>> c['list']
2019+ ['foo', 3]
2020+ """
2021+
2022+def _test_extra_values():
2023+ """
2024+ >>> spec = ['[section]']
2025+ >>> infile = ['bar = 3', '[something]', 'foo = fish', '[section]', 'foo=boo']
2026+ >>> c = ConfigObj(infile, configspec=spec)
2027+ >>> c.extra_values
2028+ []
2029+ >>> c.extra_values = ['bar', 'gosh', 'what']
2030+ >>> c.validate(Validator())
2031+ 1
2032+ >>> c.extra_values
2033+ ['bar', 'something']
2034+ >>> c['section'].extra_values
2035+ ['foo']
2036+ >>> c['something'].extra_values
2037+ []
2038+ """
2039+
2040+def _test_reset_and_clear_more():
2041+ """
2042+ >>> c = ConfigObj()
2043+ >>> c.extra_values = ['foo']
2044+ >>> c.defaults = ['bar']
2045+ >>> c.default_values = {'bar': 'baz'}
2046+ >>> c.clear()
2047+ >>> c.defaults
2048+ []
2049+ >>> c.extra_values
2050+ []
2051+ >>> c.default_values
2052+ {'bar': 'baz'}
2053+ >>> c.extra_values = ['foo']
2054+ >>> c.defaults = ['bar']
2055+ >>> c.reset()
2056+ >>> c.defaults
2057+ []
2058+ >>> c.extra_values
2059+ []
2060+ >>> c.default_values
2061+ {}
2062+ """
2063+
2064+def _test_invalid_lists():
2065+ """
2066+ >>> v = ['string = val, val2, , val3']
2067+ >>> c = ConfigObj(v)
2068+ Traceback (most recent call last):
2069+ ParseError: Parse error in value at line 1.
2070+ >>> v = ['string = val, val2,, val3']
2071+ >>> c = ConfigObj(v)
2072+ Traceback (most recent call last):
2073+ ParseError: Parse error in value at line 1.
2074+ >>> v = ['string = val, val2,,']
2075+ >>> c = ConfigObj(v)
2076+ Traceback (most recent call last):
2077+ ParseError: Parse error in value at line 1.
2078+ >>> v = ['string = val, ,']
2079+ >>> c = ConfigObj(v)
2080+ Traceback (most recent call last):
2081+ ParseError: Parse error in value at line 1.
2082+ >>> v = ['string = val, , ']
2083+ >>> c = ConfigObj(v)
2084+ Traceback (most recent call last):
2085+ ParseError: Parse error in value at line 1.
2086+ >>> v = ['string = ,,']
2087+ >>> c = ConfigObj(v)
2088+ Traceback (most recent call last):
2089+ ParseError: Parse error in value at line 1.
2090+ >>> v = ['string = ,, ']
2091+ >>> c = ConfigObj(v)
2092+ Traceback (most recent call last):
2093+ ParseError: Parse error in value at line 1.
2094+ >>> v = ['string = ,foo']
2095+ >>> c = ConfigObj(v)
2096+ Traceback (most recent call last):
2097+ ParseError: Parse error in value at line 1.
2098+ >>> v = ['string = foo, ']
2099+ >>> c = ConfigObj(v)
2100+ >>> c['string']
2101+ ['foo']
2102+ >>> v = ['string = foo, "']
2103+ >>> c = ConfigObj(v)
2104+ Traceback (most recent call last):
2105+ ParseError: Parse error in value at line 1.
2106+ """
2107+
2108+def _test_validation_with_preserve_errors():
2109+ """
2110+ >>> v = Validator()
2111+ >>> spec = ['[section]', 'foo = integer']
2112+ >>> c = ConfigObj(configspec=spec)
2113+ >>> c.validate(v, preserve_errors=True)
2114+ {'section': False}
2115+ >>> c = ConfigObj(['[section]'], configspec=spec)
2116+ >>> c.validate(v)
2117+ False
2118+ >>> c.validate(v, preserve_errors=True)
2119+ {'section': {'foo': False}}
2120+ """
2121+
2122+
2123+# test _created on Section
2124+
2125+# TODO: Test BOM handling
2126+# TODO: Test error code for badly built multiline values
2127+# TODO: Test handling of StringIO
2128+# TODO: Test interpolation with writing
2129+
2130+
2131+if __name__ == '__main__':
2132+ # run the code tests in doctest format
2133+ #
2134+ testconfig1 = """\
2135+ key1= val # comment 1
2136+ key2= val # comment 2
2137+ # comment 3
2138+ [lev1a] # comment 4
2139+ key1= val # comment 5
2140+ key2= val # comment 6
2141+ # comment 7
2142+ [lev1b] # comment 8
2143+ key1= val # comment 9
2144+ key2= val # comment 10
2145+ # comment 11
2146+ [[lev2ba]] # comment 12
2147+ key1= val # comment 13
2148+ # comment 14
2149+ [[lev2bb]] # comment 15
2150+ key1= val # comment 16
2151+ # comment 17
2152+ [lev1c] # comment 18
2153+ # comment 19
2154+ [[lev2c]] # comment 20
2155+ # comment 21
2156+ [[[lev3c]]] # comment 22
2157+ key1 = val # comment 23"""
2158+ #
2159+ testconfig2 = """\
2160+ key1 = 'val1'
2161+ key2 = "val2"
2162+ key3 = val3
2163+ ["section 1"] # comment
2164+ keys11 = val1
2165+ keys12 = val2
2166+ keys13 = val3
2167+ [section 2]
2168+ keys21 = val1
2169+ keys22 = val2
2170+ keys23 = val3
2171+
2172+ [['section 2 sub 1']]
2173+ fish = 3
2174+ """
2175+ #
2176+ testconfig6 = '''
2177+ name1 = """ a single line value """ # comment
2178+ name2 = \''' another single line value \''' # comment
2179+ name3 = """ a single line value """
2180+ name4 = \''' another single line value \'''
2181+ [ "multi section" ]
2182+ name1 = """
2183+ Well, this is a
2184+ multiline value
2185+ """
2186+ name2 = \'''
2187+ Well, this is a
2188+ multiline value
2189+ \'''
2190+ name3 = """
2191+ Well, this is a
2192+ multiline value
2193+ """ # a comment
2194+ name4 = \'''
2195+ Well, this is a
2196+ multiline value
2197+ \''' # I guess this is a comment too
2198+ '''
2199+ #
2200+ # these cannot be put among the doctests, because the doctest module
2201+ # does a string.expandtabs() on all of them, sigh
2202+ oneTabCfg = ['[sect]', '\t[[sect]]', '\t\tfoo = bar']
2203+ twoTabsCfg = ['[sect]', '\t\t[[sect]]', '\t\t\t\tfoo = bar']
2204+ tabsAndSpacesCfg = ['[sect]', '\t \t [[sect]]', '\t \t \t \t foo = bar']
2205+ #
2206+ import doctest
2207+ m = sys.modules.get('__main__')
2208+ globs = m.__dict__.copy()
2209+ a = ConfigObj(testconfig1.split('\n'), raise_errors=True)
2210+ b = ConfigObj(testconfig2.split('\n'), raise_errors=True)
2211+ i = ConfigObj(testconfig6.split('\n'), raise_errors=True)
2212+ globs.update({'INTP_VER': INTP_VER, 'a': a, 'b': b, 'i': i,
2213+ 'oneTabCfg': oneTabCfg, 'twoTabsCfg': twoTabsCfg,
2214+ 'tabsAndSpacesCfg': tabsAndSpacesCfg})
2215+ doctest.testmod(m, globs=globs)
2216+
2217+ import configobj
2218+ doctest.testmod(configobj, globs=globs)
2219+
2220+
2221+# Man alive I prefer unittest ;-)
\ No newline at end of file
--- branches/tl3_0/python-lib/configobj/configobj.py (revision 39)
+++ branches/tl3_0/python-lib/configobj/configobj.py (revision 40)
<
@@ -1,2499 +1,2468 @@
1-# configobj.py
2-# A config file reader/writer that supports nested sections in config files.
3-# Copyright (C) 2005-2008 Michael Foord, Nicola Larosa
4-# E-mail: fuzzyman AT voidspace DOT org DOT uk
5-# nico AT tekNico DOT net
6-
7-# ConfigObj 4
8-# http://www.voidspace.org.uk/python/configobj.html
9-
10-# Released subject to the BSD License
11-# Please see http://www.voidspace.org.uk/python/license.shtml
12-
13-# Scripts maintained at http://www.voidspace.org.uk/python/index.shtml
14-# For information about bugfixes, updates and support, please join the
15-# ConfigObj mailing list:
16-# http://lists.sourceforge.net/lists/listinfo/configobj-develop
17-# Comments, suggestions and bug reports welcome.
18-
19-from __future__ import generators
20-
21-import sys
22-INTP_VER = sys.version_info[:2]
23-if INTP_VER < (2, 2):
24- raise RuntimeError("Python v.2.2 or later needed")
25-
26-import os, re
27-compiler = None
28-try:
29- import compiler
30-except ImportError:
31- # for IronPython
32- pass
33-from types import StringTypes
34-from warnings import warn
35-try:
36- from codecs import BOM_UTF8, BOM_UTF16, BOM_UTF16_BE, BOM_UTF16_LE
37-except ImportError:
38- # Python 2.2 does not have these
39- # UTF-8
40- BOM_UTF8 = '\xef\xbb\xbf'
41- # UTF-16, little endian
42- BOM_UTF16_LE = '\xff\xfe'
43- # UTF-16, big endian
44- BOM_UTF16_BE = '\xfe\xff'
45- if sys.byteorder == 'little':
46- # UTF-16, native endianness
47- BOM_UTF16 = BOM_UTF16_LE
48- else:
49- # UTF-16, native endianness
50- BOM_UTF16 = BOM_UTF16_BE
51-
52-# A dictionary mapping BOM to
53-# the encoding to decode with, and what to set the
54-# encoding attribute to.
55-BOMS = {
56- BOM_UTF8: ('utf_8', None),
57- BOM_UTF16_BE: ('utf16_be', 'utf_16'),
58- BOM_UTF16_LE: ('utf16_le', 'utf_16'),
59- BOM_UTF16: ('utf_16', 'utf_16'),
60- }
61-# All legal variants of the BOM codecs.
62-# TODO: the list of aliases is not meant to be exhaustive, is there a
63-# better way ?
64-BOM_LIST = {
65- 'utf_16': 'utf_16',
66- 'u16': 'utf_16',
67- 'utf16': 'utf_16',
68- 'utf-16': 'utf_16',
69- 'utf16_be': 'utf16_be',
70- 'utf_16_be': 'utf16_be',
71- 'utf-16be': 'utf16_be',
72- 'utf16_le': 'utf16_le',
73- 'utf_16_le': 'utf16_le',
74- 'utf-16le': 'utf16_le',
75- 'utf_8': 'utf_8',
76- 'u8': 'utf_8',
77- 'utf': 'utf_8',
78- 'utf8': 'utf_8',
79- 'utf-8': 'utf_8',
80- }
81-
82-# Map of encodings to the BOM to write.
83-BOM_SET = {
84- 'utf_8': BOM_UTF8,
85- 'utf_16': BOM_UTF16,
86- 'utf16_be': BOM_UTF16_BE,
87- 'utf16_le': BOM_UTF16_LE,
88- None: BOM_UTF8
89- }
90-
91-
92-def match_utf8(encoding):
93- return BOM_LIST.get(encoding.lower()) == 'utf_8'
94-
95-
96-# Quote strings used for writing values
97-squot = "'%s'"
98-dquot = '"%s"'
99-noquot = "%s"
100-wspace_plus = ' \r\t\n\v\t\'"'
101-tsquot = '"""%s"""'
102-tdquot = "'''%s'''"
103-
104-try:
105- enumerate
106-except NameError:
107- def enumerate(obj):
108- """enumerate for Python 2.2."""
109- i = -1
110- for item in obj:
111- i += 1
112- yield i, item
113-
114-try:
115- True, False
116-except NameError:
117- True, False = 1, 0
118-
119-
120-__version__ = '4.5.3'
121-
122-__revision__ = '$Id: configobj.py 156 2006-01-31 14:57:08Z fuzzyman $'
123-
124-__docformat__ = "restructuredtext en"
125-
126-__all__ = (
127- '__version__',
128- 'DEFAULT_INDENT_TYPE',
129- 'DEFAULT_INTERPOLATION',
130- 'ConfigObjError',
131- 'NestingError',
132- 'ParseError',
133- 'DuplicateError',
134- 'ConfigspecError',
135- 'ConfigObj',
136- 'SimpleVal',
137- 'InterpolationError',
138- 'InterpolationLoopError',
139- 'MissingInterpolationOption',
140- 'RepeatSectionError',
141- 'ReloadError',
142- 'UnreprError',
143- 'UnknownType',
144- '__docformat__',
145- 'flatten_errors',
146-)
147-
148-DEFAULT_INTERPOLATION = 'configparser'
149-DEFAULT_INDENT_TYPE = ' '
150-MAX_INTERPOL_DEPTH = 10
151-
152-OPTION_DEFAULTS = {
153- 'interpolation': True,
154- 'raise_errors': False,
155- 'list_values': True,
156- 'create_empty': False,
157- 'file_error': False,
158- 'configspec': None,
159- 'stringify': True,
160- # option may be set to one of ('', ' ', '\t')
161- 'indent_type': None,
162- 'encoding': None,
163- 'default_encoding': None,
164- 'unrepr': False,
165- 'write_empty_values': False,
166-}
167-
168-
169-
170-def getObj(s):
171- s = "a=" + s
172- if compiler is None:
173- raise ImportError('compiler module not available')
174- p = compiler.parse(s)
175- return p.getChildren()[1].getChildren()[0].getChildren()[1]
176-
177-
178-class UnknownType(Exception):
179- pass
180-
181-
182-class Builder(object):
183-
184- def build(self, o):
185- m = getattr(self, 'build_' + o.__class__.__name__, None)
186- if m is None:
187- raise UnknownType(o.__class__.__name__)
188- return m(o)
189-
190- def build_List(self, o):
191- return map(self.build, o.getChildren())
192-
193- def build_Const(self, o):
194- return o.value
195-
196- def build_Dict(self, o):
197- d = {}
198- i = iter(map(self.build, o.getChildren()))
199- for el in i:
200- d[el] = i.next()
201- return d
202-
203- def build_Tuple(self, o):
204- return tuple(self.build_List(o))
205-
206- def build_Name(self, o):
207- if o.name == 'None':
208- return None
209- if o.name == 'True':
210- return True
211- if o.name == 'False':
212- return False
213-
214- # An undefined Name
215- raise UnknownType('Undefined Name')
216-
217- def build_Add(self, o):
218- real, imag = map(self.build_Const, o.getChildren())
219- try:
220- real = float(real)
221- except TypeError:
222- raise UnknownType('Add')
223- if not isinstance(imag, complex) or imag.real != 0.0:
224- raise UnknownType('Add')
225- return real+imag
226-
227- def build_Getattr(self, o):
228- parent = self.build(o.expr)
229- return getattr(parent, o.attrname)
230-
231- def build_UnarySub(self, o):
232- return -self.build_Const(o.getChildren()[0])
233-
234- def build_UnaryAdd(self, o):
235- return self.build_Const(o.getChildren()[0])
236-
237-
238-_builder = Builder()
239-
240-
241-def unrepr(s):
242- if not s:
243- return s
244- return _builder.build(getObj(s))
245-
246-
247-
248-class ConfigObjError(SyntaxError):
249- """
250- This is the base class for all errors that ConfigObj raises.
251- It is a subclass of SyntaxError.
252- """
253- def __init__(self, message='', line_number=None, line=''):
254- self.line = line
255- self.line_number = line_number
256- self.message = message
257- SyntaxError.__init__(self, message)
258-
259-
260-class NestingError(ConfigObjError):
261- """
262- This error indicates a level of nesting that doesn't match.
263- """
264-
265-
266-class ParseError(ConfigObjError):
267- """
268- This error indicates that a line is badly written.
269- It is neither a valid ``key = value`` line,
270- nor a valid section marker line.
271- """
272-
273-
274-class ReloadError(IOError):
275- """
276- A 'reload' operation failed.
277- This exception is a subclass of ``IOError``.
278- """
279- def __init__(self):
280- IOError.__init__(self, 'reload failed, filename is not set.')
281-
282-
283-class DuplicateError(ConfigObjError):
284- """
285- The keyword or section specified already exists.
286- """
287-
288-
289-class ConfigspecError(ConfigObjError):
290- """
291- An error occured whilst parsing a configspec.
292- """
293-
294-
295-class InterpolationError(ConfigObjError):
296- """Base class for the two interpolation errors."""
297-
298-
299-class InterpolationLoopError(InterpolationError):
300- """Maximum interpolation depth exceeded in string interpolation."""
301-
302- def __init__(self, option):
303- InterpolationError.__init__(
304- self,
305- 'interpolation loop detected in value "%s".' % option)
306-
307-
308-class RepeatSectionError(ConfigObjError):
309- """
310- This error indicates additional sections in a section with a
311- ``__many__`` (repeated) section.
312- """
313-
314-
315-class MissingInterpolationOption(InterpolationError):
316- """A value specified for interpolation was missing."""
317-
318- def __init__(self, option):
319- InterpolationError.__init__(
320- self,
321- 'missing option "%s" in interpolation.' % option)
322-
323-
324-class UnreprError(ConfigObjError):
325- """An error parsing in unrepr mode."""
326-
327-
328-
329-class InterpolationEngine(object):
330- """
331- A helper class to help perform string interpolation.
332-
333- This class is an abstract base class; its descendants perform
334- the actual work.
335- """
336-
337- # compiled regexp to use in self.interpolate()
338- _KEYCRE = re.compile(r"%\(([^)]*)\)s")
339-
340- def __init__(self, section):
341- # the Section instance that "owns" this engine
342- self.section = section
343-
344-
345- def interpolate(self, key, value):
346- def recursive_interpolate(key, value, section, backtrail):
347- """The function that does the actual work.
348-
349- ``value``: the string we're trying to interpolate.
350- ``section``: the section in which that string was found
351- ``backtrail``: a dict to keep track of where we've been,
352- to detect and prevent infinite recursion loops
353-
354- This is similar to a depth-first-search algorithm.
355- """
356- # Have we been here already?
357- if backtrail.has_key((key, section.name)):
358- # Yes - infinite loop detected
359- raise InterpolationLoopError(key)
360- # Place a marker on our backtrail so we won't come back here again
361- backtrail[(key, section.name)] = 1
362-
363- # Now start the actual work
364- match = self._KEYCRE.search(value)
365- while match:
366- # The actual parsing of the match is implementation-dependent,
367- # so delegate to our helper function
368- k, v, s = self._parse_match(match)
369- if k is None:
370- # That's the signal that no further interpolation is needed
371- replacement = v
372- else:
373- # Further interpolation may be needed to obtain final value
374- replacement = recursive_interpolate(k, v, s, backtrail)
375- # Replace the matched string with its final value
376- start, end = match.span()
377- value = ''.join((value[:start], replacement, value[end:]))
378- new_search_start = start + len(replacement)
379- # Pick up the next interpolation key, if any, for next time
380- # through the while loop
381- match = self._KEYCRE.search(value, new_search_start)
382-
383- # Now safe to come back here again; remove marker from backtrail
384- del backtrail[(key, section.name)]
385-
386- return value
387-
388- # Back in interpolate(), all we have to do is kick off the recursive
389- # function with appropriate starting values
390- value = recursive_interpolate(key, value, self.section, {})
391- return value
392-
393-
394- def _fetch(self, key):
395- """Helper function to fetch values from owning section.
396-
397- Returns a 2-tuple: the value, and the section where it was found.
398- """
399- # switch off interpolation before we try and fetch anything !
400- save_interp = self.section.main.interpolation
401- self.section.main.interpolation = False
402-
403- # Start at section that "owns" this InterpolationEngine
404- current_section = self.section
405- while True:
406- # try the current section first
407- val = current_section.get(key)
408- if val is not None:
409- break
410- # try "DEFAULT" next
411- val = current_section.get('DEFAULT', {}).get(key)
412- if val is not None:
413- break
414- # move up to parent and try again
415- # top-level's parent is itself
416- if current_section.parent is current_section:
417- # reached top level, time to give up
418- break
419- current_section = current_section.parent
420-
421- # restore interpolation to previous value before returning
422- self.section.main.interpolation = save_interp
423- if val is None:
424- raise MissingInterpolationOption(key)
425- return val, current_section
426-
427-
428- def _parse_match(self, match):
429- """Implementation-dependent helper function.
430-
431- Will be passed a match object corresponding to the interpolation
432- key we just found (e.g., "%(foo)s" or "$foo"). Should look up that
433- key in the appropriate config file section (using the ``_fetch()``
434- helper function) and return a 3-tuple: (key, value, section)
435-
436- ``key`` is the name of the key we're looking for
437- ``value`` is the value found for that key
438- ``section`` is a reference to the section where it was found
439-
440- ``key`` and ``section`` should be None if no further
441- interpolation should be performed on the resulting value
442- (e.g., if we interpolated "$$" and returned "$").
443- """
444- raise NotImplementedError()
445-
446-
447-
448-class ConfigParserInterpolation(InterpolationEngine):
449- """Behaves like ConfigParser."""
450- _KEYCRE = re.compile(r"%\(([^)]*)\)s")
451-
452- def _parse_match(self, match):
453- key = match.group(1)
454- value, section = self._fetch(key)
455- return key, value, section
456-
457-
458-
459-class TemplateInterpolation(InterpolationEngine):
460- """Behaves like string.Template."""
461- _delimiter = '$'
462- _KEYCRE = re.compile(r"""
463- \$(?:
464- (?P<escaped>\$) | # Two $ signs
465- (?P<named>[_a-z][_a-z0-9]*) | # $name format
466- {(?P<braced>[^}]*)} # ${name} format
467- )
468- """, re.IGNORECASE | re.VERBOSE)
469-
470- def _parse_match(self, match):
471- # Valid name (in or out of braces): fetch value from section
472- key = match.group('named') or match.group('braced')
473- if key is not None:
474- value, section = self._fetch(key)
475- return key, value, section
476- # Escaped delimiter (e.g., $$): return single delimiter
477- if match.group('escaped') is not None:
478- # Return None for key and section to indicate it's time to stop
479- return None, self._delimiter, None
480- # Anything else: ignore completely, just return it unchanged
481- return None, match.group(), None
482-
483-
484-interpolation_engines = {
485- 'configparser': ConfigParserInterpolation,
486- 'template': TemplateInterpolation,
487-}
488-
489-
490-
491-class Section(dict):
492- """
493- A dictionary-like object that represents a section in a config file.
494-
495- It does string interpolation if the 'interpolation' attribute
496- of the 'main' object is set to True.
497-
498- Interpolation is tried first from this object, then from the 'DEFAULT'
499- section of this object, next from the parent and its 'DEFAULT' section,
500- and so on until the main object is reached.
501-
502- A Section will behave like an ordered dictionary - following the
503- order of the ``scalars`` and ``sections`` attributes.
504- You can use this to change the order of members.
505-
506- Iteration follows the order: scalars, then sections.
507- """
508-
509- def __init__(self, parent, depth, main, indict=None, name=None):
510- """
511- * parent is the section above
512- * depth is the depth level of this section
513- * main is the main ConfigObj
514- * indict is a dictionary to initialise the section with
515- """
516- if indict is None:
517- indict = {}
518- dict.__init__(self)
519- # used for nesting level *and* interpolation
520- self.parent = parent
521- # used for the interpolation attribute
522- self.main = main
523- # level of nesting depth of this Section
524- self.depth = depth
525- # purely for information
526- self.name = name
527- #
528- self._initialise()
529- # we do this explicitly so that __setitem__ is used properly
530- # (rather than just passing to ``dict.__init__``)
531- for entry, value in indict.iteritems():
532- self[entry] = value
533-
534-
535- def _initialise(self):
536- # the sequence of scalar values in this Section
537- self.scalars = []
538- # the sequence of sections in this Section
539- self.sections = []
540- # for comments :-)
541- self.comments = {}
542- self.inline_comments = {}
543- # for the configspec
544- self.configspec = {}
545- self._order = []
546- self._configspec_comments = {}
547- self._configspec_inline_comments = {}
548- self._cs_section_comments = {}
549- self._cs_section_inline_comments = {}
550- # for defaults
551- self.defaults = []
552- self.default_values = {}
553-
554-
555- def _interpolate(self, key, value):
556- try:
557- # do we already have an interpolation engine?
558- engine = self._interpolation_engine
559- except AttributeError:
560- # not yet: first time running _interpolate(), so pick the engine
561- name = self.main.interpolation
562- if name == True: # note that "if name:" would be incorrect here
563- # backwards-compatibility: interpolation=True means use default
564- name = DEFAULT_INTERPOLATION
565- name = name.lower() # so that "Template", "template", etc. all work
566- class_ = interpolation_engines.get(name, None)
567- if class_ is None:
568- # invalid value for self.main.interpolation
569- self.main.interpolation = False
570- return value
571- else:
572- # save reference to engine so we don't have to do this again
573- engine = self._interpolation_engine = class_(self)
574- # let the engine do the actual work
575- return engine.interpolate(key, value)
576-
577-
578- def __getitem__(self, key):
579- """Fetch the item and do string interpolation."""
580- val = dict.__getitem__(self, key)
581- if self.main.interpolation and isinstance(val, StringTypes):
582- return self._interpolate(key, val)
583- return val
584-
585-
586- def __setitem__(self, key, value, unrepr=False):
587- """
588- Correctly set a value.
589-
590- Making dictionary values Section instances.
591- (We have to special case 'Section' instances - which are also dicts)
592-
593- Keys must be strings.
594- Values need only be strings (or lists of strings) if
595- ``main.stringify`` is set.
596-
597- `unrepr`` must be set when setting a value to a dictionary, without
598- creating a new sub-section.
599- """
600- if not isinstance(key, StringTypes):
601- raise ValueError('The key "%s" is not a string.' % key)
602-
603- # add the comment
604- if not self.comments.has_key(key):
605- self.comments[key] = []
606- self.inline_comments[key] = ''
607- # remove the entry from defaults
608- if key in self.defaults:
609- self.defaults.remove(key)
610- #
611- if isinstance(value, Section):
612- if not self.has_key(key):
613- self.sections.append(key)
614- dict.__setitem__(self, key, value)
615- elif isinstance(value, dict) and not unrepr:
616- # First create the new depth level,
617- # then create the section
618- if not self.has_key(key):
619- self.sections.append(key)
620- new_depth = self.depth + 1
621- dict.__setitem__(
622- self,
623- key,
624- Section(
625- self,
626- new_depth,
627- self.main,
628- indict=value,
629- name=key))
630- else:
631- if not self.has_key(key):
632- self.scalars.append(key)
633- if not self.main.stringify:
634- if isinstance(value, StringTypes):
635- pass
636- elif isinstance(value, (list, tuple)):
637- for entry in value:
638- if not isinstance(entry, StringTypes):
639- raise TypeError('Value is not a string "%s".' % entry)
640- else:
641- raise TypeError('Value is not a string "%s".' % value)
642- dict.__setitem__(self, key, value)
643-
644-
645- def __delitem__(self, key):
646- """Remove items from the sequence when deleting."""
647- dict. __delitem__(self, key)
648- if key in self.scalars:
649- self.scalars.remove(key)
650- else:
651- self.sections.remove(key)
652- del self.comments[key]
653- del self.inline_comments[key]
654-
655-
656- def get(self, key, default=None):
657- """A version of ``get`` that doesn't bypass string interpolation."""
658- try:
659- return self[key]
660- except KeyError:
661- return default
662-
663-
664- def update(self, indict):
665- """
666- A version of update that uses our ``__setitem__``.
667- """
668- for entry in indict:
669- self[entry] = indict[entry]
670-
671-
672- def pop(self, key, *args):
673- """
674- 'D.pop(k[,d]) -> v, remove specified key and return the corresponding value.
675- If key is not found, d is returned if given, otherwise KeyError is raised'
676- """
677- val = dict.pop(self, key, *args)
678- if key in self.scalars:
679- del self.comments[key]
680- del self.inline_comments[key]
681- self.scalars.remove(key)
682- elif key in self.sections:
683- del self.comments[key]
684- del self.inline_comments[key]
685- self.sections.remove(key)
686- if self.main.interpolation and isinstance(val, StringTypes):
687- return self._interpolate(key, val)
688- return val
689-
690-
691- def popitem(self):
692- """Pops the first (key,val)"""
693- sequence = (self.scalars + self.sections)
694- if not sequence:
695- raise KeyError(": 'popitem(): dictionary is empty'")
696- key = sequence[0]
697- val = self[key]
698- del self[key]
699- return key, val
700-
701-
702- def clear(self):
703- """
704- A version of clear that also affects scalars/sections
705- Also clears comments and configspec.
706-
707- Leaves other attributes alone :
708- depth/main/parent are not affected
709- """
710- dict.clear(self)
711- self.scalars = []
712- self.sections = []
713- self.comments = {}
714- self.inline_comments = {}
715- self.configspec = {}
716-
717-
718- def setdefault(self, key, default=None):
719- """A version of setdefault that sets sequence if appropriate."""
720- try:
721- return self[key]
722- except KeyError:
723- self[key] = default
724- return self[key]
725-
726-
727- def items(self):
728- """D.items() -> list of D's (key, value) pairs, as 2-tuples"""
729- return zip((self.scalars + self.sections), self.values())
730-
731-
732- def keys(self):
733- """D.keys() -> list of D's keys"""
734- return (self.scalars + self.sections)
735-
736-
737- def values(self):
738- """D.values() -> list of D's values"""
739- return [self[key] for key in (self.scalars + self.sections)]
740-
741-
742- def iteritems(self):
743- """D.iteritems() -> an iterator over the (key, value) items of D"""
744- return iter(self.items())
745-
746-
747- def iterkeys(self):
748- """D.iterkeys() -> an iterator over the keys of D"""
749- return iter((self.scalars + self.sections))
750-
751- __iter__ = iterkeys
752-
753-
754- def itervalues(self):
755- """D.itervalues() -> an iterator over the values of D"""
756- return iter(self.values())
757-
758-
759- def __repr__(self):
760- """x.__repr__() <==> repr(x)"""
761- return '{%s}' % ', '.join([('%s: %s' % (repr(key), repr(self[key])))
762- for key in (self.scalars + self.sections)])
763-
764- __str__ = __repr__
765- __str__.__doc__ = "x.__str__() <==> str(x)"
766-
767-
768- # Extra methods - not in a normal dictionary
769-
770- def dict(self):
771- """
772- Return a deepcopy of self as a dictionary.
773-
774- All members that are ``Section`` instances are recursively turned to
775- ordinary dictionaries - by calling their ``dict`` method.
776-
777- >>> n = a.dict()
778- >>> n == a
779- 1
780- >>> n is a
781- 0
782- """
783- newdict = {}
784- for entry in self:
785- this_entry = self[entry]
786- if isinstance(this_entry, Section):
787- this_entry = this_entry.dict()
788- elif isinstance(this_entry, list):
789- # create a copy rather than a reference
790- this_entry = list(this_entry)
791- elif isinstance(this_entry, tuple):
792- # create a copy rather than a reference
793- this_entry = tuple(this_entry)
794- newdict[entry] = this_entry
795- return newdict
796-
797-
798- def merge(self, indict):
799- """
800- A recursive update - useful for merging config files.
801-
802- >>> a = '''[section1]
803- ... option1 = True
804- ... [[subsection]]
805- ... more_options = False
806- ... # end of file'''.splitlines()
807- >>> b = '''# File is user.ini
808- ... [section1]
809- ... option1 = False
810- ... # end of file'''.splitlines()
811- >>> c1 = ConfigObj(b)
812- >>> c2 = ConfigObj(a)
813- >>> c2.merge(c1)
814- >>> c2
815- {'section1': {'option1': 'False', 'subsection': {'more_options': 'False'}}}
816- """
817- for key, val in indict.items():
818- if (key in self and isinstance(self[key], dict) and
819- isinstance(val, dict)):
820- self[key].merge(val)
821- else:
822- self[key] = val
823-
824-
825- def rename(self, oldkey, newkey):
826- """
827- Change a keyname to another, without changing position in sequence.
828-
829- Implemented so that transformations can be made on keys,
830- as well as on values. (used by encode and decode)
831-
832- Also renames comments.
833- """
834- if oldkey in self.scalars:
835- the_list = self.scalars
836- elif oldkey in self.sections:
837- the_list = self.sections
838- else:
839- raise KeyError('Key "%s" not found.' % oldkey)
840- pos = the_list.index(oldkey)
841- #
842- val = self[oldkey]
843- dict.__delitem__(self, oldkey)
844- dict.__setitem__(self, newkey, val)
845- the_list.remove(oldkey)
846- the_list.insert(pos, newkey)
847- comm = self.comments[oldkey]
848- inline_comment = self.inline_comments[oldkey]
849- del self.comments[oldkey]
850- del self.inline_comments[oldkey]
851- self.comments[newkey] = comm
852- self.inline_comments[newkey] = inline_comment
853-
854-
855- def walk(self, function, raise_errors=True,
856- call_on_sections=False, **keywargs):
857- """
858- Walk every member and call a function on the keyword and value.
859-
860- Return a dictionary of the return values
861-
862- If the function raises an exception, raise the errror
863- unless ``raise_errors=False``, in which case set the return value to
864- ``False``.
865-
866- Any unrecognised keyword arguments you pass to walk, will be pased on
867- to the function you pass in.
868-
869- Note: if ``call_on_sections`` is ``True`` then - on encountering a
870- subsection, *first* the function is called for the *whole* subsection,
871- and then recurses into it's members. This means your function must be
872- able to handle strings, dictionaries and lists. This allows you
873- to change the key of subsections as well as for ordinary members. The
874- return value when called on the whole subsection has to be discarded.
875-
876- See the encode and decode methods for examples, including functions.
877-
878- .. caution::
879-
880- You can use ``walk`` to transform the names of members of a section
881- but you mustn't add or delete members.
882-
883- >>> config = '''[XXXXsection]
884- ... XXXXkey = XXXXvalue'''.splitlines()
885- >>> cfg = ConfigObj(config)
886- >>> cfg
887- {'XXXXsection': {'XXXXkey': 'XXXXvalue'}}
888- >>> def transform(section, key):
889- ... val = section[key]
890- ... newkey = key.replace('XXXX', 'CLIENT1')
891- ... section.rename(key, newkey)
892- ... if isinstance(val, (tuple, list, dict)):
893- ... pass
894- ... else:
895- ... val = val.replace('XXXX', 'CLIENT1')
896- ... section[newkey] = val
897- >>> cfg.walk(transform, call_on_sections=True)
898- {'CLIENT1section': {'CLIENT1key': None}}
899- >>> cfg
900- {'CLIENT1section': {'CLIENT1key': 'CLIENT1value'}}
901- """
902- out = {}
903- # scalars first
904- for i in range(len(self.scalars)):
905- entry = self.scalars[i]
906- try:
907- val = function(self, entry, **keywargs)
908- # bound again in case name has changed
909- entry = self.scalars[i]
910- out[entry] = val
911- except Exception:
912- if raise_errors:
913- raise
914- else:
915- entry = self.scalars[i]
916- out[entry] = False
917- # then sections
918- for i in range(len(self.sections)):
919- entry = self.sections[i]
920- if call_on_sections:
921- try:
922- function(self, entry, **keywargs)
923- except Exception:
924- if raise_errors:
925- raise
926- else:
927- entry = self.sections[i]
928- out[entry] = False
929- # bound again in case name has changed
930- entry = self.sections[i]
931- # previous result is discarded
932- out[entry] = self[entry].walk(
933- function,
934- raise_errors=raise_errors,
935- call_on_sections=call_on_sections,
936- **keywargs)
937- return out
938-
939-
940- def decode(self, encoding):
941- """
942- Decode all strings and values to unicode, using the specified encoding.
943-
944- Works with subsections and list values.
945-
946- Uses the ``walk`` method.
947-
948- Testing ``encode`` and ``decode``.
949- >>> m = ConfigObj(a)
950- >>> m.decode('ascii')
951- >>> def testuni(val):
952- ... for entry in val:
953- ... if not isinstance(entry, unicode):
954- ... print >> sys.stderr, type(entry)
955- ... raise AssertionError, 'decode failed.'
956- ... if isinstance(val[entry], dict):
957- ... testuni(val[entry])
958- ... elif not isinstance(val[entry], unicode):
959- ... raise AssertionError, 'decode failed.'
960- >>> testuni(m)
961- >>> m.encode('ascii')
962- >>> a == m
963- 1
964- """
965- warn('use of ``decode`` is deprecated.', DeprecationWarning)
966- def decode(section, key, encoding=encoding, warn=True):
967- """ """
968- val = section[key]
969- if isinstance(val, (list, tuple)):
970- newval = []
971- for entry in val:
972- newval.append(entry.decode(encoding))
973- elif isinstance(val, dict):
974- newval = val
975- else:
976- newval = val.decode(encoding)
977- newkey = key.decode(encoding)
978- section.rename(key, newkey)
979- section[newkey] = newval
980- # using ``call_on_sections`` allows us to modify section names
981- self.walk(decode, call_on_sections=True)
982-
983-
984- def encode(self, encoding):
985- """
986- Encode all strings and values from unicode,
987- using the specified encoding.
988-
989- Works with subsections and list values.
990- Uses the ``walk`` method.
991- """
992- warn('use of ``encode`` is deprecated.', DeprecationWarning)
993- def encode(section, key, encoding=encoding):
994- """ """
995- val = section[key]
996- if isinstance(val, (list, tuple)):
997- newval = []
998- for entry in val:
999- newval.append(entry.encode(encoding))
1000- elif isinstance(val, dict):
1001- newval = val
1002- else:
1003- newval = val.encode(encoding)
1004- newkey = key.encode(encoding)
1005- section.rename(key, newkey)
1006- section[newkey] = newval
1007- self.walk(encode, call_on_sections=True)
1008-
1009-
1010- def istrue(self, key):
1011- """A deprecated version of ``as_bool``."""
1012- warn('use of ``istrue`` is deprecated. Use ``as_bool`` method '
1013- 'instead.', DeprecationWarning)
1014- return self.as_bool(key)
1015-
1016-
1017- def as_bool(self, key):
1018- """
1019- Accepts a key as input. The corresponding value must be a string or
1020- the objects (``True`` or 1) or (``False`` or 0). We allow 0 and 1 to
1021- retain compatibility with Python 2.2.
1022-
1023- If the string is one of ``True``, ``On``, ``Yes``, or ``1`` it returns
1024- ``True``.
1025-
1026- If the string is one of ``False``, ``Off``, ``No``, or ``0`` it returns
1027- ``False``.
1028-
1029- ``as_bool`` is not case sensitive.
1030-
1031- Any other input will raise a ``ValueError``.
1032-
1033- >>> a = ConfigObj()
1034- >>> a['a'] = 'fish'
1035- >>> a.as_bool('a')
1036- Traceback (most recent call last):
1037- ValueError: Value "fish" is neither True nor False
1038- >>> a['b'] = 'True'
1039- >>> a.as_bool('b')
1040- 1
1041- >>> a['b'] = 'off'
1042- >>> a.as_bool('b')
1043- 0
1044- """
1045- val = self[key]
1046- if val == True:
1047- return True
1048- elif val == False:
1049- return False
1050- else:
1051- try:
1052- if not isinstance(val, StringTypes):
1053- # TODO: Why do we raise a KeyError here?
1054- raise KeyError()
1055- else:
1056- return self.main._bools[val.lower()]
1057- except KeyError:
1058- raise ValueError('Value "%s" is neither True nor False' % val)
1059-
1060-
1061- def as_int(self, key):
1062- """
1063- A convenience method which coerces the specified value to an integer.
1064-
1065- If the value is an invalid literal for ``int``, a ``ValueError`` will
1066- be raised.
1067-
1068- >>> a = ConfigObj()
1069- >>> a['a'] = 'fish'
1070- >>> a.as_int('a')
1071- Traceback (most recent call last):
1072- ValueError: invalid literal for int(): fish
1073- >>> a['b'] = '1'
1074- >>> a.as_int('b')
1075- 1
1076- >>> a['b'] = '3.2'
1077- >>> a.as_int('b')
1078- Traceback (most recent call last):
1079- ValueError: invalid literal for int(): 3.2
1080- """
1081- return int(self[key])
1082-
1083-
1084- def as_float(self, key):
1085- """
1086- A convenience method which coerces the specified value to a float.
1087-
1088- If the value is an invalid literal for ``float``, a ``ValueError`` will
1089- be raised.
1090-
1091- >>> a = ConfigObj()
1092- >>> a['a'] = 'fish'
1093- >>> a.as_float('a')
1094- Traceback (most recent call last):
1095- ValueError: invalid literal for float(): fish
1096- >>> a['b'] = '1'
1097- >>> a.as_float('b')
1098- 1.0
1099- >>> a['b'] = '3.2'
1100- >>> a.as_float('b')
1101- 3.2000000000000002
1102- """
1103- return float(self[key])
1104-
1105-
1106- def restore_default(self, key):
1107- """
1108- Restore (and return) default value for the specified key.
1109-
1110- This method will only work for a ConfigObj that was created
1111- with a configspec and has been validated.
1112-
1113- If there is no default value for this key, ``KeyError`` is raised.
1114- """
1115- default = self.default_values[key]
1116- dict.__setitem__(self, key, default)
1117- if key not in self.defaults:
1118- self.defaults.append(key)
1119- return default
1120-
1121-
1122- def restore_defaults(self):
1123- """
1124- Recursively restore default values to all members
1125- that have them.
1126-
1127- This method will only work for a ConfigObj that was created
1128- with a configspec and has been validated.
1129-
1130- It doesn't delete or modify entries without default values.
1131- """
1132- for key in self.default_values:
1133- self.restore_default(key)
1134-
1135- for section in self.sections:
1136- self[section].restore_defaults()
1137-
1138-
1139-class ConfigObj(Section):
1140- """An object to read, create, and write config files."""
1141-
1142- _keyword = re.compile(r'''^ # line start
1143- (\s*) # indentation
1144- ( # keyword
1145- (?:".*?")| # double quotes
1146- (?:'.*?')| # single quotes
1147- (?:[^'"=].*?) # no quotes
1148- )
1149- \s*=\s* # divider
1150- (.*) # value (including list values and comments)
1151- $ # line end
1152- ''',
1153- re.VERBOSE)
1154-
1155- _sectionmarker = re.compile(r'''^
1156- (\s*) # 1: indentation
1157- ((?:\[\s*)+) # 2: section marker open
1158- ( # 3: section name open
1159- (?:"\s*\S.*?\s*")| # at least one non-space with double quotes
1160- (?:'\s*\S.*?\s*')| # at least one non-space with single quotes
1161- (?:[^'"\s].*?) # at least one non-space unquoted
1162- ) # section name close
1163- ((?:\s*\])+) # 4: section marker close
1164- \s*(\#.*)? # 5: optional comment
1165- $''',
1166- re.VERBOSE)
1167-
1168- # this regexp pulls list values out as a single string
1169- # or single values and comments
1170- # FIXME: this regex adds a '' to the end of comma terminated lists
1171- # workaround in ``_handle_value``
1172- _valueexp = re.compile(r'''^
1173- (?:
1174- (?:
1175- (
1176- (?:
1177- (?:
1178- (?:".*?")| # double quotes
1179- (?:'.*?')| # single quotes
1180- (?:[^'",\#][^,\#]*?) # unquoted
1181- )
1182- \s*,\s* # comma
1183- )* # match all list items ending in a comma (if any)
1184- )
1185- (
1186- (?:".*?")| # double quotes
1187- (?:'.*?')| # single quotes
1188- (?:[^'",\#\s][^,]*?)| # unquoted
1189- (?:(?<!,)) # Empty value
1190- )? # last item in a list - or string value
1191- )|
1192- (,) # alternatively a single comma - empty list
1193- )
1194- \s*(\#.*)? # optional comment
1195- $''',
1196- re.VERBOSE)
1197-
1198- # use findall to get the members of a list value
1199- _listvalueexp = re.compile(r'''
1200- (
1201- (?:".*?")| # double quotes
1202- (?:'.*?')| # single quotes
1203- (?:[^'",\#].*?) # unquoted
1204- )
1205- \s*,\s* # comma
1206- ''',
1207- re.VERBOSE)
1208-
1209- # this regexp is used for the value
1210- # when lists are switched off
1211- _nolistvalue = re.compile(r'''^
1212- (
1213- (?:".*?")| # double quotes
1214- (?:'.*?')| # single quotes
1215- (?:[^'"\#].*?)| # unquoted
1216- (?:) # Empty value
1217- )
1218- \s*(\#.*)? # optional comment
1219- $''',
1220- re.VERBOSE)
1221-
1222- # regexes for finding triple quoted values on one line
1223- _single_line_single = re.compile(r"^'''(.*?)'''\s*(#.*)?$")
1224- _single_line_double = re.compile(r'^"""(.*?)"""\s*(#.*)?$')
1225- _multi_line_single = re.compile(r"^(.*?)'''\s*(#.*)?$")
1226- _multi_line_double = re.compile(r'^(.*?)"""\s*(#.*)?$')
1227-
1228- _triple_quote = {
1229- "'''": (_single_line_single, _multi_line_single),
1230- '"""': (_single_line_double, _multi_line_double),
1231- }
1232-
1233- # Used by the ``istrue`` Section method
1234- _bools = {
1235- 'yes': True, 'no': False,
1236- 'on': True, 'off': False,
1237- '1': True, '0': False,
1238- 'true': True, 'false': False,
1239- }
1240-
1241-
1242- def __init__(self, infile=None, options=None, **kwargs):
1243- """
1244- Parse a config file or create a config file object.
1245-
1246- ``ConfigObj(infile=None, options=None, **kwargs)``
1247- """
1248- # init the superclass
1249- Section.__init__(self, self, 0, self)
1250-
1251- if infile is None:
1252- infile = []
1253- if options is None:
1254- options = {}
1255- else:
1256- options = dict(options)
1257-
1258- # keyword arguments take precedence over an options dictionary
1259- options.update(kwargs)
1260-
1261- defaults = OPTION_DEFAULTS.copy()
1262- # TODO: check the values too.
1263- for entry in options:
1264- if entry not in defaults:
1265- raise TypeError('Unrecognised option "%s".' % entry)
1266-
1267- # Add any explicit options to the defaults
1268- defaults.update(options)
1269- self._initialise(defaults)
1270- configspec = defaults['configspec']
1271- self._original_configspec = configspec
1272- self._load(infile, configspec)
1273-
1274-
1275- def _load(self, infile, configspec):
1276- if isinstance(infile, StringTypes):
1277- self.filename = infile
1278- if os.path.isfile(infile):
1279- h = open(infile, 'rb')
1280- infile = h.read() or []
1281- h.close()
1282- elif self.file_error:
1283- # raise an error if the file doesn't exist
1284- raise IOError('Config file not found: "%s".' % self.filename)
1285- else:
1286- # file doesn't already exist
1287- if self.create_empty:
1288- # this is a good test that the filename specified
1289- # isn't impossible - like on a non-existent device
1290- h = open(infile, 'w')
1291- h.write('')
1292- h.close()
1293- infile = []
1294-
1295- elif isinstance(infile, (list, tuple)):
1296- infile = list(infile)
1297-
1298- elif isinstance(infile, dict):
1299- # initialise self
1300- # the Section class handles creating subsections
1301- if isinstance(infile, ConfigObj):
1302- # get a copy of our ConfigObj
1303- infile = infile.dict()
1304-
1305- for entry in infile:
1306- self[entry] = infile[entry]
1307- del self._errors
1308-
1309- if configspec is not None:
1310- self._handle_configspec(configspec)
1311- else:
1312- self.configspec = None
1313- return
1314-
1315- elif hasattr(infile, 'read'):
1316- # This supports file like objects
1317- infile = infile.read() or []
1318- # needs splitting into lines - but needs doing *after* decoding
1319- # in case it's not an 8 bit encoding
1320- else:
1321- raise TypeError('infile must be a filename, file like object, or list of lines.')
1322-
1323- if infile:
1324- # don't do it for the empty ConfigObj
1325- infile = self._handle_bom(infile)
1326- # infile is now *always* a list
1327- #
1328- # Set the newlines attribute (first line ending it finds)
1329- # and strip trailing '\n' or '\r' from lines
1330- for line in infile:
1331- if (not line) or (line[-1] not in ('\r', '\n', '\r\n')):
1332- continue
1333- for end in ('\r\n', '\n', '\r'):
1334- if line.endswith(end):
1335- self.newlines = end
1336- break
1337- break
1338-
1339- infile = [line.rstrip('\r\n') for line in infile]
1340-
1341- self._parse(infile)
1342- # if we had any errors, now is the time to raise them
1343- if self._errors:
1344- info = "at line %s." % self._errors[0].line_number
1345- if len(self._errors) > 1:
1346- msg = "Parsing failed with several errors.\nFirst error %s" % info
1347- error = ConfigObjError(msg)
1348- else:
1349- error = self._errors[0]
1350- # set the errors attribute; it's a list of tuples:
1351- # (error_type, message, line_number)
1352- error.errors = self._errors
1353- # set the config attribute
1354- error.config = self
1355- raise error
1356- # delete private attributes
1357- del self._errors
1358-
1359- if configspec is None:
1360- self.configspec = None
1361- else:
1362- self._handle_configspec(configspec)
1363-
1364-
1365- def _initialise(self, options=None):
1366- if options is None:
1367- options = OPTION_DEFAULTS
1368-
1369- # initialise a few variables
1370- self.filename = None
1371- self._errors = []
1372- self.raise_errors = options['raise_errors']
1373- self.interpolation = options['interpolation']
1374- self.list_values = options['list_values']
1375- self.create_empty = options['create_empty']
1376- self.file_error = options['file_error']
1377- self.stringify = options['stringify']
1378- self.indent_type = options['indent_type']
1379- self.encoding = options['encoding']
1380- self.default_encoding = options['default_encoding']
1381- self.BOM = False
1382- self.newlines = None
1383- self.write_empty_values = options['write_empty_values']
1384- self.unrepr = options['unrepr']
1385-
1386- self.initial_comment = []
1387- self.final_comment = []
1388- self.configspec = {}
1389-
1390- # Clear section attributes as well
1391- Section._initialise(self)
1392-
1393-
1394- def __repr__(self):
1395- return ('ConfigObj({%s})' %
1396- ', '.join([('%s: %s' % (repr(key), repr(self[key])))
1397- for key in (self.scalars + self.sections)]))