From afdc72103847fc27efd00f8cc97a7320909ff6a0 Mon Sep 17 00:00:00 2001 From: Martin Nagy Date: Mon, 29 Sep 2008 17:41:30 +0200 Subject: Add support for environment variables, change tests accordingly --- ipalib/config.py | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 ipalib/config.py (limited to 'ipalib/config.py') diff --git a/ipalib/config.py b/ipalib/config.py new file mode 100644 index 00000000..bb345661 --- /dev/null +++ b/ipalib/config.py @@ -0,0 +1,84 @@ +# Authors: +# Martin Nagy +# +# Copyright (C) 2008 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; version 2 only +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +def default_environment(): + default = dict( + conf = '/etc/ipa/ipa.conf', + server_context = True, + query_dns = True, + verbose = False, + servers = LazyIter(myservers), + realm = LazyProp(myrealm), + domain = LazyProp(mydomain), + ) + return default + + +class LazyProp(object): + def __init__(self, func, value=None): + self._func = func + self._value = value + + def set_value(self, value): + self._value = value + + def get_value(self): + if self._value is None: + return self._func() + else: + return self._value + + +# FIXME: make sure to eliminate duplicates +class LazyIter(LazyProp): + def get_value(self): + if self._value is not None: + if type(self._value) is tuple: + for item in self._value: + yield item + else: + yield self._value + for item in self._func(): + yield item + + +def read_config(file): + assert isinstance(file, basestring) + # open the file and read configuration, return a dict + # for now, these are here just for testing purposes + return dict(servers="server.ipatest.com", realm="IPATEST.COM") + + +# these functions are here just to "emulate" dns resolving for now +def mydomain(): + return "ipatest.com" + + +def myrealm(): + return "IPATEST.COM" + + +def myservers(): + # print is here to demonstrate that the querying will occur only when it is + # really needed + print "Querying DNS" + yield "server.ipatest.com" + yield "backup.ipatest.com" + yield "fake.ipatest.com" -- cgit From 149429f3057e3ae934e660e3276c9e8d3c935d17 Mon Sep 17 00:00:00 2001 From: Martin Nagy Date: Thu, 2 Oct 2008 20:24:05 +0200 Subject: Environment is now subclassed from object, rather then dict. Added tests for Environment and config.py --- ipalib/config.py | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) (limited to 'ipalib/config.py') diff --git a/ipalib/config.py b/ipalib/config.py index bb345661..73d23c8e 100644 --- a/ipalib/config.py +++ b/ipalib/config.py @@ -17,22 +17,31 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +import types -def default_environment(): +DEFAULT_CONF='/etc/ipa/ipa.conf' + +def generate_env(d={}): default = dict( - conf = '/etc/ipa/ipa.conf', server_context = True, query_dns = True, verbose = False, - servers = LazyIter(myservers), - realm = LazyProp(myrealm), - domain = LazyProp(mydomain), + servers = LazyIter(get_servers), + realm = LazyProp(get_realm), + domain = LazyProp(get_domain), ) + for key, value in d.iteritems(): + if key in default and type(default[key]) in (LazyIter, LazyProp): + default[key].set_value(value) + else: + default[key] = value + return default class LazyProp(object): def __init__(self, func, value=None): + assert isinstance(func, types.FunctionType) self._func = func self._value = value @@ -40,26 +49,26 @@ class LazyProp(object): self._value = value def get_value(self): - if self._value is None: + if self._value == None: return self._func() else: return self._value -# FIXME: make sure to eliminate duplicates class LazyIter(LazyProp): def get_value(self): - if self._value is not None: - if type(self._value) is tuple: + if self._value != None: + if type(self._value) == tuple: for item in self._value: yield item else: yield self._value for item in self._func(): - yield item + if not self._value or item not in self._value: + yield item -def read_config(file): +def read_config(file=DEFAULT_CONF): assert isinstance(file, basestring) # open the file and read configuration, return a dict # for now, these are here just for testing purposes @@ -67,18 +76,15 @@ def read_config(file): # these functions are here just to "emulate" dns resolving for now -def mydomain(): +def get_domain(): return "ipatest.com" -def myrealm(): +def get_realm(): return "IPATEST.COM" -def myservers(): - # print is here to demonstrate that the querying will occur only when it is - # really needed - print "Querying DNS" +def get_servers(): yield "server.ipatest.com" yield "backup.ipatest.com" yield "fake.ipatest.com" -- cgit From 3ffbaac64cc3a9ab704c707112f59e041986576c Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Thu, 2 Oct 2008 19:42:06 -0600 Subject: Backend.xmlrpc and simple-server.py now use the xmlrpc_marshal() and xmlrpc_unmarshal() functions respectively --- ipalib/config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'ipalib/config.py') diff --git a/ipalib/config.py b/ipalib/config.py index 73d23c8e..f327cab7 100644 --- a/ipalib/config.py +++ b/ipalib/config.py @@ -23,7 +23,7 @@ DEFAULT_CONF='/etc/ipa/ipa.conf' def generate_env(d={}): default = dict( - server_context = True, + server_context = False, query_dns = True, verbose = False, servers = LazyIter(get_servers), @@ -35,7 +35,7 @@ def generate_env(d={}): default[key].set_value(value) else: default[key] = value - + return default -- cgit From 4a68c719f03c176bc63a96007c089d0ac7ae5fc1 Mon Sep 17 00:00:00 2001 From: Martin Nagy Date: Fri, 3 Oct 2008 17:08:37 +0200 Subject: Implement config file reading --- ipalib/config.py | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) (limited to 'ipalib/config.py') diff --git a/ipalib/config.py b/ipalib/config.py index f327cab7..16bc1371 100644 --- a/ipalib/config.py +++ b/ipalib/config.py @@ -17,7 +17,9 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +from ConfigParser import SafeConfigParser, ParsingError import types +import os DEFAULT_CONF='/etc/ipa/ipa.conf' @@ -26,7 +28,7 @@ def generate_env(d={}): server_context = False, query_dns = True, verbose = False, - servers = LazyIter(get_servers), + server = LazyIter(get_servers), realm = LazyProp(get_realm), domain = LazyProp(get_domain), ) @@ -68,11 +70,30 @@ class LazyIter(LazyProp): yield item -def read_config(file=DEFAULT_CONF): - assert isinstance(file, basestring) - # open the file and read configuration, return a dict - # for now, these are here just for testing purposes - return dict(servers="server.ipatest.com", realm="IPATEST.COM") +def read_config(config_file=DEFAULT_CONF): + assert isinstance(config_file, (basestring, file)) + + parser = SafeConfigParser() + files = [config_file, os.path.expanduser('~/.ipa.conf')] + + for f in files: + try: + if isinstance(f, file): + parser.readfp(f) + else: + parser.read(f) + except ParsingError: + print "Can't read %s" % f + + ret = {} + if parser.has_section('defaults'): + for name, value in parser.items('defaults'): + value = tuple(elem.strip() for elem in value.split(',')) + if len(value) == 1: + value = value[0] + ret[name] = value + + return ret # these functions are here just to "emulate" dns resolving for now -- cgit From 4a1c4a3fe3a568c98b6bab1456993c4163721c5d Mon Sep 17 00:00:00 2001 From: Martin Nagy Date: Fri, 3 Oct 2008 22:13:50 +0200 Subject: Implement argument parsing for the CLI --- ipalib/config.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) (limited to 'ipalib/config.py') diff --git a/ipalib/config.py b/ipalib/config.py index 16bc1371..a0a33b40 100644 --- a/ipalib/config.py +++ b/ipalib/config.py @@ -28,6 +28,7 @@ def generate_env(d={}): server_context = False, query_dns = True, verbose = False, + interactive = True, server = LazyIter(get_servers), realm = LazyProp(get_realm), domain = LazyProp(get_domain), @@ -70,11 +71,14 @@ class LazyIter(LazyProp): yield item -def read_config(config_file=DEFAULT_CONF): - assert isinstance(config_file, (basestring, file)) +def read_config(config_file=None): + assert config_file == None or isinstance(config_file, (basestring, file)) parser = SafeConfigParser() - files = [config_file, os.path.expanduser('~/.ipa.conf')] + if config_file == None: + files = [DEFAULT_CONF, os.path.expanduser('~/.ipa.conf')] + else: + files = [config_file] for f in files: try: -- cgit From 149912d0e7bb75459c52c9404a1af537023729dc Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Tue, 14 Oct 2008 00:38:17 -0600 Subject: Added ldap.get_user_dn() method --- ipalib/config.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'ipalib/config.py') diff --git a/ipalib/config.py b/ipalib/config.py index a0a33b40..c55c47c8 100644 --- a/ipalib/config.py +++ b/ipalib/config.py @@ -25,13 +25,15 @@ DEFAULT_CONF='/etc/ipa/ipa.conf' def generate_env(d={}): default = dict( - server_context = False, + server_context = True, query_dns = True, verbose = False, interactive = True, server = LazyIter(get_servers), realm = LazyProp(get_realm), domain = LazyProp(get_domain), + container_user='cn=users,cn=accounts', + basedn='dc=example,dc=com', ) for key, value in d.iteritems(): if key in default and type(default[key]) in (LazyIter, LazyProp): -- cgit From ff88652a405c7fd9236a9b1d80dd8955a9ca056d Mon Sep 17 00:00:00 2001 From: Martin Nagy Date: Tue, 14 Oct 2008 21:22:44 +0200 Subject: Convert string values to boolean when generating environment --- ipalib/config.py | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) (limited to 'ipalib/config.py') diff --git a/ipalib/config.py b/ipalib/config.py index c55c47c8..a606a40b 100644 --- a/ipalib/config.py +++ b/ipalib/config.py @@ -25,25 +25,41 @@ DEFAULT_CONF='/etc/ipa/ipa.conf' def generate_env(d={}): default = dict( - server_context = True, - query_dns = True, - verbose = False, + basedn = 'dc=example,dc=com', + container_user = 'cn=users,cn=accounts', + domain = LazyProp(get_domain), interactive = True, - server = LazyIter(get_servers), + query_dns = True, realm = LazyProp(get_realm), - domain = LazyProp(get_domain), - container_user='cn=users,cn=accounts', - basedn='dc=example,dc=com', + server_context = True, + server = LazyIter(get_servers), + verbose = False, ) for key, value in d.iteritems(): - if key in default and type(default[key]) in (LazyIter, LazyProp): - default[key].set_value(value) + if key in default: + if isinstance(default[key], (LazyIter, LazyProp)): + default[key].set_value(value) + else: + default[key] = convert_val(type(default[key]), value) else: - default[key] = value + default[key] = value return default +# TODO: Add a validation function +def convert_val(target_type, value): + bool_true = ('true', 'yes', 'on') + bool_false = ('false', 'no', 'off') + + if target_type == bool and isinstance(value, basestring): + if value.lower() in bool_true: + return True + elif value.lower() in bool_false: + return False + return target_type(value) + + class LazyProp(object): def __init__(self, func, value=None): assert isinstance(func, types.FunctionType) @@ -73,6 +89,8 @@ class LazyIter(LazyProp): yield item +# TODO: Make it possible to use var = 'foo, bar' without +# turning it into ("'foo", "bar'") def read_config(config_file=None): assert config_file == None or isinstance(config_file, (basestring, file)) -- cgit From cfc8450efd92dc0fb6648e97b27416c67625adfb Mon Sep 17 00:00:00 2001 From: Rob Crittenden Date: Tue, 14 Oct 2008 22:22:01 -0400 Subject: Port user-show to new CrudBackend framework --- ipalib/config.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'ipalib/config.py') diff --git a/ipalib/config.py b/ipalib/config.py index a606a40b..42bf7787 100644 --- a/ipalib/config.py +++ b/ipalib/config.py @@ -25,8 +25,11 @@ DEFAULT_CONF='/etc/ipa/ipa.conf' def generate_env(d={}): default = dict( + container_accounts = 'cn=accounts', basedn = 'dc=example,dc=com', container_user = 'cn=users,cn=accounts', + container_group = 'cn=groups,cn=accounts', + container_service = 'cn=services,cn=accounts', domain = LazyProp(get_domain), interactive = True, query_dns = True, -- cgit From 3a80297b04d6fbfd2367ec76c5651d20293adccc Mon Sep 17 00:00:00 2001 From: Martin Nagy Date: Fri, 17 Oct 2008 22:55:03 +0200 Subject: Reworking Environment, moved it to config.py --- ipalib/config.py | 211 +++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 165 insertions(+), 46 deletions(-) (limited to 'ipalib/config.py') diff --git a/ipalib/config.py b/ipalib/config.py index 42bf7787..7899d077 100644 --- a/ipalib/config.py +++ b/ipalib/config.py @@ -21,74 +21,193 @@ from ConfigParser import SafeConfigParser, ParsingError import types import os +from errors import check_isinstance, raise_TypeError + DEFAULT_CONF='/etc/ipa/ipa.conf' -def generate_env(d={}): - default = dict( - container_accounts = 'cn=accounts', - basedn = 'dc=example,dc=com', - container_user = 'cn=users,cn=accounts', - container_group = 'cn=groups,cn=accounts', - container_service = 'cn=services,cn=accounts', - domain = LazyProp(get_domain), - interactive = True, - query_dns = True, - realm = LazyProp(get_realm), - server_context = True, - server = LazyIter(get_servers), - verbose = False, - ) - for key, value in d.iteritems(): - if key in default: - if isinstance(default[key], (LazyIter, LazyProp)): - default[key].set_value(value) + +class Environment(object): + """ + A mapping object used to store the environment variables. + """ + + def __init__(self): + object.__setattr__(self, '_Environment__map', {}) + + def __getattr__(self, name): + """ + Return the attribute named ``name``. + """ + return self[name] + + def __setattr__(self, name, value): + """ + Set the attribute named ``name`` to ``value``. + """ + self[name] = value + + def __delattr__(self, name): + """ + Raise AttributeError (deletion is not allowed). + """ + raise AttributeError('cannot del %s.%s' % + (self.__class__.__name__, name) + ) + + def __getitem__(self, key): + """ + Return the value corresponding to ``key``. + """ + val = self.__map[key] + if hasattr(val, 'get_value'): + return val.get_value() + else: + return val + + def __setitem__(self, key, value): + """ + Set the item at ``key`` to ``value``. + """ + if key in self or hasattr(self, key): + if hasattr(self.__map[key], 'set_value'): + self.__map[key].set_value(value) else: - default[key] = convert_val(type(default[key]), value) + raise AttributeError('cannot overwrite %s.%s' % + (self.__class__.__name__, key) + ) else: - default[key] = value + self.__map[key] = value - return default + def __contains__(self, key): + """ + Return True if instance contains ``key``; otherwise return False. + """ + return key in self.__map + def __iter__(self): + """ + Iterate through keys in ascending order. + """ + for key in sorted(self.__map): + yield key -# TODO: Add a validation function -def convert_val(target_type, value): - bool_true = ('true', 'yes', 'on') - bool_false = ('false', 'no', 'off') + def update(self, new_vals, ignore_errors = False): + assert type(new_vals) == dict + for key, value in new_vals.iteritems(): + if ignore_errors: + try: + self[key] = value + except (AttributeError, KeyError): + pass + else: + self[key] = value - if target_type == bool and isinstance(value, basestring): - if value.lower() in bool_true: - return True - elif value.lower() in bool_false: - return False - return target_type(value) + def get(self, name, default=None): + return self.__map.get(name, default) -class LazyProp(object): - def __init__(self, func, value=None): - assert isinstance(func, types.FunctionType) - self._func = func - self._value = value - def set_value(self, value): - self._value = value +def set_default_env(env): + assert isinstance(env, Environment) + + default = dict( + basedn = EnvProp(basestring, 'dc=example,dc=com'), + container_accounts = EnvProp(basestring, 'cn=accounts'), + container_user = EnvProp(basestring, 'cn=users,cn=accounts'), + container_group = EnvProp(basestring, 'cn=groups,cn=accounts'), + container_service = EnvProp(basestring, 'cn=services,cn=accounts'), + domain = LazyProp(basestring, get_domain), + interactive = EnvProp(bool, True), + query_dns = EnvProp(bool, True), + realm = LazyProp(basestring, get_realm), + server_context = EnvProp(bool, True), + server = LazyIter(basestring, get_servers), + verbose = EnvProp(bool, False), + ) + + env.update(default) + + +class EnvProp(object): + def __init__(self, type_, default, multi_value=False): + if multi_value: + if isinstance(default, tuple) and len(default): + check_isinstance(default[0], type_, allow_none=True) + self._type = type_ + self._default = default + self._value = None + self._multi_value = multi_value def get_value(self): - if self._value == None: - return self._func() + if self._get() != None: + return self._get() else: + raise KeyError, 'Value not set' + + def set_value(self, value): + if self._value != None: + raise KeyError, 'Value already set' + self._value = self._validate(value) + + def _get(self): + if self._value != None: return self._value + elif self._default != None: + return self._default + else: + return None + + def _validate(self, value): + if self._multi_value and isinstance(value, tuple): + converted = [] + for val in value: + converted.append(self._validate_value(val)) + return tuple(converted) + else: + return self._validate_value(value) + + def _validate_value(self, value): + bool_true = ('true', 'yes', 'on') + bool_false = ('false', 'no', 'off') + + if self._type == bool and isinstance(value, basestring): + if value.lower() in bool_true: + return True + elif value.lower() in bool_false: + return False + else: + raise raise_TypeError(value, bool, 'value') + check_isinstance(value, self._type, 'value') + return value + + +class LazyProp(EnvProp): + def __init__(self, type_, func, default=None, multi_value=False): + check_isinstance(func, types.FunctionType, 'func') + self._func = func + EnvProp.__init__(self, type_, default, multi_value) + + def get_value(self): + if self._get() != None: + return self._get() + else: + return self._func() class LazyIter(LazyProp): + def __init__(self, type_, func, default=None): + LazyProp.__init__(self, type_, func, default, multi_value=True) + def get_value(self): - if self._value != None: - if type(self._value) == tuple: - for item in self._value: + val = self._get() + if val != None: + if type(val) == tuple: + for item in val: yield item else: - yield self._value + yield val for item in self._func(): - if not self._value or item not in self._value: + if not val or item not in val: yield item -- cgit From 18e74643a6979ca4e490d34975a38c83a1722417 Mon Sep 17 00:00:00 2001 From: Martin Nagy Date: Mon, 20 Oct 2008 19:53:07 +0200 Subject: Add comments in config.py and fix Environment.get() --- ipalib/config.py | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 2 deletions(-) (limited to 'ipalib/config.py') diff --git a/ipalib/config.py b/ipalib/config.py index 7899d077..e1b12f1e 100644 --- a/ipalib/config.py +++ b/ipalib/config.py @@ -17,6 +17,14 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +""" +Basic configuration management. + +This module handles the reading and representation of basic local settings. +It will also take care of settings that can be discovered by different +methods, such as DNS. +""" + from ConfigParser import SafeConfigParser, ParsingError import types import os @@ -92,6 +100,12 @@ class Environment(object): yield key def update(self, new_vals, ignore_errors = False): + """ + Update variables using keys and values from ``new_vals``. + + Error will occur if there is an attempt to override variable that was + already set, unless``ignore_errors`` is True. + """ assert type(new_vals) == dict for key, value in new_vals.iteritems(): if ignore_errors: @@ -103,11 +117,19 @@ class Environment(object): self[key] = value def get(self, name, default=None): - return self.__map.get(name, default) - + """ + Return the value corresponding to ``key``. Defaults to ``default``. + """ + if name in self: + return self[name] + else: + return default def set_default_env(env): + """ + Set default values for ``env``. + """ assert isinstance(env, Environment) default = dict( @@ -129,7 +151,15 @@ def set_default_env(env): class EnvProp(object): + """ + Environment set-once property with optional default value. + """ def __init__(self, type_, default, multi_value=False): + """ + :param type_: Type of the property. + :param default: Default value. + :param multi_value: Allow multiple values. + """ if multi_value: if isinstance(default, tuple) and len(default): check_isinstance(default[0], type_, allow_none=True) @@ -139,17 +169,29 @@ class EnvProp(object): self._multi_value = multi_value def get_value(self): + """ + Return the value if it was set. + + If the value is not set return the default. Otherwise raise an + exception. + """ if self._get() != None: return self._get() else: raise KeyError, 'Value not set' def set_value(self, value): + """ + Set the value. + """ if self._value != None: raise KeyError, 'Value already set' self._value = self._validate(value) def _get(self): + """ + Return value, default, or None. + """ if self._value != None: return self._value elif self._default != None: @@ -158,6 +200,11 @@ class EnvProp(object): return None def _validate(self, value): + """ + Make sure ``value`` is of the right type. Do conversions if necessary. + + This will also handle multi value. + """ if self._multi_value and isinstance(value, tuple): converted = [] for val in value: @@ -167,6 +214,9 @@ class EnvProp(object): return self._validate_value(value) def _validate_value(self, value): + """ + Validate and convert a single value. + """ bool_true = ('true', 'yes', 'on') bool_false = ('false', 'no', 'off') -- cgit From 1daf319a19f902d7c7bef37af065cac81be9189e Mon Sep 17 00:00:00 2001 From: Rob Crittenden Date: Wed, 22 Oct 2008 17:54:04 -0400 Subject: Implement the host commands In order for this to work against a v1 database the update host.update needs to be applied --- ipalib/config.py | 1 + 1 file changed, 1 insertion(+) (limited to 'ipalib/config.py') diff --git a/ipalib/config.py b/ipalib/config.py index e1b12f1e..ebd602b9 100644 --- a/ipalib/config.py +++ b/ipalib/config.py @@ -138,6 +138,7 @@ def set_default_env(env): container_user = EnvProp(basestring, 'cn=users,cn=accounts'), container_group = EnvProp(basestring, 'cn=groups,cn=accounts'), container_service = EnvProp(basestring, 'cn=services,cn=accounts'), + container_host = EnvProp(basestring, 'cn=computers,cn=accounts'), domain = LazyProp(basestring, get_domain), interactive = EnvProp(bool, True), query_dns = EnvProp(bool, True), -- cgit From 06a82bf4b646cd077a43841abb5670d9a495b24c Mon Sep 17 00:00:00 2001 From: Rob Crittenden Date: Thu, 23 Oct 2008 11:00:50 -0400 Subject: Fix ipa command running in server_context=True Make the LDAP host and port environment variables More changes so that commands have a shell return value lite-xmlrpc no longer hardcodes the kerberos credentials cache location --- ipalib/config.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'ipalib/config.py') diff --git a/ipalib/config.py b/ipalib/config.py index ebd602b9..b3155490 100644 --- a/ipalib/config.py +++ b/ipalib/config.py @@ -146,6 +146,8 @@ def set_default_env(env): server_context = EnvProp(bool, True), server = LazyIter(basestring, get_servers), verbose = EnvProp(bool, False), + ldaphost = EnvProp(basestring, 'localhost'), + ldapport = EnvProp(int, 389), ) env.update(default) -- cgit From 2ec0312eb6d4131fe22fab6f4409b71cac83f98f Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Fri, 24 Oct 2008 01:51:36 -0600 Subject: Finished doodle with stricter version of Environment --- ipalib/config.py | 87 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) (limited to 'ipalib/config.py') diff --git a/ipalib/config.py b/ipalib/config.py index b3155490..4f7a008d 100644 --- a/ipalib/config.py +++ b/ipalib/config.py @@ -28,6 +28,7 @@ methods, such as DNS. from ConfigParser import SafeConfigParser, ParsingError import types import os +import sys from errors import check_isinstance, raise_TypeError @@ -126,6 +127,92 @@ class Environment(object): return default +class Env(object): + """ + A mapping object used to store the environment variables. + """ + + __locked = False + + def __init__(self): + object.__setattr__(self, '_Env__d', {}) + + def __lock__(self): + """ + Prevent further changes to environment. + """ + if self.__locked is True: + raise StandardError( + '%s.__lock__() already called' % self.__class__.__name__ + ) + object.__setattr__(self, '_Env__locked', True) + + def __getattr__(self, name): + """ + Return the attribute named ``name``. + """ + if name in self.__d: + return self[name] + raise AttributeError('%s.%s' % + (self.__class__.__name__, name) + ) + + def __setattr__(self, name, value): + """ + Set the attribute named ``name`` to ``value``. + """ + self[name] = value + + def __delattr__(self, name): + """ + Raise AttributeError (deletion is not allowed). + """ + raise AttributeError('cannot del %s.%s' % + (self.__class__.__name__, name) + ) + + def __getitem__(self, key): + """ + Return the value corresponding to ``key``. + """ + if key not in self.__d: + raise KeyError(key) + value = self.__d[key] + if callable(value): + return value() + return value + + def __setitem__(self, key, value): + """ + Set ``key`` to ``value``. + """ + if self.__locked: + raise AttributeError('locked: cannot set %s.%s to %r' % + (self.__class__.__name__, key, value) + ) + if key in self.__d or hasattr(self, key): + raise AttributeError('cannot overwrite %s.%s with %r' % + (self.__class__.__name__, key, value) + ) + self.__d[key] = value + if not callable(value): + assert type(value) in (str, int, bool) + object.__setattr__(self, key, value) + + def __contains__(self, key): + """ + Return True if instance contains ``key``; otherwise return False. + """ + return key in self.__d + + def __iter__(self): # Fix + """ + Iterate through keys in ascending order. + """ + for key in sorted(self.__d): + yield key + + def set_default_env(env): """ Set default values for ``env``. -- cgit From f80beb948bb8914df922e85ef20d9152ca47b527 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Fri, 24 Oct 2008 15:07:07 -0600 Subject: Added ipalib/constants.py; added Env._load_config() method along with comprehensive unit tests for same --- ipalib/config.py | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 78 insertions(+), 3 deletions(-) (limited to 'ipalib/config.py') diff --git a/ipalib/config.py b/ipalib/config.py index 4f7a008d..86d8f1da 100644 --- a/ipalib/config.py +++ b/ipalib/config.py @@ -25,12 +25,13 @@ It will also take care of settings that can be discovered by different methods, such as DNS. """ -from ConfigParser import SafeConfigParser, ParsingError +from ConfigParser import SafeConfigParser, ParsingError, RawConfigParser import types import os +from os import path import sys - from errors import check_isinstance, raise_TypeError +import constants DEFAULT_CONF='/etc/ipa/ipa.conf' @@ -136,6 +137,71 @@ class Env(object): def __init__(self): object.__setattr__(self, '_Env__d', {}) + self.ipalib = path.dirname(path.abspath(__file__)) + self.site_packages = path.dirname(self.ipalib) + self.script = path.abspath(sys.argv[0]) + self.bin = path.dirname(self.script) + self.home = path.abspath(os.environ['HOME']) + self.dot_ipa = path.join(self.home, '.ipa') + + def _bootstrap(self, **overrides): + """ + Initialize basic environment. + + This method will initialize only enough environment information to + determine whether ipa is running in-tree, what the context is, + and the location of the configuration file. + + This method should be called before any plugins are loaded. + """ + for (key, value) in overrides.items(): + self[key] = value + if 'in_tree' not in self: + if self.bin == self.site_packages and \ + path.isfile(path.join(self.bin, 'setup.py')): + self.in_tree = True + else: + self.in_tree = False + if 'context' not in self: + self.context = 'default' + if 'conf' not in self: + name = '%s.conf' % self.context + if self.in_tree: + self.conf = path.join(self.dot_ipa, name) + else: + self.conf = path.join('/', 'etc', 'ipa', name) + + def _load_config(self, conf_file): + """ + Merge in values from ``conf_file`` into this `Env`. + """ + section = constants.CONFIG_SECTION + if not path.isfile(conf_file): + return + parser = RawConfigParser() + try: + parser.read(conf_file) + except ParsingError: + return + if not parser.has_section(section): + parser.add_section(section) + items = parser.items(section) + if len(items) == 0: + return + i = 0 + for (key, value) in items: + if key not in self: + self[key] = value + i += 1 + return (i, len(items)) + + def _finalize(self, **defaults): + """ + Finalize and lock environment. + + This method should be called after all plugins have bean loaded and + after `plugable.API.finalize()` has been called. + """ def __lock__(self): """ @@ -186,6 +252,7 @@ class Env(object): """ Set ``key`` to ``value``. """ + # FIXME: the key should be checked with check_name() if self.__locked: raise AttributeError('locked: cannot set %s.%s to %r' % (self.__class__.__name__, key, value) @@ -194,10 +261,18 @@ class Env(object): raise AttributeError('cannot overwrite %s.%s with %r' % (self.__class__.__name__, key, value) ) - self.__d[key] = value if not callable(value): + if isinstance(value, basestring): + value = str(value.strip()) + if value.lower() == 'true': + value = True + elif value.lower() == 'false': + value = False + elif value.isdigit(): + value = int(value) assert type(value) in (str, int, bool) object.__setattr__(self, key, value) + self.__d[key] = value def __contains__(self, key): """ -- cgit From 2a41db33c6d9f6efa826e16d91eba76defe899d2 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Fri, 24 Oct 2008 15:35:58 -0600 Subject: Env._bootstrap() now raises StandardError if called more than once --- ipalib/config.py | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'ipalib/config.py') diff --git a/ipalib/config.py b/ipalib/config.py index 86d8f1da..dd00d713 100644 --- a/ipalib/config.py +++ b/ipalib/config.py @@ -137,6 +137,7 @@ class Env(object): def __init__(self): object.__setattr__(self, '_Env__d', {}) + object.__setattr__(self, '_Env__done', set()) self.ipalib = path.dirname(path.abspath(__file__)) self.site_packages = path.dirname(self.ipalib) self.script = path.abspath(sys.argv[0]) @@ -144,6 +145,13 @@ class Env(object): self.home = path.abspath(os.environ['HOME']) self.dot_ipa = path.join(self.home, '.ipa') + def __do(self, name): + if name in self.__done: + raise StandardError( + '%s.%s() already called' % (self.__class__.__name__, name) + ) + self.__done.add(name) + def _bootstrap(self, **overrides): """ Initialize basic environment. @@ -154,6 +162,7 @@ class Env(object): This method should be called before any plugins are loaded. """ + self.__do('_bootstrap') for (key, value) in overrides.items(): self[key] = value if 'in_tree' not in self: -- cgit From ac4efac3944d180cffd0ad9d63f631dc928e1d28 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Fri, 24 Oct 2008 20:02:14 -0600 Subject: Finished Env._finalize_core() and corresponding unit tests --- ipalib/config.py | 62 ++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 49 insertions(+), 13 deletions(-) (limited to 'ipalib/config.py') diff --git a/ipalib/config.py b/ipalib/config.py index dd00d713..bced62fd 100644 --- a/ipalib/config.py +++ b/ipalib/config.py @@ -145,13 +145,20 @@ class Env(object): self.home = path.abspath(os.environ['HOME']) self.dot_ipa = path.join(self.home, '.ipa') - def __do(self, name): + def __doing(self, name): if name in self.__done: raise StandardError( '%s.%s() already called' % (self.__class__.__name__, name) ) self.__done.add(name) + def __do_if_not_done(self, name): + if name not in self.__done: + getattr(self, name)() + + def _isdone(self, name): + return name in self.__done + def _bootstrap(self, **overrides): """ Initialize basic environment. @@ -159,10 +166,8 @@ class Env(object): This method will initialize only enough environment information to determine whether ipa is running in-tree, what the context is, and the location of the configuration file. - - This method should be called before any plugins are loaded. """ - self.__do('_bootstrap') + self.__doing('_bootstrap') for (key, value) in overrides.items(): self[key] = value if 'in_tree' not in self: @@ -180,7 +185,46 @@ class Env(object): else: self.conf = path.join('/', 'etc', 'ipa', name) - def _load_config(self, conf_file): + def _finalize_core(self, **defaults): + """ + Complete initialization of standard IPA environment. + + After this method is called, the all environment variables + used by all the built-in plugins will be available. + + This method should be called before loading any plugins. It will + automatically call `Env._bootstrap()` if it has not yet been called. + + After this method has finished, the `Env` instance is still writable + so that third + """ + self.__doing('_finalize_core') + self.__do_if_not_done('_bootstrap') + self._merge_config(self.conf) + if 'in_server' not in self: + self.in_server = (self.context == 'server') + if 'log' not in self: + name = '%s.log' % self.context + if self.in_tree or self.context == 'cli': + self.log = path.join(self.dot_ipa, 'log', name) + else: + self.log = path.join('/', 'var', 'log', 'ipa', name) + for (key, value) in defaults.items(): + if key not in self: + self[key] = value + + def _finalize(self): + """ + Finalize and lock environment. + + This method should be called after all plugins have bean loaded and + after `plugable.API.finalize()` has been called. + """ + self.__doing('_finalize') + self.__do_if_not_done('_finalize_core') + self.__lock__() + + def _merge_config(self, conf_file): """ Merge in values from ``conf_file`` into this `Env`. """ @@ -204,14 +248,6 @@ class Env(object): i += 1 return (i, len(items)) - def _finalize(self, **defaults): - """ - Finalize and lock environment. - - This method should be called after all plugins have bean loaded and - after `plugable.API.finalize()` has been called. - """ - def __lock__(self): """ Prevent further changes to environment. -- cgit From 759734864e72c209d62c970d2e325e96ae02fcb7 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Fri, 24 Oct 2008 20:21:27 -0600 Subject: Finished Env._finalize() and corresponding unit tests --- ipalib/config.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) (limited to 'ipalib/config.py') diff --git a/ipalib/config.py b/ipalib/config.py index bced62fd..71d3024c 100644 --- a/ipalib/config.py +++ b/ipalib/config.py @@ -168,7 +168,7 @@ class Env(object): and the location of the configuration file. """ self.__doing('_bootstrap') - for (key, value) in overrides.items(): + for (key, value) in overrides.iteritems(): self[key] = value if 'in_tree' not in self: if self.bin == self.site_packages and \ @@ -209,11 +209,11 @@ class Env(object): self.log = path.join(self.dot_ipa, 'log', name) else: self.log = path.join('/', 'var', 'log', 'ipa', name) - for (key, value) in defaults.items(): + for (key, value) in defaults.iteritems(): if key not in self: self[key] = value - def _finalize(self): + def _finalize(self, **lastchance): """ Finalize and lock environment. @@ -222,11 +222,14 @@ class Env(object): """ self.__doing('_finalize') self.__do_if_not_done('_finalize_core') + for (key, value) in lastchance.iteritems(): + if key not in self: + self[key] = value self.__lock__() def _merge_config(self, conf_file): """ - Merge in values from ``conf_file`` into this `Env`. + Merge values from ``conf_file`` into this `Env`. """ section = constants.CONFIG_SECTION if not path.isfile(conf_file): @@ -258,6 +261,9 @@ class Env(object): ) object.__setattr__(self, '_Env__locked', True) + def __islocked__(self): + return self.__locked + def __getattr__(self, name): """ Return the attribute named ``name``. -- cgit From 28dd8e74bdefd62307881f6e086af59db97a21a0 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Mon, 27 Oct 2008 00:58:25 -0600 Subject: Env._bootstrap() now also sets Env.conf_default --- ipalib/config.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) (limited to 'ipalib/config.py') diff --git a/ipalib/config.py b/ipalib/config.py index 71d3024c..7bb3e072 100644 --- a/ipalib/config.py +++ b/ipalib/config.py @@ -178,12 +178,14 @@ class Env(object): self.in_tree = False if 'context' not in self: self.context = 'default' + if self.in_tree: + base = self.dot_ipa + else: + base = path.join('/', 'etc', 'ipa') if 'conf' not in self: - name = '%s.conf' % self.context - if self.in_tree: - self.conf = path.join(self.dot_ipa, name) - else: - self.conf = path.join('/', 'etc', 'ipa', name) + self.conf = path.join(base, '%s.conf' % self.context) + if 'conf_default' not in self: + self.conf_default = path.join(base, 'default.conf') def _finalize_core(self, **defaults): """ -- cgit From 25a7df9615058372b81a41df6baa2c4692df0063 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Mon, 27 Oct 2008 01:09:53 -0600 Subject: Env._finalize_core() now also loads config from Env.conf_default --- ipalib/config.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'ipalib/config.py') diff --git a/ipalib/config.py b/ipalib/config.py index 7bb3e072..538a97a7 100644 --- a/ipalib/config.py +++ b/ipalib/config.py @@ -203,6 +203,8 @@ class Env(object): self.__doing('_finalize_core') self.__do_if_not_done('_bootstrap') self._merge_config(self.conf) + if self.conf_default != self.conf: + self._merge_config(self.conf_default) if 'in_server' not in self: self.in_server = (self.context == 'server') if 'log' not in self: -- cgit From 54f37503d2076b99b3b7479b19fec4fa17bc7c59 Mon Sep 17 00:00:00 2001 From: Rob Crittenden Date: Mon, 27 Oct 2008 12:24:17 -0400 Subject: Implement host groups --- ipalib/config.py | 1 + 1 file changed, 1 insertion(+) (limited to 'ipalib/config.py') diff --git a/ipalib/config.py b/ipalib/config.py index b3155490..75e009dc 100644 --- a/ipalib/config.py +++ b/ipalib/config.py @@ -139,6 +139,7 @@ def set_default_env(env): container_group = EnvProp(basestring, 'cn=groups,cn=accounts'), container_service = EnvProp(basestring, 'cn=services,cn=accounts'), container_host = EnvProp(basestring, 'cn=computers,cn=accounts'), + container_hostgroup = EnvProp(basestring, 'cn=hostgroups,cn=accounts'), domain = LazyProp(basestring, get_domain), interactive = EnvProp(bool, True), query_dns = EnvProp(bool, True), -- cgit From c26a3c8542472a2d3931c7dc82edfd684354af6b Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Fri, 7 Nov 2008 02:26:38 -0700 Subject: Finished fist draft of plugin tutorial in ipalib/__init__.py docstring --- ipalib/config.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) (limited to 'ipalib/config.py') diff --git a/ipalib/config.py b/ipalib/config.py index 02a3fadd..aa7d9cdf 100644 --- a/ipalib/config.py +++ b/ipalib/config.py @@ -202,8 +202,8 @@ class Env(object): """ self.__doing('_finalize_core') self.__do_if_not_done('_bootstrap') - self._merge_config(self.conf) - if self.conf_default != self.conf: + if self.__d.get('mode', None) != 'dummy': + self._merge_config(self.conf) self._merge_config(self.conf_default) if 'in_server' not in self: self.in_server = (self.context == 'server') @@ -335,7 +335,13 @@ class Env(object): """ return key in self.__d - def __iter__(self): # Fix + def __len__(self): + """ + Return number of variables currently set. + """ + return len(self.__d) + + def __iter__(self): """ Iterate through keys in ascending order. """ -- cgit From 9aa14333a46d3a57c1fc9fad6068090eb029070f Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Mon, 10 Nov 2008 15:53:10 -0700 Subject: Added 'conf_dir' env variable, which is directory containing config files --- ipalib/config.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'ipalib/config.py') diff --git a/ipalib/config.py b/ipalib/config.py index aa7d9cdf..1bec57e5 100644 --- a/ipalib/config.py +++ b/ipalib/config.py @@ -186,6 +186,8 @@ class Env(object): self.conf = path.join(base, '%s.conf' % self.context) if 'conf_default' not in self: self.conf_default = path.join(base, 'default.conf') + if 'conf_dir' not in self: + self.conf_dir = base def _finalize_core(self, **defaults): """ -- cgit From 5b637f6a18a647a0ff084b2932faa1a4a887a5c2 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Mon, 22 Dec 2008 15:41:24 -0700 Subject: Removed depreciated code from config.py; removed corresponding unit tests --- ipalib/config.py | 281 +------------------------------------------------------ 1 file changed, 1 insertion(+), 280 deletions(-) (limited to 'ipalib/config.py') diff --git a/ipalib/config.py b/ipalib/config.py index 1bec57e5..06ecb13f 100644 --- a/ipalib/config.py +++ b/ipalib/config.py @@ -25,107 +25,13 @@ It will also take care of settings that can be discovered by different methods, such as DNS. """ -from ConfigParser import SafeConfigParser, ParsingError, RawConfigParser +from ConfigParser import RawConfigParser, ParsingError import types import os from os import path import sys -from errors import check_isinstance, raise_TypeError import constants -DEFAULT_CONF='/etc/ipa/ipa.conf' - - -class Environment(object): - """ - A mapping object used to store the environment variables. - """ - - def __init__(self): - object.__setattr__(self, '_Environment__map', {}) - - def __getattr__(self, name): - """ - Return the attribute named ``name``. - """ - return self[name] - - def __setattr__(self, name, value): - """ - Set the attribute named ``name`` to ``value``. - """ - self[name] = value - - def __delattr__(self, name): - """ - Raise AttributeError (deletion is not allowed). - """ - raise AttributeError('cannot del %s.%s' % - (self.__class__.__name__, name) - ) - - def __getitem__(self, key): - """ - Return the value corresponding to ``key``. - """ - val = self.__map[key] - if hasattr(val, 'get_value'): - return val.get_value() - else: - return val - - def __setitem__(self, key, value): - """ - Set the item at ``key`` to ``value``. - """ - if key in self or hasattr(self, key): - if hasattr(self.__map[key], 'set_value'): - self.__map[key].set_value(value) - else: - raise AttributeError('cannot overwrite %s.%s' % - (self.__class__.__name__, key) - ) - else: - self.__map[key] = value - - def __contains__(self, key): - """ - Return True if instance contains ``key``; otherwise return False. - """ - return key in self.__map - - def __iter__(self): - """ - Iterate through keys in ascending order. - """ - for key in sorted(self.__map): - yield key - - def update(self, new_vals, ignore_errors = False): - """ - Update variables using keys and values from ``new_vals``. - - Error will occur if there is an attempt to override variable that was - already set, unless``ignore_errors`` is True. - """ - assert type(new_vals) == dict - for key, value in new_vals.iteritems(): - if ignore_errors: - try: - self[key] = value - except (AttributeError, KeyError): - pass - else: - self[key] = value - - def get(self, name, default=None): - """ - Return the value corresponding to ``key``. Defaults to ``default``. - """ - if name in self: - return self[name] - else: - return default class Env(object): @@ -349,188 +255,3 @@ class Env(object): """ for key in sorted(self.__d): yield key - - -def set_default_env(env): - """ - Set default values for ``env``. - """ - assert isinstance(env, Environment) - - default = dict( - basedn = EnvProp(basestring, 'dc=example,dc=com'), - container_accounts = EnvProp(basestring, 'cn=accounts'), - container_user = EnvProp(basestring, 'cn=users,cn=accounts'), - container_group = EnvProp(basestring, 'cn=groups,cn=accounts'), - container_service = EnvProp(basestring, 'cn=services,cn=accounts'), - container_host = EnvProp(basestring, 'cn=computers,cn=accounts'), - container_hostgroup = EnvProp(basestring, 'cn=hostgroups,cn=accounts'), - domain = LazyProp(basestring, get_domain), - interactive = EnvProp(bool, True), - query_dns = EnvProp(bool, True), - realm = LazyProp(basestring, get_realm), - server_context = EnvProp(bool, True), - server = LazyIter(basestring, get_servers), - verbose = EnvProp(bool, False), - ldaphost = EnvProp(basestring, 'localhost'), - ldapport = EnvProp(int, 389), - ) - - env.update(default) - - -class EnvProp(object): - """ - Environment set-once property with optional default value. - """ - def __init__(self, type_, default, multi_value=False): - """ - :param type_: Type of the property. - :param default: Default value. - :param multi_value: Allow multiple values. - """ - if multi_value: - if isinstance(default, tuple) and len(default): - check_isinstance(default[0], type_, allow_none=True) - self._type = type_ - self._default = default - self._value = None - self._multi_value = multi_value - - def get_value(self): - """ - Return the value if it was set. - - If the value is not set return the default. Otherwise raise an - exception. - """ - if self._get() != None: - return self._get() - else: - raise KeyError, 'Value not set' - - def set_value(self, value): - """ - Set the value. - """ - if self._value != None: - raise KeyError, 'Value already set' - self._value = self._validate(value) - - def _get(self): - """ - Return value, default, or None. - """ - if self._value != None: - return self._value - elif self._default != None: - return self._default - else: - return None - - def _validate(self, value): - """ - Make sure ``value`` is of the right type. Do conversions if necessary. - - This will also handle multi value. - """ - if self._multi_value and isinstance(value, tuple): - converted = [] - for val in value: - converted.append(self._validate_value(val)) - return tuple(converted) - else: - return self._validate_value(value) - - def _validate_value(self, value): - """ - Validate and convert a single value. - """ - bool_true = ('true', 'yes', 'on') - bool_false = ('false', 'no', 'off') - - if self._type == bool and isinstance(value, basestring): - if value.lower() in bool_true: - return True - elif value.lower() in bool_false: - return False - else: - raise raise_TypeError(value, bool, 'value') - check_isinstance(value, self._type, 'value') - return value - - -class LazyProp(EnvProp): - def __init__(self, type_, func, default=None, multi_value=False): - check_isinstance(func, types.FunctionType, 'func') - self._func = func - EnvProp.__init__(self, type_, default, multi_value) - - def get_value(self): - if self._get() != None: - return self._get() - else: - return self._func() - - -class LazyIter(LazyProp): - def __init__(self, type_, func, default=None): - LazyProp.__init__(self, type_, func, default, multi_value=True) - - def get_value(self): - val = self._get() - if val != None: - if type(val) == tuple: - for item in val: - yield item - else: - yield val - for item in self._func(): - if not val or item not in val: - yield item - - -# TODO: Make it possible to use var = 'foo, bar' without -# turning it into ("'foo", "bar'") -def read_config(config_file=None): - assert config_file == None or isinstance(config_file, (basestring, file)) - - parser = SafeConfigParser() - if config_file == None: - files = [DEFAULT_CONF, os.path.expanduser('~/.ipa.conf')] - else: - files = [config_file] - - for f in files: - try: - if isinstance(f, file): - parser.readfp(f) - else: - parser.read(f) - except ParsingError: - print "Can't read %s" % f - - ret = {} - if parser.has_section('defaults'): - for name, value in parser.items('defaults'): - value = tuple(elem.strip() for elem in value.split(',')) - if len(value) == 1: - value = value[0] - ret[name] = value - - return ret - - -# these functions are here just to "emulate" dns resolving for now -def get_domain(): - return "ipatest.com" - - -def get_realm(): - return "IPATEST.COM" - - -def get_servers(): - yield "server.ipatest.com" - yield "backup.ipatest.com" - yield "fake.ipatest.com" -- cgit From c070d390e92df0c9cc6b6070e6c94bd3a130ff65 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Mon, 22 Dec 2008 15:51:54 -0700 Subject: Removed Env.__getattr__(); Env no longer accepts callables for values (no more dynamic/lazy values) --- ipalib/config.py | 38 +++++++++++--------------------------- 1 file changed, 11 insertions(+), 27 deletions(-) (limited to 'ipalib/config.py') diff --git a/ipalib/config.py b/ipalib/config.py index 06ecb13f..8a23cde6 100644 --- a/ipalib/config.py +++ b/ipalib/config.py @@ -176,16 +176,6 @@ class Env(object): def __islocked__(self): return self.__locked - def __getattr__(self, name): - """ - Return the attribute named ``name``. - """ - if name in self.__d: - return self[name] - raise AttributeError('%s.%s' % - (self.__class__.__name__, name) - ) - def __setattr__(self, name, value): """ Set the attribute named ``name`` to ``value``. @@ -204,12 +194,7 @@ class Env(object): """ Return the value corresponding to ``key``. """ - if key not in self.__d: - raise KeyError(key) - value = self.__d[key] - if callable(value): - return value() - return value + return self.__d[key] def __setitem__(self, key, value): """ @@ -224,17 +209,16 @@ class Env(object): raise AttributeError('cannot overwrite %s.%s with %r' % (self.__class__.__name__, key, value) ) - if not callable(value): - if isinstance(value, basestring): - value = str(value.strip()) - if value.lower() == 'true': - value = True - elif value.lower() == 'false': - value = False - elif value.isdigit(): - value = int(value) - assert type(value) in (str, int, bool) - object.__setattr__(self, key, value) + if isinstance(value, basestring): + value = str(value.strip()) + if value.lower() == 'true': + value = True + elif value.lower() == 'false': + value = False + elif value.isdigit(): + value = int(value) + assert type(value) in (str, int, bool) + object.__setattr__(self, key, value) self.__d[key] = value def __contains__(self, key): -- cgit From 014cca57ad31f0ff9230923c8b7fdb1b59157dae Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Mon, 22 Dec 2008 16:16:57 -0700 Subject: The Env.__setitem__() implied conversion is now case sensitive; Env.__setitem__() now also accepts None as a value --- ipalib/config.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) (limited to 'ipalib/config.py') diff --git a/ipalib/config.py b/ipalib/config.py index 8a23cde6..8ff45dd9 100644 --- a/ipalib/config.py +++ b/ipalib/config.py @@ -26,11 +26,11 @@ methods, such as DNS. """ from ConfigParser import RawConfigParser, ParsingError -import types +from types import NoneType import os from os import path import sys -import constants +from constants import CONFIG_SECTION, TYPE_ERROR, OVERRIDE_ERROR @@ -143,7 +143,6 @@ class Env(object): """ Merge values from ``conf_file`` into this `Env`. """ - section = constants.CONFIG_SECTION if not path.isfile(conf_file): return parser = RawConfigParser() @@ -151,9 +150,9 @@ class Env(object): parser.read(conf_file) except ParsingError: return - if not parser.has_section(section): - parser.add_section(section) - items = parser.items(section) + if not parser.has_section(CONFIG_SECTION): + parser.add_section(CONFIG_SECTION) + items = parser.items(CONFIG_SECTION) if len(items) == 0: return i = 0 @@ -211,13 +210,16 @@ class Env(object): ) if isinstance(value, basestring): value = str(value.strip()) - if value.lower() == 'true': - value = True - elif value.lower() == 'false': - value = False + m = { + 'True': True, + 'False': False, + 'None': None, + } + if value in m: + value = m[value] elif value.isdigit(): value = int(value) - assert type(value) in (str, int, bool) + assert type(value) in (str, int, bool, type(NoneType)) object.__setattr__(self, key, value) self.__d[key] = value -- cgit From 6b055b435f93bf9b63ee9b3b2fdd6f082dacc07b Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Mon, 22 Dec 2008 17:29:11 -0700 Subject: Cleaned up Env.__setattr__() and Env.__setitem__() a bit updated their unit tests --- ipalib/config.py | 73 +++++++++++++++++++++++++++++++------------------------- 1 file changed, 40 insertions(+), 33 deletions(-) (limited to 'ipalib/config.py') diff --git a/ipalib/config.py b/ipalib/config.py index 8ff45dd9..7f12b425 100644 --- a/ipalib/config.py +++ b/ipalib/config.py @@ -30,7 +30,8 @@ from types import NoneType import os from os import path import sys -from constants import CONFIG_SECTION, TYPE_ERROR, OVERRIDE_ERROR +from constants import CONFIG_SECTION +from constants import TYPE_ERROR, OVERRIDE_ERROR, LOCK_ERROR @@ -51,6 +52,42 @@ class Env(object): self.home = path.abspath(os.environ['HOME']) self.dot_ipa = path.join(self.home, '.ipa') + def __setattr__(self, name, value): + """ + Set the attribute named ``name`` to ``value``. + + This just calls `Env.__setitem__()`. + """ + self[name] = value + + def __setitem__(self, key, value): + """ + Set ``key`` to ``value``. + """ + # FIXME: the key should be checked with check_name() + if self.__locked: + raise AttributeError( + LOCK_ERROR % (self.__class__.__name__, key, value) + ) + if key in self.__d: + raise AttributeError(OVERRIDE_ERROR % + (self.__class__.__name__, key, self.__d[key], value) + ) + if isinstance(value, basestring): + value = str(value.strip()) + m = { + 'True': True, + 'False': False, + 'None': None, + } + if value in m: + value = m[value] + elif value.isdigit(): + value = int(value) + assert type(value) in (str, int, bool, NoneType) + object.__setattr__(self, key, value) + self.__d[key] = value + def __doing(self, name): if name in self.__done: raise StandardError( @@ -175,11 +212,7 @@ class Env(object): def __islocked__(self): return self.__locked - def __setattr__(self, name, value): - """ - Set the attribute named ``name`` to ``value``. - """ - self[name] = value + def __delattr__(self, name): """ @@ -195,33 +228,7 @@ class Env(object): """ return self.__d[key] - def __setitem__(self, key, value): - """ - Set ``key`` to ``value``. - """ - # FIXME: the key should be checked with check_name() - if self.__locked: - raise AttributeError('locked: cannot set %s.%s to %r' % - (self.__class__.__name__, key, value) - ) - if key in self.__d or hasattr(self, key): - raise AttributeError('cannot overwrite %s.%s with %r' % - (self.__class__.__name__, key, value) - ) - if isinstance(value, basestring): - value = str(value.strip()) - m = { - 'True': True, - 'False': False, - 'None': None, - } - if value in m: - value = m[value] - elif value.isdigit(): - value = int(value) - assert type(value) in (str, int, bool, type(NoneType)) - object.__setattr__(self, key, value) - self.__d[key] = value + def __contains__(self, key): """ -- cgit From 01cae56e0a19876cf6a614469c0c5e6fb73170e6 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Mon, 22 Dec 2008 21:02:43 -0700 Subject: Some more reorganization in Env and added class docstring to Env with lots of examples --- ipalib/config.py | 120 +++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 104 insertions(+), 16 deletions(-) (limited to 'ipalib/config.py') diff --git a/ipalib/config.py b/ipalib/config.py index 7f12b425..4631d899 100644 --- a/ipalib/config.py +++ b/ipalib/config.py @@ -31,13 +31,95 @@ import os from os import path import sys from constants import CONFIG_SECTION -from constants import TYPE_ERROR, OVERRIDE_ERROR, LOCK_ERROR - +from constants import TYPE_ERROR, OVERRIDE_ERROR, SET_ERROR, DEL_ERROR class Env(object): """ - A mapping object used to store the environment variables. + Store and retrieve environment variables. + + First an foremost, the `Env` class provides a handy container for + environment variables. These variables can be both set and retrieved as + either attributes or as dictionary items. + + For example, we can set a variable as an attribute: + + >>> env = Env() + >>> env.attr = 'I was set as an attribute.' + >>> env.attr # Retrieve as an attribute + 'I was set as an attribute.' + >>> env['attr'] # Also retrieve as a dictionary item + 'I was set as an attribute.' + + Or we can set a variable as a dictionary item: + + >>> env['item'] = 'I was set as a dictionary item.' + >>> env['item'] # Retrieve as a dictionary item + 'I was set as a dictionary item.' + >>> env.item # Also retrieve as an attribute + 'I was set as a dictionary item.' + + The variable values can be ``str`` or ``int`` instances, or the ``True``, + ``False``, or ``None`` constants. When the value provided is an ``str`` + instance, some limited automatic type conversion is performed, which allows + values of specific types to be set easily from configuration files and from + command-line options. + + The ``True``, ``False``, and ``None`` constants can be specified with a + string that matches what ``repr()`` would return. For example: + + >>> env.true = 'True' + >>> env.true + True + + Note that the automatic type conversion is case sensitive. For example: + + >>> env.false = 'false' # Doesn't match repr(False) + >>> env.false + 'false' + + If an ``str`` value looks like an integer, it's automatically converted to + the ``int`` type. For example: + + >>> env.lucky = '7' + >>> env.lucky + 7 + + Also, leading and trailing white-space is automatically stripped from + ``str`` values. For example: + + >>> env.message = ' Hello! ' # Surrounded by double spaces + >>> env.message + 'Hello!' + >>> env.number = '42 ' # Still converted to an int + >>> env.number + 42 + >>> env.actually_false = ' False' # Still matches repr(False) + >>> env.actually_false + False + + `Env` is set-once, first-one-wins. Once a variable has been set, trying to + override it will raise an ``AttributeError``. For example: + + >>> env.my_var = 'first' + >>> env.my_var = 'second' + Traceback (most recent call last): + ... + AttributeError: cannot override Env.my_var value 'first' with 'second' + + An `Env` instance can also be *locked*, after which no further variables can + be set. Trying to set variables on a locked `Env` instance will also raise + an ``AttributeError``. For example: + + >>> env = Env() + >>> env.var1 = 'This will work.' + >>> env.__lock__() + >>> env.var2 = 'This wont work!' + Traceback (most recent call last): + ... + AttributeError: locked: cannot set Env.var2 to 'This wont work!' + + Finish me! """ __locked = False @@ -67,12 +149,16 @@ class Env(object): # FIXME: the key should be checked with check_name() if self.__locked: raise AttributeError( - LOCK_ERROR % (self.__class__.__name__, key, value) + SET_ERROR % (self.__class__.__name__, key, value) ) if key in self.__d: raise AttributeError(OVERRIDE_ERROR % (self.__class__.__name__, key, self.__d[key], value) ) + if hasattr(self, key): + raise AttributeError(OVERRIDE_ERROR % + (self.__class__.__name__, key, getattr(self, key), value) + ) if isinstance(value, basestring): value = str(value.strip()) m = { @@ -88,6 +174,20 @@ class Env(object): object.__setattr__(self, key, value) self.__d[key] = value + def __getitem__(self, key): + """ + Return the value corresponding to ``key``. + """ + return self.__d[key] + + def __delattr__(self, name): + """ + Raise AttributeError (deletion is never allowed). + """ + raise AttributeError( + DEL_ERROR % (self.__class__.__name__, name) + ) + def __doing(self, name): if name in self.__done: raise StandardError( @@ -214,19 +314,7 @@ class Env(object): - def __delattr__(self, name): - """ - Raise AttributeError (deletion is not allowed). - """ - raise AttributeError('cannot del %s.%s' % - (self.__class__.__name__, name) - ) - def __getitem__(self, key): - """ - Return the value corresponding to ``key``. - """ - return self.__d[key] -- cgit From fd43b39145382b96cd2e0d0da3d5dcbe0d3a4a2a Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Mon, 22 Dec 2008 23:09:35 -0700 Subject: Moved setting of run-time variables from Env.__init__() to Env._bootstrap() --- ipalib/config.py | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) (limited to 'ipalib/config.py') diff --git a/ipalib/config.py b/ipalib/config.py index 4631d899..9fe02cb3 100644 --- a/ipalib/config.py +++ b/ipalib/config.py @@ -62,15 +62,18 @@ class Env(object): The variable values can be ``str`` or ``int`` instances, or the ``True``, ``False``, or ``None`` constants. When the value provided is an ``str`` instance, some limited automatic type conversion is performed, which allows - values of specific types to be set easily from configuration files and from + values of specific types to be set easily from configuration files or command-line options. The ``True``, ``False``, and ``None`` constants can be specified with a string that matches what ``repr()`` would return. For example: - >>> env.true = 'True' + >>> env.true = True + >>> env.also_true = 'True' >>> env.true True + >>> env.also_true + True Note that the automatic type conversion is case sensitive. For example: @@ -98,14 +101,14 @@ class Env(object): >>> env.actually_false False - `Env` is set-once, first-one-wins. Once a variable has been set, trying to - override it will raise an ``AttributeError``. For example: + `Env` variables are all set-once (first-one-wins). Once a variable has been + set, trying to override it will raise an ``AttributeError``. For example: - >>> env.my_var = 'first' - >>> env.my_var = 'second' + >>> env.date = 'First' + >>> env.date = 'Second' Traceback (most recent call last): ... - AttributeError: cannot override Env.my_var value 'first' with 'second' + AttributeError: cannot override Env.date value 'First' with 'Second' An `Env` instance can also be *locked*, after which no further variables can be set. Trying to set variables on a locked `Env` instance will also raise @@ -127,12 +130,6 @@ class Env(object): def __init__(self): object.__setattr__(self, '_Env__d', {}) object.__setattr__(self, '_Env__done', set()) - self.ipalib = path.dirname(path.abspath(__file__)) - self.site_packages = path.dirname(self.ipalib) - self.script = path.abspath(sys.argv[0]) - self.bin = path.dirname(self.script) - self.home = path.abspath(os.environ['HOME']) - self.dot_ipa = path.join(self.home, '.ipa') def __setattr__(self, name, value): """ @@ -211,6 +208,15 @@ class Env(object): and the location of the configuration file. """ self.__doing('_bootstrap') + + # Set run-time variables: + self.ipalib = path.dirname(path.abspath(__file__)) + self.site_packages = path.dirname(self.ipalib) + self.script = path.abspath(sys.argv[0]) + self.bin = path.dirname(self.script) + self.home = path.abspath(os.environ['HOME']) + self.dot_ipa = path.join(self.home, '.ipa') + for (key, value) in overrides.iteritems(): self[key] = value if 'in_tree' not in self: -- cgit From 16526142f36e81f4d8a767f339c559188485f756 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Tue, 23 Dec 2008 01:11:03 -0700 Subject: Finished Env class docstring; more organizational cleanup in Env and its unit tests --- ipalib/config.py | 159 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 99 insertions(+), 60 deletions(-) (limited to 'ipalib/config.py') diff --git a/ipalib/config.py b/ipalib/config.py index 9fe02cb3..c39d99a9 100644 --- a/ipalib/config.py +++ b/ipalib/config.py @@ -39,14 +39,14 @@ class Env(object): Store and retrieve environment variables. First an foremost, the `Env` class provides a handy container for - environment variables. These variables can be both set and retrieved as - either attributes or as dictionary items. + environment variables. These variables can be both set *and* retrieved + either as attributes *or* as dictionary items. For example, we can set a variable as an attribute: >>> env = Env() >>> env.attr = 'I was set as an attribute.' - >>> env.attr # Retrieve as an attribute + >>> env.attr 'I was set as an attribute.' >>> env['attr'] # Also retrieve as a dictionary item 'I was set as an attribute.' @@ -54,7 +54,7 @@ class Env(object): Or we can set a variable as a dictionary item: >>> env['item'] = 'I was set as a dictionary item.' - >>> env['item'] # Retrieve as a dictionary item + >>> env['item'] 'I was set as a dictionary item.' >>> env.item # Also retrieve as an attribute 'I was set as a dictionary item.' @@ -65,11 +65,12 @@ class Env(object): values of specific types to be set easily from configuration files or command-line options. - The ``True``, ``False``, and ``None`` constants can be specified with a - string that matches what ``repr()`` would return. For example: + So in addition to their actual values, the ``True``, ``False``, and ``None`` + constants can be specified with an ``str`` equal to what ``repr()`` would + return. For example: >>> env.true = True - >>> env.also_true = 'True' + >>> env.also_true = 'True' # Equal to repr(True) >>> env.true True >>> env.also_true @@ -77,7 +78,7 @@ class Env(object): Note that the automatic type conversion is case sensitive. For example: - >>> env.false = 'false' # Doesn't match repr(False) + >>> env.false = 'false' # Not equal to repr(False)! >>> env.false 'false' @@ -88,19 +89,25 @@ class Env(object): >>> env.lucky 7 - Also, leading and trailing white-space is automatically stripped from - ``str`` values. For example: + Leading and trailing white-space is automatically stripped from ``str`` + values. For example: >>> env.message = ' Hello! ' # Surrounded by double spaces >>> env.message 'Hello!' - >>> env.number = '42 ' # Still converted to an int + >>> env.number = ' 42 ' # Still converted to an int >>> env.number 42 - >>> env.actually_false = ' False' # Still matches repr(False) + >>> env.actually_false = ' False ' # Still equal to repr(False) >>> env.actually_false False + Also, empty ``str`` instances are converted to ``None``. For example: + + >>> env.empty = '' + >>> env.empty is None + True + `Env` variables are all set-once (first-one-wins). Once a variable has been set, trying to override it will raise an ``AttributeError``. For example: @@ -110,19 +117,56 @@ class Env(object): ... AttributeError: cannot override Env.date value 'First' with 'Second' - An `Env` instance can also be *locked*, after which no further variables can - be set. Trying to set variables on a locked `Env` instance will also raise + An `Env` instance can be *locked*, after which no further variables can be + set. Trying to set variables on a locked `Env` instance will also raise an ``AttributeError``. For example: >>> env = Env() - >>> env.var1 = 'This will work.' + >>> env.okay = 'This will work.' >>> env.__lock__() - >>> env.var2 = 'This wont work!' + >>> env.nope = 'This wont work!' Traceback (most recent call last): ... - AttributeError: locked: cannot set Env.var2 to 'This wont work!' + AttributeError: locked: cannot set Env.nope to 'This wont work!' - Finish me! + `Env` instances also provide standard container emulation for membership + testing, counting, and iteration. For example: + + >>> env = Env() + >>> 'key1' in env # Has key1 been set? + False + >>> env.key1 = 'value 1' + >>> 'key1' in env + True + >>> env.key2 = 'value 2' + >>> len(env) # How many variables have been set? + 2 + >>> list(env) # What variables have been set? + ['key1', 'key2'] + + Lastly, in addition to all the handy container functionality, the `Env` + class provides high-level methods for bootstraping a fresh `Env` instance + into one containing all the run-time and configuration information needed + by the built-in freeIPA plugins. + + These are the `Env` bootstraping methods, in the order they must be called: + + 1. `Env._bootstrap()` - initialize the run-time variables and then + merge-in variables specified on the command-line. + + 2. `Env._finalize_core()` - merge-in variables from the configuration + files and then merge-in variables from the internal defaults, after + which at least all the standard variables will be set. After this + method is called, the plugins will be loaded, during which 3rd-party + plugins can set additional variables they may need. + + 3. `Env._finalize()` - one last chance to merge-in variables and then + the instance is locked. After this method is called, no more + environment variables can be set during the remaining life of the + process. + + However, normally none of the above methods are called directly and only + `ipalib.plugable.API.bootstrap()` is called instead. """ __locked = False @@ -131,6 +175,22 @@ class Env(object): object.__setattr__(self, '_Env__d', {}) object.__setattr__(self, '_Env__done', set()) + def __lock__(self): + """ + Prevent further changes to environment. + """ + if self.__locked is True: + raise StandardError( + '%s.__lock__() already called' % self.__class__.__name__ + ) + object.__setattr__(self, '_Env__locked', True) + + def __islocked__(self): + """ + Return ``True`` if locked. + """ + return self.__locked + def __setattr__(self, name, value): """ Set the attribute named ``name`` to ``value``. @@ -152,16 +212,14 @@ class Env(object): raise AttributeError(OVERRIDE_ERROR % (self.__class__.__name__, key, self.__d[key], value) ) - if hasattr(self, key): - raise AttributeError(OVERRIDE_ERROR % - (self.__class__.__name__, key, getattr(self, key), value) - ) + assert not hasattr(self, key) if isinstance(value, basestring): value = str(value.strip()) m = { 'True': True, 'False': False, 'None': None, + '': None, } if value in m: value = m[value] @@ -185,6 +243,25 @@ class Env(object): DEL_ERROR % (self.__class__.__name__, name) ) + def __contains__(self, key): + """ + Return True if instance contains ``key``; otherwise return False. + """ + return key in self.__d + + def __len__(self): + """ + Return number of variables currently set. + """ + return len(self.__d) + + def __iter__(self): + """ + Iterate through keys in ascending order. + """ + for key in sorted(self.__d): + yield key + def __doing(self, name): if name in self.__done: raise StandardError( @@ -304,41 +381,3 @@ class Env(object): self[key] = value i += 1 return (i, len(items)) - - def __lock__(self): - """ - Prevent further changes to environment. - """ - if self.__locked is True: - raise StandardError( - '%s.__lock__() already called' % self.__class__.__name__ - ) - object.__setattr__(self, '_Env__locked', True) - - def __islocked__(self): - return self.__locked - - - - - - - - def __contains__(self, key): - """ - Return True if instance contains ``key``; otherwise return False. - """ - return key in self.__d - - def __len__(self): - """ - Return number of variables currently set. - """ - return len(self.__d) - - def __iter__(self): - """ - Iterate through keys in ascending order. - """ - for key in sorted(self.__d): - yield key -- cgit From f7cae9a27cde3346783809cbf949762b0c0708f1 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Tue, 23 Dec 2008 01:28:00 -0700 Subject: More docstring cleanup in Env and its methods --- ipalib/config.py | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) (limited to 'ipalib/config.py') diff --git a/ipalib/config.py b/ipalib/config.py index c39d99a9..8c602cdc 100644 --- a/ipalib/config.py +++ b/ipalib/config.py @@ -165,8 +165,8 @@ class Env(object): environment variables can be set during the remaining life of the process. - However, normally none of the above methods are called directly and only - `ipalib.plugable.API.bootstrap()` is called instead. + However, normally none of the above methods are called directly and instead + only `ipalib.plugable.API.bootstrap()` is called. """ __locked = False @@ -280,9 +280,10 @@ class Env(object): """ Initialize basic environment. - This method will initialize only enough environment information to - determine whether ipa is running in-tree, what the context is, - and the location of the configuration file. + In addition to certain run-time information, this method will + initialize only enough environment information to determine whether + IPA is running in-tree, what the context is, and the location of the + configuration file. """ self.__doing('_bootstrap') @@ -322,11 +323,12 @@ class Env(object): After this method is called, the all environment variables used by all the built-in plugins will be available. - This method should be called before loading any plugins. It will + This method should be called before loading any plugins. It will automatically call `Env._bootstrap()` if it has not yet been called. After this method has finished, the `Env` instance is still writable - so that third + so that 3rd-party plugins can set variables they may require as the + plugins are registered. """ self.__doing('_finalize_core') self.__do_if_not_done('_bootstrap') @@ -349,8 +351,17 @@ class Env(object): """ Finalize and lock environment. - This method should be called after all plugins have bean loaded and - after `plugable.API.finalize()` has been called. + This method should be called after all plugins have been loaded and + after `plugable.API.finalize()` has been called. This method will + automatically call `Env._finalize_core()` if it hasn't been called + already, but in normal operation this would result in an exception + being raised because the internal default values will not have been + merged-in. + + After this method finishes, the `Env` instance will be locked and no + more environment variables can be set. Aside from unit-tests and + example code, normally only one `Env` instance is created, meaning + no more variables can be set during the remaining life of the process. """ self.__doing('_finalize') self.__do_if_not_done('_finalize_core') -- cgit From 7766f0be6189c06c3cbbdef59bdbf4eb2a65e2a1 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Tue, 23 Dec 2008 01:59:31 -0700 Subject: Yet more small docstring cleanup in Env --- ipalib/config.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'ipalib/config.py') diff --git a/ipalib/config.py b/ipalib/config.py index 8c602cdc..dc5d35f8 100644 --- a/ipalib/config.py +++ b/ipalib/config.py @@ -42,7 +42,7 @@ class Env(object): environment variables. These variables can be both set *and* retrieved either as attributes *or* as dictionary items. - For example, we can set a variable as an attribute: + For example, you can set a variable as an attribute: >>> env = Env() >>> env.attr = 'I was set as an attribute.' @@ -51,7 +51,7 @@ class Env(object): >>> env['attr'] # Also retrieve as a dictionary item 'I was set as an attribute.' - Or we can set a variable as a dictionary item: + Or you can set a variable as a dictionary item: >>> env['item'] = 'I was set as a dictionary item.' >>> env['item'] @@ -166,7 +166,8 @@ class Env(object): process. However, normally none of the above methods are called directly and instead - only `ipalib.plugable.API.bootstrap()` is called. + only `plugable.API.bootstrap()` is called, which itself takes care of + correctly calling the `Env` bootstrapping methods. """ __locked = False @@ -360,7 +361,7 @@ class Env(object): After this method finishes, the `Env` instance will be locked and no more environment variables can be set. Aside from unit-tests and - example code, normally only one `Env` instance is created, meaning + example code, normally only one `Env` instance is created, which means no more variables can be set during the remaining life of the process. """ self.__doing('_finalize') -- cgit From e14fc84dfccbb06f775bbd5d3de864c7b879453f Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Mon, 29 Dec 2008 21:23:34 -0700 Subject: Renamed Env._merge_config() to Env._merge_from_file() --- ipalib/config.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'ipalib/config.py') diff --git a/ipalib/config.py b/ipalib/config.py index dc5d35f8..6b016541 100644 --- a/ipalib/config.py +++ b/ipalib/config.py @@ -334,8 +334,8 @@ class Env(object): self.__doing('_finalize_core') self.__do_if_not_done('_bootstrap') if self.__d.get('mode', None) != 'dummy': - self._merge_config(self.conf) - self._merge_config(self.conf_default) + self._merge_from_file(self.conf) + self._merge_from_file(self.conf_default) if 'in_server' not in self: self.in_server = (self.context == 'server') if 'log' not in self: @@ -371,7 +371,7 @@ class Env(object): self[key] = value self.__lock__() - def _merge_config(self, conf_file): + def _merge_from_file(self, conf_file): """ Merge values from ``conf_file`` into this `Env`. """ -- cgit From 447c88a2bb9dd364f9c67a73bfce5000ac81d375 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Tue, 30 Dec 2008 00:45:48 -0700 Subject: Started moving some core classes and functions from plugable.py to new base.py module --- ipalib/config.py | 81 +++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 54 insertions(+), 27 deletions(-) (limited to 'ipalib/config.py') diff --git a/ipalib/config.py b/ipalib/config.py index 6b016541..7317e4f0 100644 --- a/ipalib/config.py +++ b/ipalib/config.py @@ -30,6 +30,8 @@ from types import NoneType import os from os import path import sys + +from base import check_name from constants import CONFIG_SECTION from constants import TYPE_ERROR, OVERRIDE_ERROR, SET_ERROR, DEL_ERROR @@ -204,11 +206,11 @@ class Env(object): """ Set ``key`` to ``value``. """ - # FIXME: the key should be checked with check_name() if self.__locked: raise AttributeError( SET_ERROR % (self.__class__.__name__, key, value) ) + check_name(key) if key in self.__d: raise AttributeError(OVERRIDE_ERROR % (self.__class__.__name__, key, self.__d[key], value) @@ -263,6 +265,56 @@ class Env(object): for key in sorted(self.__d): yield key + def _merge(self, **kw): + """ + Merge variables in ``kw`` into environment. + + Any variables in ``kw`` that have already been set will be skipped + (which means this method will not try to override them). + + This method returns a (set, total) tuple contained the number of + variables actually set and the number of variables requested to be set. + + For example: + + >>> env = Env() + >>> env._merge(first=1, second=2) + (2, 2) + >>> env._merge(first=1, third=3) + (1, 2) + >>> env._merge(first=1, second=2, third=3) + (0, 3) + """ + i = 0 + for (key, value) in kw.iteritems(): + if key not in self: + self[key] = value + i += 1 + return (i, len(kw)) + + def _merge_from_file(self, conf_file): + """ + Merge values from ``conf_file`` into this `Env`. + """ + if not path.isfile(conf_file): + return + parser = RawConfigParser() + try: + parser.read(conf_file) + except ParsingError: + return + if not parser.has_section(CONFIG_SECTION): + parser.add_section(CONFIG_SECTION) + items = parser.items(CONFIG_SECTION) + if len(items) == 0: + return + i = 0 + for (key, value) in items: + if key not in self: + self[key] = value + i += 1 + return (i, len(items)) + def __doing(self, name): if name in self.__done: raise StandardError( @@ -344,9 +396,7 @@ class Env(object): self.log = path.join(self.dot_ipa, 'log', name) else: self.log = path.join('/', 'var', 'log', 'ipa', name) - for (key, value) in defaults.iteritems(): - if key not in self: - self[key] = value + self._merge(**defaults) def _finalize(self, **lastchance): """ @@ -370,26 +420,3 @@ class Env(object): if key not in self: self[key] = value self.__lock__() - - def _merge_from_file(self, conf_file): - """ - Merge values from ``conf_file`` into this `Env`. - """ - if not path.isfile(conf_file): - return - parser = RawConfigParser() - try: - parser.read(conf_file) - except ParsingError: - return - if not parser.has_section(CONFIG_SECTION): - parser.add_section(CONFIG_SECTION) - items = parser.items(CONFIG_SECTION) - if len(items) == 0: - return - i = 0 - for (key, value) in items: - if key not in self: - self[key] = value - i += 1 - return (i, len(items)) -- cgit From 11e165073eb3bd21d764fa1c4d71f1e6a52eae1b Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Tue, 30 Dec 2008 03:11:45 -0700 Subject: Docstring cleanup in the Env bootstraping methods --- ipalib/config.py | 123 ++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 86 insertions(+), 37 deletions(-) (limited to 'ipalib/config.py') diff --git a/ipalib/config.py b/ipalib/config.py index 7317e4f0..c3c3a95c 100644 --- a/ipalib/config.py +++ b/ipalib/config.py @@ -267,23 +267,27 @@ class Env(object): def _merge(self, **kw): """ - Merge variables in ``kw`` into environment. + Merge variables from ``kw`` into the environment. - Any variables in ``kw`` that have already been set will be skipped - (which means this method will not try to override them). + Any variables in ``kw`` that have already been set will be ignored + (meaning this method will *not* try to override them, which would raise + an exception). - This method returns a (set, total) tuple contained the number of - variables actually set and the number of variables requested to be set. + This method returns a ``(num_set, num_total)`` tuple containing first + the number of variables that were actually set, and second the total + number of variables that were provided. For example: >>> env = Env() - >>> env._merge(first=1, second=2) + >>> env._merge(one=1, two=2) (2, 2) - >>> env._merge(first=1, third=3) + >>> env._merge(one=1, three=3) (1, 2) - >>> env._merge(first=1, second=2, third=3) + >>> env._merge(one=1, two=2, three=3) (0, 3) + + Also see `Env._merge_from_file()`. """ i = 0 for (key, value) in kw.iteritems(): @@ -292,22 +296,35 @@ class Env(object): i += 1 return (i, len(kw)) - def _merge_from_file(self, conf_file): + def _merge_from_file(self, config_file): """ - Merge values from ``conf_file`` into this `Env`. + Merge variables from ``config_file`` into the environment. + + Any variables in ``config_file`` that have already been set will be + ignored (meaning this method will *not* try to override them, which + would raise an exception). + + If ``config_file`` does not exist or is not a regular file, or if there + is an error parsing ``config_file``, ``None`` is returned. + + Otherwise this method returns a ``(num_set, num_total)`` tuple + containing first the number of variables that were actually set, and + second the total number of variables found in ``config_file``. + + Also see `Env._merge()`. """ - if not path.isfile(conf_file): + if not path.isfile(config_file): return parser = RawConfigParser() try: - parser.read(conf_file) + parser.read(config_file) except ParsingError: return if not parser.has_section(CONFIG_SECTION): parser.add_section(CONFIG_SECTION) items = parser.items(CONFIG_SECTION) if len(items) == 0: - return + return (0, 0) i = 0 for (key, value) in items: if key not in self: @@ -333,10 +350,24 @@ class Env(object): """ Initialize basic environment. - In addition to certain run-time information, this method will - initialize only enough environment information to determine whether - IPA is running in-tree, what the context is, and the location of the - configuration file. + This method will perform the following steps: + + 1. Initialize certain run-time variables. These run-time variables + are strictly determined by the external environment the process + is running in; they cannot be specified on the command-line nor + in the configuration files. + + 2. Merge-in the variables in ``overrides`` by calling + `Env._merge()`. The intended use of ``overrides`` is to merge-in + variables specified on the command-line. + + 3. Intelligently fill-in the ``in_tree``, ``context``, ``conf``, + and ``conf_default`` variables if they haven`t been set already. + + Also see `Env._finalize_core()`, the next method in the bootstrap + sequence. + + :param overrides: Variables specified via command-line options. """ self.__doing('_bootstrap') @@ -347,9 +378,7 @@ class Env(object): self.bin = path.dirname(self.script) self.home = path.abspath(os.environ['HOME']) self.dot_ipa = path.join(self.home, '.ipa') - - for (key, value) in overrides.iteritems(): - self[key] = value + self._merge(**overrides) if 'in_tree' not in self: if self.bin == self.site_packages and \ path.isfile(path.join(self.bin, 'setup.py')): @@ -373,15 +402,33 @@ class Env(object): """ Complete initialization of standard IPA environment. - After this method is called, the all environment variables - used by all the built-in plugins will be available. + This method will perform the following steps: - This method should be called before loading any plugins. It will - automatically call `Env._bootstrap()` if it has not yet been called. + 1. Call `Env._bootstrap()` if it hasn't already been called. + + 2. Merge-in variables from the configuration file ``self.conf`` + (if it exists) by calling `Env._merge_from_file()`. + + 3. Merge-in variables from the defaults configuration file + ``self.conf_default`` (if it exists) by calling + `Env._merge_from_file()`. + + 4. Intelligently fill-in the ``in_server`` and ``log`` variables + if they haven't already been set. + + 5. Merge in the internal defaults by calling `Env._merge()`. In + normal circumstances, these internal defaults will simply be + those specified in `constants.DEFAULT_CONFIG`. + + After this method is called, the all environment variables + used by all the built-in plugins will be available. As such, this + method should be called *before* any plugins are loaded. After this method has finished, the `Env` instance is still writable so that 3rd-party plugins can set variables they may require as the plugins are registered. + + Also see `Env._finalize()`, the final method in the bootstrap sequence. """ self.__doing('_finalize_core') self.__do_if_not_done('_bootstrap') @@ -402,21 +449,23 @@ class Env(object): """ Finalize and lock environment. + This method will perform the following steps: + + 1. Call `Env._finalize_core()` if it hasn't already been called. + + 2. Merge-in the variables in ``lastchance`` by calling + `Env._merge()`. + + 3. Lock this `Env` instance, after which no more environment + variables can be set on this instance. Aside from unit-tests + and example code, normally only one `Env` instance is created, + which means that after this step, no more variables can be set + during the remaining life of the process. + This method should be called after all plugins have been loaded and - after `plugable.API.finalize()` has been called. This method will - automatically call `Env._finalize_core()` if it hasn't been called - already, but in normal operation this would result in an exception - being raised because the internal default values will not have been - merged-in. - - After this method finishes, the `Env` instance will be locked and no - more environment variables can be set. Aside from unit-tests and - example code, normally only one `Env` instance is created, which means - no more variables can be set during the remaining life of the process. + after `plugable.API.finalize()` has been called. """ self.__doing('_finalize') self.__do_if_not_done('_finalize_core') - for (key, value) in lastchance.iteritems(): - if key not in self: - self[key] = value + self._merge(**lastchance) self.__lock__() -- cgit From 03c9114958e428c5fe6b286df9eda3bd932dc9dc Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Tue, 30 Dec 2008 13:52:36 -0700 Subject: More docstring cleanup in ipalib.config --- ipalib/config.py | 50 +++++++++++++++++++++++++++++++++++++------------- 1 file changed, 37 insertions(+), 13 deletions(-) (limited to 'ipalib/config.py') diff --git a/ipalib/config.py b/ipalib/config.py index c3c3a95c..84c0aab4 100644 --- a/ipalib/config.py +++ b/ipalib/config.py @@ -18,11 +18,14 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ -Basic configuration management. +Process-wide static configuration and environment. -This module handles the reading and representation of basic local settings. -It will also take care of settings that can be discovered by different -methods, such as DNS. +The standard run-time instance of the `Env` class is initialized early in the +`ipalib` process and is then locked into a read-only state, after which no +further changes can be made to the environment throughout the remaining life +of the process. + +For the per-request thread-local information, see `ipalib.request`. """ from ConfigParser import RawConfigParser, ParsingError @@ -288,6 +291,8 @@ class Env(object): (0, 3) Also see `Env._merge_from_file()`. + + :param kw: Variables provides as keyword arguments. """ i = 0 for (key, value) in kw.iteritems(): @@ -311,8 +316,23 @@ class Env(object): containing first the number of variables that were actually set, and second the total number of variables found in ``config_file``. + This method will raise a ``ValueError`` if ``config_file`` is not an + absolute path. For example: + + >>> env = Env() + >>> env._merge_from_file('my/config.conf') + Traceback (most recent call last): + ... + ValueError: config_file must be an absolute path; got 'my/config.conf' + Also see `Env._merge()`. + + :param config_file: Absolute path of the configuration file to load. """ + if path.abspath(config_file) != config_file: + raise ValueError( + 'config_file must be an absolute path; got %r' % config_file + ) if not path.isfile(config_file): return parser = RawConfigParser() @@ -361,8 +381,8 @@ class Env(object): `Env._merge()`. The intended use of ``overrides`` is to merge-in variables specified on the command-line. - 3. Intelligently fill-in the ``in_tree``, ``context``, ``conf``, - and ``conf_default`` variables if they haven`t been set already. + 3. Intelligently fill-in the *in_tree*, *context*, *conf*, and + *conf_default* variables if they haven`t been set already. Also see `Env._finalize_core()`, the next method in the bootstrap sequence. @@ -413,22 +433,24 @@ class Env(object): ``self.conf_default`` (if it exists) by calling `Env._merge_from_file()`. - 4. Intelligently fill-in the ``in_server`` and ``log`` variables + 4. Intelligently fill-in the *in_server* and *log* variables if they haven't already been set. - 5. Merge in the internal defaults by calling `Env._merge()`. In - normal circumstances, these internal defaults will simply be - those specified in `constants.DEFAULT_CONFIG`. + 5. Merge-in the variables in ``defaults`` by calling `Env._merge()`. + In normal circumstances ``defaults`` will simply be those + specified in `constants.DEFAULT_CONFIG`. - After this method is called, the all environment variables - used by all the built-in plugins will be available. As such, this - method should be called *before* any plugins are loaded. + After this method is called, all the environment variables used by all + the built-in plugins will be available. As such, this method should be + called *before* any plugins are loaded. After this method has finished, the `Env` instance is still writable so that 3rd-party plugins can set variables they may require as the plugins are registered. Also see `Env._finalize()`, the final method in the bootstrap sequence. + + :param defaults: Internal defaults for all built-in variables. """ self.__doing('_finalize_core') self.__do_if_not_done('_bootstrap') @@ -464,6 +486,8 @@ class Env(object): This method should be called after all plugins have been loaded and after `plugable.API.finalize()` has been called. + + :param lastchance: Any final variables to merge-in before locking. """ self.__doing('_finalize') self.__do_if_not_done('_finalize_core') -- cgit From ecccc5c236a1a11f899e9776991dbc1e719b9490 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Tue, 30 Dec 2008 14:05:08 -0700 Subject: Added my name to Athors of config.py --- ipalib/config.py | 1 + 1 file changed, 1 insertion(+) (limited to 'ipalib/config.py') diff --git a/ipalib/config.py b/ipalib/config.py index 84c0aab4..59e531e9 100644 --- a/ipalib/config.py +++ b/ipalib/config.py @@ -1,5 +1,6 @@ # Authors: # Martin Nagy +# Jason Gerard DeRose # # Copyright (C) 2008 Red Hat # see file 'COPYING' for use and warranty information -- cgit From 379c549fc16fbb2eed6685f5e189da26f021abe9 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Tue, 30 Dec 2008 15:02:15 -0700 Subject: Env now supports float values --- ipalib/config.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) (limited to 'ipalib/config.py') diff --git a/ipalib/config.py b/ipalib/config.py index 59e531e9..c1947433 100644 --- a/ipalib/config.py +++ b/ipalib/config.py @@ -65,10 +65,10 @@ class Env(object): >>> env.item # Also retrieve as an attribute 'I was set as a dictionary item.' - The variable values can be ``str`` or ``int`` instances, or the ``True``, - ``False``, or ``None`` constants. When the value provided is an ``str`` - instance, some limited automatic type conversion is performed, which allows - values of specific types to be set easily from configuration files or + The variable values can be ``str``, ``int``, or ``float`` instances, or the + ``True``, ``False``, or ``None`` constants. When the value provided is an + ``str`` instance, some limited automatic type conversion is performed, which + allows values of specific types to be set easily from configuration files or command-line options. So in addition to their actual values, the ``True``, ``False``, and ``None`` @@ -89,11 +89,15 @@ class Env(object): 'false' If an ``str`` value looks like an integer, it's automatically converted to - the ``int`` type. For example: + the ``int`` type. Likewise, if an ``str`` value looks like a floating-point + number, it's automatically converted to the ``float`` type. For example: >>> env.lucky = '7' >>> env.lucky 7 + >>> env.three_halves = '1.5' + >>> env.three_halves + 1.5 Leading and trailing white-space is automatically stripped from ``str`` values. For example: @@ -232,7 +236,12 @@ class Env(object): value = m[value] elif value.isdigit(): value = int(value) - assert type(value) in (str, int, bool, NoneType) + else: + try: + value = float(value) + except (TypeError, ValueError): + pass + assert type(value) in (str, int, float, bool, NoneType) object.__setattr__(self, key, value) self.__d[key] = value -- cgit From e9be796950070790543ca0cfaf5182958aee5dd6 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Tue, 30 Dec 2008 15:14:33 -0700 Subject: Fixed Env._bootstrap() docstring typo --- ipalib/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ipalib/config.py') diff --git a/ipalib/config.py b/ipalib/config.py index c1947433..6feae573 100644 --- a/ipalib/config.py +++ b/ipalib/config.py @@ -392,7 +392,7 @@ class Env(object): variables specified on the command-line. 3. Intelligently fill-in the *in_tree*, *context*, *conf*, and - *conf_default* variables if they haven`t been set already. + *conf_default* variables if they haven't been set already. Also see `Env._finalize_core()`, the next method in the bootstrap sequence. -- cgit From 7be459af0b52753ccde74e6833d664341935973d Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Fri, 2 Jan 2009 01:14:37 -0700 Subject: Added a bit to config.Env docstring about that variable names must pass check_name() function --- ipalib/config.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'ipalib/config.py') diff --git a/ipalib/config.py b/ipalib/config.py index 6feae573..04f1442f 100644 --- a/ipalib/config.py +++ b/ipalib/config.py @@ -65,6 +65,20 @@ class Env(object): >>> env.item # Also retrieve as an attribute 'I was set as a dictionary item.' + The variable names must be valid lower-case Python identifiers that neither + start nor end with an underscore. If your variable name doesn't meet these + criteria, a ``ValueError`` will be raised when you try to set the variable + (compliments of the `base.check_name()` function). For example: + + >>> env.BadName = 'Wont work as an attribute' + Traceback (most recent call last): + ... + ValueError: name must match '^[a-z][_a-z0-9]*[a-z0-9]$'; got 'BadName' + >>> env['BadName'] = 'Also wont work as a dictionary item' + Traceback (most recent call last): + ... + ValueError: name must match '^[a-z][_a-z0-9]*[a-z0-9]$'; got 'BadName' + The variable values can be ``str``, ``int``, or ``float`` instances, or the ``True``, ``False``, or ``None`` constants. When the value provided is an ``str`` instance, some limited automatic type conversion is performed, which -- cgit From 4a24b49d5d1df0558242c4a8e7e3f9333fc007db Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Mon, 5 Jan 2009 03:28:27 -0700 Subject: A few docstring improvements in Env --- ipalib/config.py | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) (limited to 'ipalib/config.py') diff --git a/ipalib/config.py b/ipalib/config.py index 04f1442f..3544331d 100644 --- a/ipalib/config.py +++ b/ipalib/config.py @@ -98,8 +98,8 @@ class Env(object): Note that the automatic type conversion is case sensitive. For example: - >>> env.false = 'false' # Not equal to repr(False)! - >>> env.false + >>> env.not_false = 'false' # Not equal to repr(False)! + >>> env.not_false 'false' If an ``str`` value looks like an integer, it's automatically converted to @@ -122,8 +122,8 @@ class Env(object): >>> env.number = ' 42 ' # Still converted to an int >>> env.number 42 - >>> env.actually_false = ' False ' # Still equal to repr(False) - >>> env.actually_false + >>> env.false = ' False ' # Still equal to repr(False) + >>> env.false False Also, empty ``str`` instances are converted to ``None``. For example: @@ -181,17 +181,18 @@ class Env(object): 2. `Env._finalize_core()` - merge-in variables from the configuration files and then merge-in variables from the internal defaults, after which at least all the standard variables will be set. After this - method is called, the plugins will be loaded, during which 3rd-party - plugins can set additional variables they may need. + method is called, the plugins will be loaded, during which + third-party plugins can merge-in defaults for additional variables + they use (likely using the `Env._merge()` method). 3. `Env._finalize()` - one last chance to merge-in variables and then the instance is locked. After this method is called, no more environment variables can be set during the remaining life of the process. - However, normally none of the above methods are called directly and instead - only `plugable.API.bootstrap()` is called, which itself takes care of - correctly calling the `Env` bootstrapping methods. + However, normally none of these three bootstraping methods are called + directly and instead only `plugable.API.bootstrap()` is called, which itself + takes care of correctly calling the `Env` bootstrapping methods. """ __locked = False @@ -267,7 +268,16 @@ class Env(object): def __delattr__(self, name): """ - Raise AttributeError (deletion is never allowed). + Raise an ``AttributeError`` (deletion is never allowed). + + For example: + + >>> env = Env() + >>> env.name = 'A value' + >>> del env.name + Traceback (most recent call last): + ... + AttributeError: locked: cannot delete Env.name """ raise AttributeError( DEL_ERROR % (self.__class__.__name__, name) -- cgit