]> granicus.if.org Git - python/commitdiff
add consistent support for the vars and default arguments on all
authorFred Drake <fdrake@acm.org>
Sat, 4 Sep 2010 04:35:34 +0000 (04:35 +0000)
committerFred Drake <fdrake@acm.org>
Sat, 4 Sep 2010 04:35:34 +0000 (04:35 +0000)
configuration parser classes
(http://bugs.python.org/issue9421)

Doc/library/configparser.rst
Lib/configparser.py
Lib/test/test_cfgparser.py
Misc/NEWS

index 87f5aa4ea5520206ba15fcab4b75c428da878373..6a75b263216572684fdf71d4c95e9b4d8eacd3ae 100644 (file)
@@ -135,7 +135,7 @@ keys within each section.
       *empty_lines_in_values* were added.
 
 
-.. class:: SafeConfigParser(defaults=None, dict_type=collections.OrderedDict, allow_no_value=False, delimiters=('=', ':'), comment_prefixes=('#', ';'), strict=False, empty_lines_in_values=True)
+.. class:: SafeConfigParser(defaults=None, dict_type=collections.OrderedDict, allow_no_value=False, delimiters=('=', ':'), comment_prefixes=_COMPATIBLE, strict=False, empty_lines_in_values=True)
 
    Derived class of :class:`ConfigParser` that implements a sane variant of the
    magical interpolation feature.  This implementation is more predictable as it
@@ -155,7 +155,7 @@ keys within each section.
       *empty_lines_in_values* were added.
 
 
-.. class:: ConfigParser(defaults=None, dict_type=collections.OrderedDict, allow_no_value=False, delimiters=('=', ':'), comment_prefixes=('#', ';'), strict=False, empty_lines_in_values=True)
+.. class:: ConfigParser(defaults=None, dict_type=collections.OrderedDict, allow_no_value=False, delimiters=('=', ':'), comment_prefixes=_COMPATIBLE, strict=False, empty_lines_in_values=True)
 
    Derived class of :class:`RawConfigParser` that implements the magical
    interpolation feature and adds optional arguments to the :meth:`get` and
@@ -366,39 +366,44 @@ RawConfigParser Objects
    Load configuration from a dictionary. Keys are section names, values are
    dictionaries with keys and values that should be present in the section. If
    the used dictionary type preserves order, sections and their keys will be
-   added in order.
+   added in order. Values are automatically converted to strings.
 
    Optional argument *source* specifies a context-specific name of the
    dictionary passed.  If not given, ``<dict>`` is used.
 
    .. versionadded:: 3.2
 
+.. method:: RawConfigParser.get(section, option, [vars, default])
 
-.. method:: RawConfigParser.get(section, option)
-
-   Get an *option* value for the named *section*.
+   Get an *option* value for the named *section*. If *vars* is provided, it
+   must be a dictionary.  The *option* is looked up in *vars* (if provided),
+   *section*, and in *DEFAULTSECT* in that order. If the key is not found and
+   *default* is provided, it is used as a fallback value. ``None`` can be
+   provided as a *default* value.
 
 
-.. method:: RawConfigParser.getint(section, option)
+.. method:: RawConfigParser.getint(section, option, [vars, default])
 
-   A convenience method which coerces the *option* in the specified *section* to an
-   integer.
+   A convenience method which coerces the *option* in the specified *section* to
+   an integer. See :meth:`get` for explanation of *vars* and *default*.
 
 
-.. method:: RawConfigParser.getfloat(section, option)
+.. method:: RawConfigParser.getfloat(section, option, [vars, default])
 
-   A convenience method which coerces the *option* in the specified *section* to a
-   floating point number.
+   A convenience method which coerces the *option* in the specified *section* to
+   a floating point number.  See :meth:`get` for explanation of *vars* and
+   *default*.
 
 
-.. method:: RawConfigParser.getboolean(section, option)
+.. method:: RawConfigParser.getboolean(section, option, [vars, default])
 
-   A convenience method which coerces the *option* in the specified *section* to a
-   Boolean value.  Note that the accepted values for the option are ``"1"``,
-   ``"yes"``, ``"true"``, and ``"on"``, which cause this method to return ``True``,
-   and ``"0"``, ``"no"``, ``"false"``, and ``"off"``, which cause it to return
-   ``False``.  These string values are checked in a case-insensitive manner.  Any
-   other value will cause it to raise :exc:`ValueError`.
+   A convenience method which coerces the *option* in the specified *section*
+   to a Boolean value.  Note that the accepted values for the option are
+   ``"1"``, ``"yes"``, ``"true"``, and ``"on"``, which cause this method to
+   return ``True``, and ``"0"``, ``"no"``, ``"false"``, and ``"off"``, which
+   cause it to return ``False``.  These string values are checked in
+   a case-insensitive manner.  Any other value will cause it to raise
+   :exc:`ValueError`. See :meth:`get` for explanation of *vars* and *default*.
 
 
 .. method:: RawConfigParser.items(section)
@@ -475,17 +480,44 @@ can, consider using :class:`SafeConfigParser` which adds validation and escaping
 for the interpolation.
 
 
-.. method:: ConfigParser.get(section, option, raw=False, vars=None)
+.. method:: ConfigParser.get(section, option, raw=False, [vars, default])
 
    Get an *option* value for the named *section*.  If *vars* is provided, it
    must be a dictionary.  The *option* is looked up in *vars* (if provided),
-   *section*, and in *defaults* in that order.
+   *section*, and in *DEFAULTSECT* in that order. If the key is not found and
+   *default* is provided, it is used as a fallback value. ``None`` can be
+   provided as a *default* value.
 
    All the ``'%'`` interpolations are expanded in the return values, unless the
    *raw* argument is true.  Values for interpolation keys are looked up in the
    same manner as the option.
 
 
+.. method:: ConfigParser.getint(section, option, raw=False, [vars, default])
+
+   A convenience method which coerces the *option* in the specified *section* to
+   an integer. See :meth:`get` for explanation of *raw*, *vars* and *default*.
+
+
+.. method:: ConfigParser.getfloat(section, option, raw=False, [vars, default])
+
+   A convenience method which coerces the *option* in the specified *section* to
+   a floating point number. See :meth:`get` for explanation of *raw*, *vars*
+   and *default*.
+
+
+.. method:: ConfigParser.getboolean(section, option, raw=False, [vars, default])
+
+   A convenience method which coerces the *option* in the specified *section*
+   to a Boolean value.  Note that the accepted values for the option are
+   ``"1"``, ``"yes"``, ``"true"``, and ``"on"``, which cause this method to
+   return ``True``, and ``"0"``, ``"no"``, ``"false"``, and ``"off"``, which
+   cause it to return ``False``.  These string values are checked in
+   a case-insensitive manner.  Any other value will cause it to raise
+   :exc:`ValueError`. See :meth:`get` for explanation of *raw*, *vars* and
+   *default*.
+
+
 .. method:: ConfigParser.items(section, raw=False, vars=None)
 
    Return a list of ``(name, value)`` pairs for each option in the given
index fb39ac3343b07f600b545dae663eb793de4243eb..7f1514f902077cdbb1abe83ed730a05d2527641a 100644 (file)
@@ -24,9 +24,9 @@ ConfigParser -- responsible for parsing a list of
 
     methods:
 
-    __init__(defaults=None, dict_type=_default_dict,
-             delimiters=('=', ':'), comment_prefixes=('#', ';'),
-             strict=False, empty_lines_in_values=True, allow_no_value=False):
+    __init__(defaults=None, dict_type=_default_dict, allow_no_value=False,
+             delimiters=('=', ':'), comment_prefixes=_COMPATIBLE,
+             strict=False, empty_lines_in_values=True):
         Create the parser. When `defaults' is given, it is initialized into the
         dictionary or intrinsic defaults. The keys must be strings, the values
         must be appropriate for %()s string interpolation. Note that `__name__'
@@ -82,22 +82,24 @@ ConfigParser -- responsible for parsing a list of
         Read configuration from a dictionary. Keys are section names,
         values are dictionaries with keys and values that should be present
         in the section. If the used dictionary type preserves order, sections
-        and their keys will be added in order.
+        and their keys will be added in order. Values are automatically
+        converted to strings.
 
-    get(section, option, raw=False, vars=None)
+    get(section, option, raw=False, vars=None, default=_UNSET)
         Return a string value for the named option.  All % interpolations are
         expanded in the return values, based on the defaults passed into the
         constructor and the DEFAULT section.  Additional substitutions may be
         provided using the `vars' argument, which must be a dictionary whose
-        contents override any pre-existing defaults.
+        contents override any pre-existing defaults. If `option' is a key in
+        `vars', the value from `vars' is used.
 
-    getint(section, options)
+    getint(section, options, raw=False, vars=None, default=_UNSET)
         Like get(), but convert value to an integer.
 
-    getfloat(section, options)
+    getfloat(section, options, raw=False, vars=None, default=_UNSET)
         Like get(), but convert value to a float.
 
-    getboolean(section, options)
+    getboolean(section, options, raw=False, vars=None, default=_UNSET)
         Like get(), but convert value to a boolean (currently case
         insensitively defined as 0, false, no, off for False, and 1, true,
         yes, on for True).  Returns False or True.
@@ -353,6 +355,17 @@ class MissingSectionHeaderError(ParsingError):
         self.args = (filename, lineno, line)
 
 
+# Used in parsers to denote selecting a backwards-compatible inline comment
+# character behavior (; and # are comments at the start of a line, but ; only
+# inline)
+_COMPATIBLE = object()
+
+# Used in parser getters to indicate the default behaviour when a specific
+# option is not found it to raise an exception. Created to enable `None' as
+# a valid fallback value.
+_UNSET = object()
+
+
 class RawConfigParser:
     """ConfigParser that does not do interpolation."""
 
@@ -389,9 +402,9 @@ class RawConfigParser:
     OPTCRE_NV = re.compile(_OPT_NV_TMPL.format(delim="=|:"), re.VERBOSE)
     # Compiled regular expression for matching leading whitespace in a line
     NONSPACECRE = re.compile(r"\S")
-    # Select backwards-compatible inline comment character behavior
-    # (; and # are comments at the start of a line, but ; only inline)
-    _COMPATIBLE = object()
+    # Possible boolean values in the configuration.
+    BOOLEAN_STATES = {'1': True, 'yes': True, 'true': True, 'on': True,
+                      '0': False, 'no': False, 'false': False, 'off': False}
 
     def __init__(self, defaults=None, dict_type=_default_dict,
                  allow_no_value=False, *, delimiters=('=', ':'),
@@ -414,7 +427,7 @@ class RawConfigParser:
             else:
                 self._optcre = re.compile(self._OPT_TMPL.format(delim=d),
                                           re.VERBOSE)
-        if comment_prefixes is self._COMPATIBLE:
+        if comment_prefixes is _COMPATIBLE:
             self._startonly_comment_prefixes = ('#',)
             self._comment_prefixes = (';',)
         else:
@@ -528,6 +541,8 @@ class RawConfigParser:
                 elements_added.add(section)
             for key, value in keys.items():
                 key = self.optionxform(key)
+                if value is not None:
+                    value = str(value)
                 if self._strict and (section, key) in elements_added:
                     raise DuplicateOptionError(section, key, source)
                 elements_added.add((section, key))
@@ -542,21 +557,29 @@ class RawConfigParser:
         )
         self.read_file(fp, source=filename)
 
-    def get(self, section, option):
-        opt = self.optionxform(option)
-        if section not in self._sections:
-            if section != DEFAULTSECT:
-                raise NoSectionError(section)
-            if opt in self._defaults:
-                return self._defaults[opt]
+    def get(self, section, option, vars=None, default=_UNSET):
+        """Get an option value for a given section.
+
+        If `vars' is provided, it must be a dictionary. The option is looked up
+        in `vars' (if provided), `section', and in `DEFAULTSECT' in that order.
+        If the key is not found and `default' is provided, it is used as
+        a fallback value. `None' can be provided as a `default' value.
+        """
+        try:
+            d = self._unify_values(section, vars)
+        except NoSectionError:
+            if default is _UNSET:
+                raise
             else:
+                return default
+        option = self.optionxform(option)
+        try:
+            return d[option]
+        except KeyError:
+            if default is _UNSET:
                 raise NoOptionError(option, section)
-        elif opt in self._sections[section]:
-            return self._sections[section][opt]
-        elif opt in self._defaults:
-            return self._defaults[opt]
-        else:
-            raise NoOptionError(option, section)
+            else:
+                return default
 
     def items(self, section):
         try:
@@ -571,23 +594,35 @@ class RawConfigParser:
             del d["__name__"]
         return d.items()
 
-    def _get(self, section, conv, option):
-        return conv(self.get(section, option))
+    def _get(self, section, conv, option, *args, **kwargs):
+        return conv(self.get(section, option, *args, **kwargs))
 
-    def getint(self, section, option):
-        return self._get(section, int, option)
-
-    def getfloat(self, section, option):
-        return self._get(section, float, option)
+    def getint(self, section, option, vars=None, default=_UNSET):
+        try:
+            return self._get(section, int, option, vars)
+        except (NoSectionError, NoOptionError):
+            if default is _UNSET:
+                raise
+            else:
+                return default
 
-    _boolean_states = {'1': True, 'yes': True, 'true': True, 'on': True,
-                       '0': False, 'no': False, 'false': False, 'off': False}
+    def getfloat(self, section, option, vars=None, default=_UNSET):
+        try:
+            return self._get(section, float, option, vars)
+        except (NoSectionError, NoOptionError):
+            if default is _UNSET:
+                raise
+            else:
+                return default
 
-    def getboolean(self, section, option):
-        v = self.get(section, option)
-        if v.lower() not in self._boolean_states:
-            raise ValueError('Not a boolean: %s' % v)
-        return self._boolean_states[v.lower()]
+    def getboolean(self, section, option, vars=None, default=_UNSET):
+        try:
+            return self._get(section, self._convert_to_boolean, option, vars)
+        except (NoSectionError, NoOptionError):
+            if default is _UNSET:
+                raise
+            else:
+                return default
 
     def optionxform(self, optionstr):
         return optionstr.lower()
@@ -797,15 +832,43 @@ class RawConfigParser:
         exc.append(lineno, repr(line))
         return exc
 
+    def _unify_values(self, section, vars):
+        """Create a copy of the DEFAULTSECT with values from a specific
+        `section' and the `vars' dictionary. If provided, values in `vars'
+        take precendence.
+        """
+        d = self._defaults.copy()
+        try:
+            d.update(self._sections[section])
+        except KeyError:
+            if section != DEFAULTSECT:
+                raise NoSectionError(section)
+        # Update with the entry specific variables
+        if vars:
+            for key, value in vars.items():
+                if value is not None:
+                    value = str(value)
+                d[self.optionxform(key)] = value
+        return d
+
+    def _convert_to_boolean(self, value):
+        """Return a boolean value translating from other types if necessary.
+        """
+        if value.lower() not in self.BOOLEAN_STATES:
+            raise ValueError('Not a boolean: %s' % value)
+        return self.BOOLEAN_STATES[value.lower()]
+
 
 class ConfigParser(RawConfigParser):
     """ConfigParser implementing interpolation."""
 
-    def get(self, section, option, raw=False, vars=None):
+    def get(self, section, option, raw=False, vars=None, default=_UNSET):
         """Get an option value for a given section.
 
         If `vars' is provided, it must be a dictionary. The option is looked up
-        in `vars' (if provided), `section', and in `defaults' in that order.
+        in `vars' (if provided), `section', and in `DEFAULTSECT' in that order.
+        If the key is not found and `default' is provided, it is used as
+        a fallback value. `None' can be provided as a `default' value.
 
         All % interpolations are expanded in the return values, unless the
         optional argument `raw' is true.  Values for interpolation keys are
@@ -813,27 +876,56 @@ class ConfigParser(RawConfigParser):
 
         The section DEFAULT is special.
         """
-        d = self._defaults.copy()
         try:
-            d.update(self._sections[section])
-        except KeyError:
-            if section != DEFAULTSECT:
-                raise NoSectionError(section)
-        # Update with the entry specific variables
-        if vars:
-            for key, value in vars.items():
-                d[self.optionxform(key)] = value
+            d = self._unify_values(section, vars)
+        except NoSectionError:
+            if default is _UNSET:
+                raise
+            else:
+                return default
         option = self.optionxform(option)
         try:
             value = d[option]
         except KeyError:
-            raise NoOptionError(option, section)
+            if default is _UNSET:
+                raise NoOptionError(option, section)
+            else:
+                return default
 
         if raw or value is None:
             return value
         else:
             return self._interpolate(section, option, value, d)
 
+    def getint(self, section, option, raw=False, vars=None, default=_UNSET):
+        try:
+            return self._get(section, int, option, raw, vars)
+        except (NoSectionError, NoOptionError):
+            if default is _UNSET:
+                raise
+            else:
+                return default
+
+    def getfloat(self, section, option, raw=False, vars=None, default=_UNSET):
+        try:
+            return self._get(section, float, option, raw, vars)
+        except (NoSectionError, NoOptionError):
+            if default is _UNSET:
+                raise
+            else:
+                return default
+
+    def getboolean(self, section, option, raw=False, vars=None,
+                   default=_UNSET):
+        try:
+            return self._get(section, self._convert_to_boolean, option, raw,
+                             vars)
+        except (NoSectionError, NoOptionError):
+            if default is _UNSET:
+                raise
+            else:
+                return default
+
     def items(self, section, raw=False, vars=None):
         """Return a list of (name, value) tuples for each option in a section.
 
index a20678dc2cf007237917be79cb935b3106608ee0..3079cfb80ce6bbdd74b2a5d46752d4b84ae72106 100644 (file)
@@ -62,9 +62,10 @@ class BasicTestCase(CfgParserTestCaseClass):
              'Spaces',
              'Spacey Bar',
              'Spacey Bar From The Beginning',
+             'Types',
              ]
         if self.allow_no_value:
-            E.append(r'NoValue')
+            E.append('NoValue')
         E.sort()
         eq = self.assertEqual
         eq(L, E)
@@ -80,9 +81,43 @@ class BasicTestCase(CfgParserTestCaseClass):
         eq(cf.get('Commented Bar', 'baz'), 'qwe')
         eq(cf.get('Spaces', 'key with spaces'), 'value')
         eq(cf.get('Spaces', 'another with spaces'), 'splat!')
+        eq(cf.getint('Types', 'int'), 42)
+        eq(cf.get('Types', 'int'), "42")
+        self.assertAlmostEqual(cf.getfloat('Types', 'float'), 0.44)
+        eq(cf.get('Types', 'float'), "0.44")
+        eq(cf.getboolean('Types', 'boolean'), False)
         if self.allow_no_value:
             eq(cf.get('NoValue', 'option-without-value'), None)
 
+        # test vars= and default=
+        eq(cf.get('Foo Bar', 'foo', default='baz'), 'bar')
+        eq(cf.get('Foo Bar', 'foo', vars={'foo': 'baz'}), 'baz')
+        with self.assertRaises(configparser.NoSectionError):
+            cf.get('No Such Foo Bar', 'foo')
+        with self.assertRaises(configparser.NoOptionError):
+            cf.get('Foo Bar', 'no-such-foo')
+        eq(cf.get('No Such Foo Bar', 'foo', default='baz'), 'baz')
+        eq(cf.get('Foo Bar', 'no-such-foo', default='baz'), 'baz')
+        eq(cf.get('Spacey Bar', 'foo', default=None), 'bar')
+        eq(cf.get('No Such Spacey Bar', 'foo', default=None), None)
+        eq(cf.getint('Types', 'int', default=18), 42)
+        eq(cf.getint('Types', 'no-such-int', default=18), 18)
+        eq(cf.getint('Types', 'no-such-int', default="18"), "18") # sic!
+        self.assertAlmostEqual(cf.getfloat('Types', 'float',
+                                           default=0.0), 0.44)
+        self.assertAlmostEqual(cf.getfloat('Types', 'no-such-float',
+                                           default=0.0), 0.0)
+        eq(cf.getfloat('Types', 'no-such-float', default="0.0"), "0.0") # sic!
+        eq(cf.getboolean('Types', 'boolean', default=True), False)
+        eq(cf.getboolean('Types', 'no-such-boolean', default="yes"),
+           "yes") # sic!
+        eq(cf.getboolean('Types', 'no-such-boolean', default=True), True)
+        eq(cf.getboolean('No Such Types', 'boolean', default=True), True)
+        if self.allow_no_value:
+            eq(cf.get('NoValue', 'option-without-value', default=False), None)
+            eq(cf.get('NoValue', 'no-such-option-without-value',
+                      default=False), False)
+
         self.assertNotIn('__name__', cf.options("Foo Bar"),
                          '__name__ "option" should not be exposed by the API!')
 
@@ -127,6 +162,10 @@ foo[de]{0[0]}Deutsch
 [Spaces]
 key with spaces {0[1]} value
 another with spaces {0[0]} splat!
+[Types]
+int {0[1]} 42
+float {0[0]} 0.44
+boolean {0[0]} NO
 """.format(self.delimiters, self.comment_prefixes)
         if self.allow_no_value:
             config_string += (
@@ -194,7 +233,12 @@ another with spaces {0[0]} splat!
             "Spaces": {
                 "key with spaces": "value",
                 "another with spaces": "splat!",
-            }
+            },
+            "Types": {
+                "int": 42,
+                "float": 0.44,
+                "boolean": False,
+            },
         }
         if self.allow_no_value:
             config.update({
@@ -732,8 +776,11 @@ class SafeConfigParserTestCaseTrickyFile(CfgParserTestCaseClass):
                                          'no values here',
                                          'tricky interpolation',
                                          'more interpolation'])
