Rev 6395: Configuration option value can be overridden by os environ variables in file:///home/vila/src/bzr/bugs/907279-override-from-env/

Vincent Ladeuil v.ladeuil+lp at free.fr
Thu Dec 22 08:53:35 UTC 2011


At file:///home/vila/src/bzr/bugs/907279-override-from-env/

------------------------------------------------------------
revno: 6395
revision-id: v.ladeuil+lp at free.fr-20111222085335-modhecpx6s1io7ku
parent: v.ladeuil+lp at free.fr-20111221142526-pnwau0xnalimujts
fixes bug: https://launchpad.net/bugs/907279
committer: Vincent Ladeuil <v.ladeuil+lp at free.fr>
branch nick: 907279-override-from-env
timestamp: Thu 2011-12-22 09:53:35 +0100
message:
  Configuration option value can be overridden by os environ variables
-------------- next part --------------
=== modified file 'bzrlib/config.py'
--- a/bzrlib/config.py	2011-12-21 14:25:26 +0000
+++ b/bzrlib/config.py	2011-12-22 08:53:35 +0000
@@ -2343,12 +2343,16 @@
     encoutered, in which config files it can be stored.
     """
 
-    def __init__(self, name, default=None, default_from_env=None,
+    def __init__(self, name, override_from_env=None,
+                 default=None, default_from_env=None,
                  help=None, from_unicode=None, invalid=None):
         """Build an option definition.
 
         :param name: the name used to refer to the option.
 
+        :param override_from_env: A list of environment variables which can
+           provide override any configuration setting.
+
         :param default: the default value to use when none exist in the config
             stores. This is either a string that ``from_unicode`` will convert
             into the proper type, a callable returning a unicode string so that
@@ -2373,9 +2377,12 @@
             'warning' (emit a warning), 'error' (emit an error message and
             terminates).
         """
+        if override_from_env is None:
+            override_from_env = []
         if default_from_env is None:
             default_from_env = []
         self.name = name
+        self.override_from_env = override_from_env
         # Convert the default value to a unicode string so all values are
         # strings internally before conversion (via from_unicode) is attempted.
         if default is None:
@@ -2420,6 +2427,17 @@
                 raise errors.ConfigOptionValueError(self.name, unicode_value)
         return converted
 
+    def get_override(self):
+        value = None
+        for var in self.override_from_env:
+            try:
+                # If the env variable is defined, its value takes precedence
+                value = os.environ[var].decode(osutils.get_user_encoding())
+                break
+            except KeyError:
+                continue
+        return value
+
     def get_default(self):
         value = None
         for var in self.default_from_env:
@@ -3353,17 +3371,6 @@
         if expand is None:
             expand = _get_expand_default_value()
         value = None
-        # Ensuring lazy loading is achieved by delaying section matching (which
-        # implies querying the persistent storage) until it can't be avoided
-        # anymore by using callables to describe (possibly empty) section
-        # lists.
-        for sections in self.sections_def:
-            for store, section in sections():
-                value = section.get(name)
-                if value is not None:
-                    break
-            if value is not None:
-                break
         # If the option is registered, it may provide additional info about
         # value handling
         try:
@@ -3371,6 +3378,7 @@
         except KeyError:
             # Not registered
             opt = None
+
         def expand_and_convert(val):
             # This may need to be called twice if the value is None or ends up
             # being None during expansion or conversion.
@@ -3385,11 +3393,29 @@
                 if opt is not None:
                     val = opt.convert_from_unicode(val)
             return val
-        value = expand_and_convert(value)
-        if opt is not None and value is None:
-            # If the option is registered, it may provide a default value
-            value = opt.get_default()
-            value = expand_and_convert(value)
+
+        # First of all, check if the environment can override the configuration
+        # value
+        if opt is not None and opt.override_from_env:
+            value = opt.get_override()
+            value = expand_and_convert(value)
+        if value is None:
+            # Ensuring lazy loading is achieved by delaying section matching
+            # (which implies querying the persistent storage) until it can't be
+            # avoided anymore by using callables to describe (possibly empty)
+            # section lists.
+            for sections in self.sections_def:
+                for store, section in sections():
+                    value = section.get(name)
+                    if value is not None:
+                        break
+                if value is not None:
+                    break
+            value = expand_and_convert(value)
+            if opt is not None and value is None:
+                # If the option is registered, it may provide a default value
+                value = opt.get_default()
+                value = expand_and_convert(value)
         for hook in ConfigHooks['get']:
             hook(self, name, value)
         return value

=== modified file 'bzrlib/tests/test_config.py'
--- a/bzrlib/tests/test_config.py	2011-12-21 14:25:26 +0000
+++ b/bzrlib/tests/test_config.py	2011-12-22 08:53:35 +0000
@@ -3395,6 +3395,36 @@
         self.assertRaises(TypeError, conf_stack.get, 'foo')
 
 
+class TestStackWithSimpleStore(tests.TestCase):
+
+    def setUp(self):
+        super(TestStackWithSimpleStore, self).setUp()
+        self.overrideAttr(config, 'option_registry', config.OptionRegistry())
+        self.registry = config.option_registry
+
+    def get_conf(self, content=None):
+        return config.MemoryStack(content)
+
+
+    def test_override_value_from_env(self):
+        self.registry.register(
+            config.Option('foo', default='bar', override_from_env=['FOO']))
+        self.overrideEnv('FOO', 'quux')
+        # Env variable provides a default taking over the option one
+        conf = self.get_conf('foo=store')
+        self.assertEquals('quux', conf.get('foo'))
+
+    def test_first_override_value_from_env_wins(self):
+        self.registry.register(
+            config.Option('foo', default='bar',
+                          override_from_env=['NO_VALUE', 'FOO', 'BAZ']))
+        self.overrideEnv('FOO', 'foo')
+        self.overrideEnv('BAZ', 'baz')
+        # The first env var set wins
+        conf = self.get_conf('foo=store')
+        self.assertEquals('foo', conf.get('foo'))
+
+
 class TestMemoryStack(tests.TestCase):
 
     def test_get(self):



More information about the bazaar-commits mailing list