← Back to team overview

configglue team mailing list archive

[Merge] lp:~ricardokirkner/configglue/environ-vars into lp:configglue

 

Ricardo Kirkner has proposed merging lp:~ricardokirkner/configglue/environ-vars into lp:configglue with lp:~ricardokirkner/configglue/refactor-options as a prerequisite.

Requested reviews:
  Configglue developers (configglue)

For more details, see:
https://code.launchpad.net/~ricardokirkner/configglue/environ-vars/+merge/62584

This branch adds support for environment variables.

Environment variables are supported as follows:

1. In the commandline glue

Variable interpolation is done according to the precedence:
a. commandline parameter (aka --foo-bar=4)
b. environment variable (aka CONFIGGLUE_FOO_BAR=4)
c. configuration files 
d. defaults

2. In the configuration files

Variable interpolation is supported for environment variables using the standard notation

$VAR
${VAR}

If the variable is undefined, it resolves to the default value.
-- 
https://code.launchpad.net/~ricardokirkner/configglue/environ-vars/+merge/62584
Your team Configglue developers is requested to review the proposed merge of lp:~ricardokirkner/configglue/environ-vars into lp:configglue.
=== modified file 'configglue/pyschema/glue.py'
--- configglue/pyschema/glue.py	2011-03-26 22:24:51 +0000
+++ configglue/pyschema/glue.py	2011-05-27 00:04:32 +0000
@@ -15,6 +15,7 @@
 # 
 ###############################################################################
 
+import os
 import sys
 from optparse import OptionParser
 from collections import namedtuple
@@ -67,16 +68,29 @@
             og.add_option('--' + long_name(option), **kwargs)
     options, args = op.parse_args(argv)
 
+    def set_value(section, option, value):
+        # the value has been overridden by an argument;
+        # update it, but make sure it's a string, as
+        # SafeConfigParser will complain otherwise.
+        if not isinstance(value, basestring):
+            value = repr(value)
+        parser.set(section.name, option.name, value)
+
     for section in schema.sections():
         for option in section.options():
-            value = getattr(options, opt_name(option))
-            if parser.get(section.name, option.name) != value:
-                # the value has been overridden by an argument;
-                # update it, but make sure it's a string, as
-                # SafeConfigParser will complain otherwise.
-                if not isinstance(value, basestring):
-                    value = repr(value)
-                parser.set(section.name, option.name, value)
+            # 1. op value != parser value
+            # 2. op value == parser value != env value
+            # 3. op value == parser value == env value or not env value
+
+            op_value = getattr(options, opt_name(option))
+            parser_value = parser.get(section.name, option.name)
+            env_value = os.environ.get("CONFIGGLUE_{0}".format(
+                long_name(option).upper()))
+
+            if op_value != parser_value:
+                set_value(section, option, op_value)
+            elif env_value is not None and env_value != parser_value:
+                set_value(section, option, env_value)
 
     return op, options, args
 

=== modified file 'configglue/pyschema/parser.py'
--- configglue/pyschema/parser.py	2011-05-27 00:04:32 +0000
+++ configglue/pyschema/parser.py	2011-05-27 00:04:32 +0000
@@ -19,6 +19,7 @@
 import collections
 import copy
 import os