-        #self.assertEqual(cf.getint('DEFAULT', 'go', vars={'interpolate': '-1'}),
-        #                 -1)
+        self.assertEqual(cf.getint('DEFAULT', 'go',
+                                   vars={'interpolate': '-1'}), -1)
+        with self.assertRaises(ValueError):
+            # no interpolation will happen
+            cf.getint('DEFAULT', 'go', raw=True, vars={'interpolate': '-1'})
         self.assertEqual(len(cf.get('strange', 'other').split('\n')), 4)
         self.assertEqual(len(cf.get('corruption', 'value').split('\n')), 10)
         longname = 'yeah, sections can be indented as well'
@@ -808,7 +855,7 @@ class SortedTestCase(RawConfigParserTestCase):
 
 class CompatibleTestCase(CfgParserTestCaseClass):
     config_class = configparser.RawConfigParser
-    comment_prefixes = configparser.RawConfigParser._COMPATIBLE
+    comment_prefixes = configparser._COMPATIBLE
 
     def test_comment_handling(self):
         config_string = textwrap.dedent("""\
index 7eb61072405a3ff505b710b77ad36435fda389f8..3c162a6d99cb7fe134ac0965618a556de698c681 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -158,6 +158,9 @@ Library
 - Issue #9753: Fixed socket.dup, which did not always work correctly
   on Windows.
 
+- Issue #9421: Made the get<type> methods consistently accept the vars
+  and default arguments on all parser classes.
+
 - Issue #7005: Fixed output of None values for RawConfigParser.write and
   ConfigParser.write.