diff options
-rw-r--r-- | configobj.py | 112 | ||||
-rw-r--r-- | docs/configobj.txt | 49 | ||||
-rw-r--r-- | docs/validate.txt | 10 | ||||
-rw-r--r-- | test_configobj.py | 17 | ||||
-rw-r--r-- | validate.py | 76 |
5 files changed, 116 insertions, 148 deletions
diff --git a/configobj.py b/configobj.py index 7675f84..90fcdc8 100644 --- a/configobj.py +++ b/configobj.py @@ -30,7 +30,6 @@ except ImportError: # for IronPython pass -from warnings import warn try: from codecs import BOM_UTF8, BOM_UTF16, BOM_UTF16_BE, BOM_UTF16_LE @@ -816,7 +815,7 @@ class Section(dict): >>> c2 = ConfigObj(a) >>> c2.merge(c1) >>> c2 - {'section1': {'option1': 'False', 'subsection': {'more_options': 'False'}}} + ConfigObj({'section1': {'option1': 'False', 'subsection': {'more_options': 'False'}}}) """ for key, val in indict.items(): if (key in self and isinstance(self[key], dict) and @@ -888,7 +887,7 @@ class Section(dict): ... XXXXkey = XXXXvalue'''.splitlines() >>> cfg = ConfigObj(config) >>> cfg - {'XXXXsection': {'XXXXkey': 'XXXXvalue'}} + ConfigObj({'XXXXsection': {'XXXXkey': 'XXXXvalue'}}) >>> def transform(section, key): ... val = section[key] ... newkey = key.replace('XXXX', 'CLIENT1') @@ -901,7 +900,7 @@ class Section(dict): >>> cfg.walk(transform, call_on_sections=True) {'CLIENT1section': {'CLIENT1key': None}} >>> cfg - {'CLIENT1section': {'CLIENT1key': 'CLIENT1value'}} + ConfigObj({'CLIENT1section': {'CLIENT1key': 'CLIENT1value'}}) """ out = {} # scalars first @@ -941,83 +940,6 @@ class Section(dict): return out - def decode(self, encoding): - """ - Decode all strings and values to unicode, using the specified encoding. - - Works with subsections and list values. - - Uses the ``walk`` method. - - Testing ``encode`` and ``decode``. - >>> m = ConfigObj(a) - >>> m.decode('ascii') - >>> def testuni(val): - ... for entry in val: - ... if not isinstance(entry, unicode): - ... print >> sys.stderr, type(entry) - ... raise AssertionError, 'decode failed.' - ... if isinstance(val[entry], dict): - ... testuni(val[entry]) - ... elif not isinstance(val[entry], unicode): - ... raise AssertionError, 'decode failed.' - >>> testuni(m) - >>> m.encode('ascii') - >>> a == m - 1 - """ - warn('use of ``decode`` is deprecated.', DeprecationWarning) - def decode(section, key, encoding=encoding, warn=True): - """ """ - val = section[key] - if isinstance(val, (list, tuple)): - newval = [] - for entry in val: - newval.append(entry.decode(encoding)) - elif isinstance(val, dict): - newval = val - else: - newval = val.decode(encoding) - newkey = key.decode(encoding) - section.rename(key, newkey) - section[newkey] = newval - # using ``call_on_sections`` allows us to modify section names - self.walk(decode, call_on_sections=True) - - - def encode(self, encoding): - """ - Encode all strings and values from unicode, - using the specified encoding. - - Works with subsections and list values. - Uses the ``walk`` method. - """ - warn('use of ``encode`` is deprecated.', DeprecationWarning) - def encode(section, key, encoding=encoding): - """ """ - val = section[key] - if isinstance(val, (list, tuple)): - newval = [] - for entry in val: - newval.append(entry.encode(encoding)) - elif isinstance(val, dict): - newval = val - else: - newval = val.encode(encoding) - newkey = key.encode(encoding) - section.rename(key, newkey) - section[newkey] = newval - self.walk(encode, call_on_sections=True) - - - def istrue(self, key): - """A deprecated version of ``as_bool``.""" - warn('use of ``istrue`` is deprecated. Use ``as_bool`` method ' - 'instead.', DeprecationWarning) - return self.as_bool(key) - - def as_bool(self, key): """ Accepts a key as input. The corresponding value must be a string or @@ -1073,14 +995,14 @@ class Section(dict): >>> a['a'] = 'fish' >>> a.as_int('a') Traceback (most recent call last): - ValueError: invalid literal for int(): fish + ValueError: invalid literal for int() with base 10: 'fish' >>> a['b'] = '1' >>> a.as_int('b') 1 >>> a['b'] = '3.2' >>> a.as_int('b') Traceback (most recent call last): - ValueError: invalid literal for int(): 3.2 + ValueError: invalid literal for int() with base 10: '3.2' """ return int(self[key]) @@ -1105,7 +1027,29 @@ class Section(dict): 3.2000000000000002 """ return float(self[key]) - + + + def as_list(self, key): + """ + A convenience method which fetches the specified value, guaranteeing + that it is a list. + + >>> a = ConfigObj() + >>> a['a'] = 1 + >>> a.as_list('a') + [1] + >>> a['a'] = (1,) + >>> a.as_list('a') + [1] + >>> a['a'] = [1] + >>> a.as_list('a') + [1] + """ + result = self[key] + if isinstance(result, (tuple, list)): + return list(result) + return [result] + def restore_default(self, key): """ diff --git a/docs/configobj.txt b/docs/configobj.txt index cec093c..1cb79c5 100644 --- a/docs/configobj.txt +++ b/docs/configobj.txt @@ -1274,9 +1274,6 @@ Section Methods This method renames a key, without affecting its position in the sequence. - It is mainly implemented for the ``encode`` and ``decode`` methods, which - provide some Unicode support. - * **merge** ``merge(indict)`` @@ -1315,24 +1312,6 @@ Section Methods This method can be used to transform values and names. See `walking a section`_ for examples and explanation. -* **decode** - - ``decode(encoding)`` - - This method decodes names and values into Unicode objects, using the - supplied encoding. - -* **encode** - - ``encode(encoding)`` - - This method is the opposite of ``decode`` {sm;:!:}. - - It encodes names and values using the supplied encoding. If any of your - names/values are strings rather than Unicode, Python will have to do an - implicit decode first. (This method uses ``sys.defaultencoding`` for - implicit decodes.) - * **as_bool** ``as_bool(key)`` @@ -1353,11 +1332,6 @@ Section Methods false, no, off, 0 - .. note:: - - In ConfigObj 4.1.0, this method was called ``istrue``. That method is - now deprecated and will issue a warning when used. It will go away - in a future release. * **as_int** @@ -1367,6 +1341,7 @@ Section Methods It raises a ``ValueError`` if the conversion can't be done. + * **as_float** ``as_float(key)`` @@ -1375,6 +1350,17 @@ Section Methods It raises a ``ValueError`` if the conversion can't be done. + +* **as_list** + + ``as_list(key)`` + + This returns the value contained in the specified key as a list. + + If it isn't a list it will be wrapped as a list so that you can + guarantee the returned value will be a list. + + * **restore_default** ``restore_default(key)`` @@ -1451,13 +1437,6 @@ note that walk discards the return value when it calls your function. Examples -------- -Examples that use the walk method are the ``encode`` and ``decode`` methods. -They both define a function and pass it to walk. Because these functions -transform names as well as values (from byte strings to Unicode) they set -``call_on_sections=True``. - -To see how they do it, *read the source Luke* {sm;:cool:}. - You can use this for transforming all values in your ConfigObj. For example you might like the nested lists from ConfigObj 3. This was provided by the listquote_ module. You could switch off the parsing for list values @@ -1533,7 +1512,6 @@ change the values if appropriate. # Because ``walk`` doesn't recognise the ``encode`` argument # it passes it to our function. config.walk(string_escape, encode=True) - {-coloring} Here's a simple example of using ``walk`` to transform names and values. One @@ -2345,6 +2323,9 @@ From version 4 it lists all releases and changes. * You can now have normal sections inside configspec sections that use __many__ * You can now create an empty ConfigObj with a configspec, programmatically set values and then validate * A section that was supplied as a value (or vice-versa) in the actual config file would cause an exception during validation (the config file is still broken of course, but it is now handled gracefully) +* Added ``as_list`` method +* Removed the deprecated ``istrue``, ``encode`` and ``decode`` methods +* Running test_configobj now also runs the doctests in the configobj module As a consequence of the changes to configspec handling, when you create a ConfigObj instance and provide a configspec, the configspec attribute is only set on the ConfigObj instance - it isn't set on the sections until you validate. You also can't set the configspec attribute to be a dictionary. This wasn't documented but did work previously. diff --git a/docs/validate.txt b/docs/validate.txt index cbb3ce2..45522a9 100644 --- a/docs/validate.txt +++ b/docs/validate.txt @@ -8,7 +8,7 @@ :Authors: `Michael Foord`_, `Nicola Larosa`_, `Mark Andrews`_ -:Version: Validate 0.3.2 +:Version: Validate 1.0.0 :Date: 2008/02/24 :Homepage: `Validate Homepage`_ :License: `BSD License`_ @@ -614,6 +614,14 @@ to specify arguments for 'mixed_lists'. CHANGELOG ========= +2009/04/13 - Version 1.0.0 +-------------------------- + +BUGFIX: can now handle multiline strings. + +As there are no known bugs or outstanding feature requests I am marking this 1.0. + + 2008/02/24 - Version 0.3.2 -------------------------- diff --git a/test_configobj.py b/test_configobj.py index 51697b5..2d105a0 100644 --- a/test_configobj.py +++ b/test_configobj.py @@ -1984,6 +1984,20 @@ def _test_pickle(): >>> new.validate(v) 1 """ + +def _test_as_list(): + """ + >>> a = ConfigObj() + >>> a['a'] = 1 + >>> a.as_list('a') + [1] + >>> a['a'] = (1,) + >>> a.as_list('a') + [1] + >>> a['a'] = [1] + >>> a.as_list('a') + [1] + """ # drop support for Python 2.2 ? @@ -2080,6 +2094,9 @@ if __name__ == '__main__': 'oneTabCfg': oneTabCfg, 'twoTabsCfg': twoTabsCfg, 'tabsAndSpacesCfg': tabsAndSpacesCfg}) doctest.testmod(m, globs=globs) + + import configobj + doctest.testmod(configobj, globs=globs) # Man alive I prefer unittest ;-)
\ No newline at end of file diff --git a/validate.py b/validate.py index a5512e8..aeba0dc 100644 --- a/validate.py +++ b/validate.py @@ -167,13 +167,7 @@ __all__ = ( ) -import sys -INTP_VER = sys.version_info[:2] -if INTP_VER < (2, 2): - raise RuntimeError("Python v.2.2 or later needed") - import re -StringTypes = (str, unicode) _list_arg = re.compile(r''' @@ -197,7 +191,7 @@ _list_arg = re.compile(r''' ) \) ) -''', re.VERBOSE) # two groups +''', re.VERBOSE | re.DOTALL) # two groups _list_members = re.compile(r''' ( @@ -208,7 +202,7 @@ _list_members = re.compile(r''' (?: (?:\s*,\s*)|(?:\s*$) # comma ) -''', re.VERBOSE) # one group +''', re.VERBOSE | re.DOTALL) # one group _paramstring = r''' (?: @@ -475,9 +469,9 @@ class Validator(object): ... # check that value is of the correct type. ... # possible valid inputs are integers or strings ... # that represent integers - ... if not isinstance(value, (int, long, StringTypes)): + ... if not isinstance(value, (int, long, basestring)): ... raise VdtTypeError(value) - ... elif isinstance(value, StringTypes): + ... elif isinstance(value, basestring): ... # if we are given a string ... # attempt to convert to an integer ... try: @@ -525,10 +519,10 @@ class Validator(object): """ # this regex does the initial parsing of the checks - _func_re = re.compile(r'(.+?)\((.*)\)') + _func_re = re.compile(r'(.+?)\((.*)\)', re.DOTALL) # this regex takes apart keyword arguments - _key_arg = re.compile(r'^([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.*)$') + _key_arg = re.compile(r'^([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.*)$', re.DOTALL) # this regex finds keyword=list(....) type values @@ -539,8 +533,8 @@ class Validator(object): # These regexes check a set of arguments for validity # and then pull the members out - _paramfinder = re.compile(_paramstring, re.VERBOSE) - _matchfinder = re.compile(_matchstring, re.VERBOSE) + _paramfinder = re.compile(_paramstring, re.VERBOSE | re.DOTALL) + _matchfinder = re.compile(_matchstring, re.VERBOSE | re.DOTALL) def __init__(self, functions=None): @@ -756,7 +750,7 @@ def _is_num_param(names, values, to_float=False): for (name, val) in zip(names, values): if val is None: out_params.append(val) - elif isinstance(val, (int, long, float, StringTypes)): + elif isinstance(val, (int, long, float, basestring)): try: out_params.append(fun(val)) except ValueError, e: @@ -813,9 +807,9 @@ def is_integer(value, min=None, max=None): 0 """ (min_val, max_val) = _is_num_param(('min', 'max'), (min, max)) - if not isinstance(value, (int, long, StringTypes)): + if not isinstance(value, (int, long, basestring)): raise VdtTypeError(value) - if isinstance(value, StringTypes): + if isinstance(value, basestring): # if it's a string - does it represent an integer ? try: value = int(value) @@ -865,7 +859,7 @@ def is_float(value, min=None, max=None): """ (min_val, max_val) = _is_num_param( ('min', 'max'), (min, max), to_float=True) - if not isinstance(value, (int, long, float, StringTypes)): + if not isinstance(value, (int, long, float, basestring)): raise VdtTypeError(value) if not isinstance(value, float): # if it's a string - does it represent a float ? @@ -930,7 +924,7 @@ def is_boolean(value): VdtTypeError: the value "up" is of the wrong type. """ - if isinstance(value, StringTypes): + if isinstance(value, basestring): try: return bool_dict[value.lower()] except KeyError: @@ -973,7 +967,7 @@ def is_ip_addr(value): Traceback (most recent call last): VdtTypeError: the value "0" is of the wrong type. """ - if not isinstance(value, StringTypes): + if not isinstance(value, basestring): raise VdtTypeError(value) value = value.strip() try: @@ -1015,7 +1009,7 @@ def is_list(value, min=None, max=None): VdtTypeError: the value "12" is of the wrong type. """ (min_len, max_len) = _is_num_param(('min', 'max'), (min, max)) - if isinstance(value, StringTypes): + if isinstance(value, basestring): raise VdtTypeError(value) try: num_members = len(value) @@ -1084,7 +1078,7 @@ def is_string(value, min=None, max=None): Traceback (most recent call last): VdtValueTooLongError: the value "1234" is too long. """ - if not isinstance(value, StringTypes): + if not isinstance(value, basestring): raise VdtTypeError(value) (min_len, max_len) = _is_num_param(('min', 'max'), (min, max)) try: @@ -1190,7 +1184,7 @@ def is_string_list(value, min=None, max=None): Traceback (most recent call last): VdtTypeError: the value "hello" is of the wrong type. """ - if isinstance(value, StringTypes): + if isinstance(value, basestring): raise VdtTypeError(value) return [is_string(mem) for mem in is_list(value, min, max)] @@ -1272,10 +1266,7 @@ def is_mixed_list(value, *args): ... 'yoda', ... '" for parameter "mixed_list".', ... ) - >>> if INTP_VER == (2, 2): - ... res_str = "".join(res_seq) - ... else: - ... res_str = "'".join(res_seq) + >>> res_str = "'".join(res_seq) >>> try: ... vtor.check('mixed_list("yoda")', ('a')) ... except VdtParamError, err: @@ -1309,7 +1300,7 @@ def is_option(value, *options): Traceback (most recent call last): VdtTypeError: the value "0" is of the wrong type. """ - if not isinstance(value, StringTypes): + if not isinstance(value, basestring): raise VdtTypeError(value) if not value in options: raise VdtValueError(value) @@ -1410,14 +1401,41 @@ def _test2(): 3 """ +def _test3(): + r""" + >>> vtor.check('string(default="")', '', missing=True) + '' + >>> vtor.check('string(default="\n")', '', missing=True) + '\n' + >>> print vtor.check('string(default="\n")', '', missing=True), + <BLANKLINE> + >>> vtor.check('string()', '\n') + '\n' + >>> vtor.check('string(default="\n\n\n")', '', missing=True) + '\n\n\n' + >>> vtor.check('string()', 'random \n text goes here\n\n') + 'random \n text goes here\n\n' + >>> vtor.check('string(default=" \nrandom text\ngoes \n here\n\n ")', + ... '', missing=True) + ' \nrandom text\ngoes \n here\n\n ' + >>> vtor.check("string(default='\n\n\n')", '', missing=True) + '\n\n\n' + >>> vtor.check("option('\n','a','b',default='\n')", '', missing=True) + '\n' + >>> vtor.check("string_list()", ['foo', '\n', 'bar']) + ['foo', '\n', 'bar'] + >>> vtor.check("string_list(default=list('\n'))", '', missing=True) + ['\n'] + """ + if __name__ == '__main__': # run the code tests in doctest format + import sys import doctest m = sys.modules.get('__main__') globs = m.__dict__.copy() globs.update({ - 'INTP_VER': INTP_VER, 'vtor': Validator(), }) doctest.testmod(m, globs=globs) |