[Zope-Coders] Environment variables
Tres Seaver
tseaver@zope.com
29 Apr 2002 11:44:23 -0400
--=-Kl94KR0j8bN0qEF4CB8t
Content-Type: text/plain
Content-Transfer-Encoding: 7bit
On Mon, 2002-04-29 at 08:56, Matt Behrens wrote:
> Next brief topic up for discussion: instance.conf is going to at
> least support environment variable setting, a la
>
> [Environment]
> STUPID_LOG_FILE=/foo/bar/baz
> STUPID_LOG_SEVERITY=-200
>
> However, I'd rather go down the path of eliminating environment
> variables for specifying future behavior entirely.
>
> Specifically, I'd like to be able to take an instance of my
> runtime-overridable ConfigParser that is almost done being written
> in and make it available globally somehow, so we could at least
> eliminate future os.environ use. Since this would (I assume) involve
> putting this into the globally available namespace, and probably
> has lots of ramifications I don't understand, I'd like an experienced
> commentary on how best to do this.
I think the Right Thing (TM) is to add a new module (perhaps inside
the 'App' package) called 'Options' or 'Configuration'. It would be the
central repository for knowledge of configuration values, whether set
from the 'instance.conf' file or from command-line or environment
variables. I am attaching a file which illustrates this strategy.
--
===============================================================
Tres Seaver tseaver@zope.com
Zope Corporation "Zope Dealers" http://www.zope.com
--=-Kl94KR0j8bN0qEF4CB8t
Content-Disposition: attachment; filename=ZopeConfiguration.py
Content-Transfer-Encoding: quoted-printable
Content-Type: text/x-python; name=ZopeConfiguration.py; charset=ISO-8859-1
"""
Manage Zope configuration options, which may be set from=20
Functions:
- getConfigurationValue
- getConfigurationSource
- setConfigurationValue
"""
BASE_DIR =3D '/home/zope'
CONFIG_FILE_NAME =3D 'etc/zope.conf'
#
# Configuration sources
#
DEFAULT =3D 0 # Defined in software.
CONFIG_FILE =3D 1 # Defined in configuration file.
ENVIRONMENT =3D 2 # Defined in environment variable
COMMAND_LINE =3D 3 # Defined by command-line option.
#
# Registry for options
#
_all_options =3D {}
_environment_index =3D {}
_long_options_index =3D {}
_short_options_index =3D {}
def _clear():
global _all_options
global _environment_index
global _long_options_index
global _short_options_index
_all_options =3D {}
_environment_index =3D {}
_long_options_index =3D {}
_short_options_index =3D {}
def registerConfigurationOption( config_file_key
, default_value=3DNone
, is_switch=3D0
, description=3D''
, environment_key=3DNone
, short_option=3DNone
, long_option=3DNone,=20
):
"""
Register a possible configuration option, including its
default value and the optional channels for setting it.
"""
global _all_options
global _environment_index
global _long_options_index
global _short_options_index
option =3D ConfigurationOption( config_file_key
, default_value
, is_switch
, description
, environment_key
, long_option
, short_option
)
_all_options[ config_file_key ] =3D option
if environment_key:
_environment_index[ environment_key ] =3D option
if long_option:
_long_options_index[ long_option ] =3D option
if short_option:
_short_options_index[ short_option ] =3D option
#
# Configuration option specification
#
class ConfigurationOption:
def __init__( self
, config_file_key
, default_value
, is_switch=3D0
, description=3DNone
, environment_key=3DNone
, long_option=3DNone=20
, short_option=3DNone
):
""
self._source =3D DEFAULT
self._config_file_key =3D config_file_key
self._value =3D default_value
self._is_switch =3D is_switch
self._description =3D description
self._environment_key =3D environment_key
self._short_option =3D short_option
self._long_option =3D long_option
def getConfigFileKey( self ):
"""
Return the key used to define this value in a configuration
file.
"""
return self._config_file_key
def isSwitch( self ):
"""
Is this option a switch?
"""
return self._is_switch
def getEnvironmentKey( self ):
"""
Return the key used to define this value as an environment
variable (defaults to None, meaning the value cannot be
set via the environment).
"""
return self._environment_key
def getShortOption( self ):
"""
Return the key used to define this value as a "short"
command-line option (defaults to None, meaning the value
cannot be set via the command-line).
"""
return self._short_option
def getLongOption( self ):
"""
Return the key used to define this value as a "long"
command-line option (defaults to None, meaning the value
cannot be set via the command-line).
"""
return self._long_option
def getDescription( self ):
"""
Return the description of this option.
"""
return self._description
def getValue( self ):
"""
Return the value of this option.
"""
return self._value
def getSource( self ):
"""
Return the source for the value assigned to this option.
"""
return self._source
def setValue( self, value, source ):
"""
Update the value assigned to this option, and its source.
'source' should be one of the constants declared above.
"""
self._value =3D value
self._source =3D source
#
# Configuration API
#
def getConfigurationInfo( key ):
"""
Return the configuration option corresponding to 'key', or
None if no option has been registered for that key.
TODO: Search other indexes, too.
"""
indexes =3D ( _all_options
, _environment_index
, _long_options_index
, _short_options_index
)
for index in indexes:
info =3D index.get( key )
if info is not None:
return info
return None
def getConfigurationValue( key, default=3DNone ):
"""
Return the configuration value corresponding to 'key', or
'default' if no value exists.
"""
option =3D getConfigurationInfo( key )
if option:
return option.getValue()
return default
def getConfigurationSource( key ):
"""
Return the source of the configuration value corresponding to
'key'. Source should be one of the
known constants defined above, or None if no value exists=20
"""
option =3D getConfigurationInfo( key )
if option:
return option.getSource()
return None
def parseConfigFile( fully_qualified_path=3DNone ):
"""
Parse the config file (default is '/home/zope/etc/zope.conf').
"""
if fully_qualified_path is None:
fully_qualified_path =3D '%s/%s' % ( BASE_DIR, CONFIG_FILE_NAME )
g =3D { '__builtins__' : {}
, 'None' : None
}
l =3D {}
execfile( fully_qualified_path, g, l )
for k, v in l.items():
option =3D getConfigurationInfo( k )
if option:
option.setValue( v, CONFIG_FILE )
else:
pass # TODO: log warning
def parseEnvironment( env ):
"""
Extract relevant environment variables.
"""
for key, option in _environment_index.items():
value =3D env.get( key )
if value is None:
continue
if option.isSwitch():
value =3D 1
option.setValue( value, ENVIRONMENT )
def parseCommandLine( argv ):
"""
Extract relevant command-line options, returning remaining
arguments.
Raise getopt.GetoptError if something doesn't match.
"""
import getopt
shorts =3D []
for short, option in _short_options_index.items():
if not option.isSwitch():
short =3D short + ':'
shorts.append( short )
longs =3D []
for long, option in _long_options_index.items():
if not option.isSwitch():
long =3D long + '=3D'
longs.append( long )
options, arguments =3D getopt.getopt( argv, ''.join( shorts ), longs )
for k, v in options:
if k.startswith( '--' ):
option =3D _long_options_index[ k[2:] ]
else:
option =3D _short_options_index[ k[1:] ]
if option.isSwitch():
option.setValue( 1, COMMAND_LINE )
else:
option.setValue( v, COMMAND_LINE )
return arguments
def parseOptions():
"""
Do the default setup
"""
import sys, os
parseConfigFile()
parseEnvironment( os.environ )
parseCommandLine( sys.argv )
if __name__ =3D=3D '__main__':
import unittest
class Tests( unittest.TestCase ):
def setUp( self ):
self._old_BASE_DIR =3D BASE_DIR
self._old_CONFIG_FILE_NAME =3D CONFIG_FILE_NAME
registerConfigurationOption( 'foo'
, 'Foo_default'
, 0
, 'Foo description.'
, 'FOO_ENV'
, 'f'
, 'foo'
)
registerConfigurationOption( 'bar'
, 'Bar_default'
, 0
, 'Bar description.'
, 'BAR_ENV'
, 'b'
, 'bar'
)
registerConfigurationOption( 'baz'
, 0
, 1
, 'Baz description.'
, 'BAZ_ENV'
, 'B'
, 'baz'
)
def tearDown( self ):
_clear()
global BASE_DIR, CONFIG_FILE_NAME
BASE_DIR =3D self._old_BASE_DIR
CONFIG_FILE_NAME =3D self._old_CONFIG_FILE_NAME
def test_Info( self ):
info =3D getConfigurationInfo( 'foo' )
self.assertEqual( info.getConfigFileKey(), 'foo' )
self.assertEqual( info.isSwitch(), 0 )
self.assertEqual( info.getEnvironmentKey(), 'FOO_ENV' )
self.assertEqual( info.getShortOption(), 'f' )
self.assertEqual( info.getLongOption(), 'foo' )
self.assertEqual( info.getDescription(), 'Foo description.' )
self.assertEqual( info.getValue(), 'Foo_default' )
self.assertEqual( info.getSource(), DEFAULT )
def test_Info_env( self ):
info =3D getConfigurationInfo( 'FOO_ENV' )
self.assertEqual( info.getConfigFileKey(), 'foo' )
self.assertEqual( info.isSwitch(), 0 )
self.assertEqual( info.getEnvironmentKey(), 'FOO_ENV' )
self.assertEqual( info.getShortOption(), 'f' )
self.assertEqual( info.getLongOption(), 'foo' )
self.assertEqual( info.getDescription(), 'Foo description.' )
self.assertEqual( info.getValue(), 'Foo_default' )
self.assertEqual( info.getSource(), DEFAULT )
def test_ConfigFile( self ):
import tempfile
import os
fn =3D tempfile.mktemp()
f =3D open( fn, 'w+b' )
f.write( 'foo =3D "Foo_nondefault"\n' )
f.write( 'baz =3D 1\n' )
f.close()
parseConfigFile( fn )
os.unlink( fn )
self.assertEqual( getConfigurationValue( 'foo' ), 'Foo_nondefau=
lt' )
self.assertEqual( getConfigurationSource( 'foo' ), CONFIG_FILE =
)
self.assertEqual( getConfigurationValue( 'bar' ), 'Bar_default'=
)
self.assertEqual( getConfigurationSource( 'bar' ), DEFAULT )
self.assertEqual( getConfigurationValue( 'baz' ), 1 )
self.assertEqual( getConfigurationSource( 'baz' ), CONFIG_FILE =
)
def test_Environment( self ):
env =3D { 'FOO_ENV' : 'Foo_nondefault'
, 'BAZ_ENV' : 1
}
parseEnvironment( env )
self.assertEqual( getConfigurationValue( 'foo' ), 'Foo_nondefau=
lt' )
self.assertEqual( getConfigurationSource( 'foo' ), ENVIRONMENT =
)
self.assertEqual( getConfigurationValue( 'bar' ), 'Bar_default'=
)
self.assertEqual( getConfigurationSource( 'bar' ), DEFAULT )
self.assertEqual( getConfigurationValue( 'baz' ), 1 )
self.assertEqual( getConfigurationSource( 'baz' ), ENVIRONMENT =
)
def test_CommandLine( self ):
argv =3D ( '-f', 'Foo_nondefault', '--baz' )
parseCommandLine( argv )
self.assertEqual( getConfigurationValue( 'foo' ), 'Foo_nondefau=
lt' )
self.assertEqual( getConfigurationSource( 'foo' ), COMMAND_LINE=
)
self.assertEqual( getConfigurationValue( 'bar' ), 'Bar_default'=
)
self.assertEqual( getConfigurationSource( 'bar' ), DEFAULT )
self.assertEqual( getConfigurationValue( 'baz' ), 1 )
self.assertEqual( getConfigurationSource( 'baz' ), COMMAND_LINE=
)
def test_Layered( self ):
import os
import sys
import tempfile
sys.argv =3D ( '--baz', )
os.environ =3D { 'FOO_ENV' : 'Foo_nondefault' }
fn =3D tempfile.mktemp()
f =3D open( fn, 'w+b' )
f.write( 'foo =3D "Foo_config_file"\n' )
f.close()
global BASE_DIR, CONFIG_FILE_NAME
BASE_DIR =3D ''
CONFIG_FILE_NAME =3D fn
parseOptions()
os.unlink( fn )
self.assertEqual( getConfigurationValue( 'foo' ), 'Foo_nondefau=
lt' )
self.assertEqual( getConfigurationSource( 'foo' ), ENVIRONMENT =
)
self.assertEqual( getConfigurationValue( 'bar' ), 'Bar_default'=
)
self.assertEqual( getConfigurationSource( 'bar' ), DEFAULT )
self.assertEqual( getConfigurationValue( 'baz' ), 1 )
self.assertEqual( getConfigurationSource( 'baz' ), COMMAND_LINE=
)
unittest.main()
--=-Kl94KR0j8bN0qEF4CB8t--