+import re
 import string
 
 from ConfigParser import (
@@ -435,6 +436,14 @@
         assert isinstance(result, basestring)
         return result
 
+    def interpolate_environment(self, rawval, raw=False):
+        if not raw:
+            # interpolate environment variables
+            rawval = re.sub(r'\${([A-Z_]+)}', r'%(\1)s', rawval)
+            rawval = re.sub(r'\$([A-Z_]+)', r'%(\1)s', rawval)
+            rawval = rawval % os.environ
+        return rawval
+
     def _get_default(self, section, option):
         # mark the value as not initialized to be able to have a None default
         marker = object()
@@ -511,6 +520,13 @@
                 self._sections[section] = {}
             self.set(section, option, value)
 
+        # interpolate environment variables
+        try:
+            value = self.interpolate_environment(value, raw=raw)
+        except KeyError:
+            # interpolation failed, fallback to default value
+            value = self._get_default(section, option)
+
         if parse:
             value = self.parse(section, option, value)
         return value

=== modified file 'configglue/tests/pyschema/test_parser.py'
--- configglue/tests/pyschema/test_parser.py	2011-05-27 00:04:32 +0000
+++ configglue/tests/pyschema/test_parser.py	2011-05-27 00:04:32 +0000
@@ -31,6 +31,7 @@
 
 from mock import (
     Mock,
+    patch,
     patch_object,
 )
 
@@ -251,6 +252,53 @@
         self.assertRaises(InterpolationMissingOptionError, parser.get,
                           'foo', 'bar')
 
+    @patch('configglue.pyschema.parser.os')
+    def test_interpolate_environment_basic_syntax(self, mock_os):
+        mock_os.environ = {'PATH': 'foo'}
+        parser = SchemaConfigParser(Schema())
+        result = parser.interpolate_environment("$PATH")
+        self.assertEqual(result, 'foo')
+
+    @patch('configglue.pyschema.parser.os')
+    def test_interpolate_environment_extended_syntax(self, mock_os):
+        mock_os.environ = {'PATH': 'foo'}
+        parser = SchemaConfigParser(Schema())
+        result = parser.interpolate_environment("${PATH}")
+        self.assertEqual(result, 'foo')
+
+    @patch('configglue.pyschema.parser.os')
+    def test_interpolate_environment_in_config(self, mock_os):
+        mock_os.environ = {'PYTHONPATH': 'foo', 'PATH': 'bar'}
+        class MySchema(Schema):
+            pythonpath = StringOption()
+            path = StringOption()
+
+        config = StringIO("[__main__]\npythonpath=${PYTHONPATH}\npath=$PATH")
+        parser = SchemaConfigParser(MySchema())
+        parser.readfp(config)
+        self.assertEqual(parser.values('__main__'),
+            {'pythonpath': 'foo', 'path': 'bar'})
+
+    @patch('configglue.pyschema.parser.os')
+    def test_get_with_environment_var(self, mock_os):
+        mock_os.environ = {'FOO': '42'}
+        class MySchema(Schema):
+            foo = IntOption()
+
+        config = StringIO("[__main__]\nfoo=$FOO")
+        parser = SchemaConfigParser(MySchema())
+        parser.readfp(config)
+        self.assertEqual(parser.get('__main__', 'foo'), 42)
+
+    def test_get_without_environment_var(self):
+        class MySchema(Schema):
+            foo = IntOption()
+
+        config = StringIO("[__main__]\nfoo=$FOO")
+        parser = SchemaConfigParser(MySchema())
+        parser.readfp(config)
+        self.assertEqual(parser.get('__main__', 'foo'), 0)
+
     def test_get_interpolation_keys_string(self):
         class MySchema(Schema):
             foo = StringOption()
@@ -366,7 +414,6 @@
             value = parser._interpolate_value('__main__', 'foo')
             self.assertEqual(value, None)
 
-
     def test_get_with_raw_value(self):
         class MySchema(Schema):
             foo = StringOption(raw=True)

=== modified file 'configglue/tests/pyschema/test_schemaconfig.py'
--- configglue/tests/pyschema/test_schemaconfig.py	2011-05-27 00:04:32 +0000
+++ configglue/tests/pyschema/test_schemaconfig.py	2011-05-27 00:04:32 +0000
@@ -17,10 +17,15 @@
 ###############################################################################
 
 import unittest
+import os
 import sys
 from StringIO import StringIO
 
-from mock import patch, Mock
+from mock import (
+    Mock,
+    patch,
+    patch_object,
+)
 
 from configglue.pyschema.glue import (
     configglue,
@@ -131,14 +136,13 @@
         self.assertEqual(self.parser.values(),
             {'foo': {'bar': 0}, '__main__': {'baz': 1}})
 
-        _argv = sys.argv
-        sys.argv = []
-
-        op, options, args = schemaconfigglue(self.parser)
-        self.assertEqual(self.parser.values(),
-            {'foo': {'bar': 0}, '__main__': {'baz': 1}})
-
-        sys.argv = _argv
+        _argv, sys.argv = sys.argv, []
+        try:
+            op, options, args = schemaconfigglue(self.parser)
+            self.assertEqual(self.parser.values(),
+                {'foo': {'bar': 0}, '__main__': {'baz': 1}})
+        finally:
+            sys.argv = _argv
 
     def test_glue_section_option(self):
         config = StringIO("[foo]\nbar=1")
@@ -151,6 +155,35 @@
         self.assertEqual(self.parser.values(),
                          {'foo': {'bar': 2}, '__main__': {'baz': 0}})
 
+    @patch('configglue.pyschema.glue.os')
+    def test_glue_environ(self, mock_os):
+        mock_os.environ = {'CONFIGGLUE_FOO_BAR': '42', 'CONFIGGLUE_BAZ': 3}
+        config = StringIO("[foo]\nbar=1")
+        self.parser.readfp(config)
+
+        _argv, sys.argv = sys.argv, ['prognam']
+        try:
+            op, options, args = schemaconfigglue(self.parser)
+            self.assertEqual(self.parser.values(),
+                {'foo': {'bar': 42}, '__main__': {'baz': 3}})
+        finally:
+            sys.argv = _argv
+
+
+    def test_glue_environ_precedence(self):
+        with patch_object(os, 'environ',
+            {'CONFIGGLUE_FOO_BAR': '42', 'BAR': '1'}):
+
+            config = StringIO("[foo]\nbar=$BAR")
+            self.parser.readfp(config)
+
+            _argv, sys.argv = sys.argv, ['prognam']
+            try:
+                op, options, args = schemaconfigglue(self.parser)
+                self.assertEqual(self.parser.get('foo', 'bar'), 42)
+            finally:
+                sys.argv = _argv
+
     def test_ambiguous_option(self):
         class MySchema(Schema):
             class foo(Section):