Re: Repost: Suggestion - builtin module 'settings' and usage

Ken Manheimer (ken.manheimer@NIST.GOV)
Fri, 29 Jul 1994 00:21:19 -0400 (EDT)

I like the idea of a functional interface, and have whipped up
'custom.py' to take care of a number of concerns.

(1) It enables users to set (custom.set()) values for variables before
or after the module that uses the var is loaded and before or after
the var is registered (custom.register()).
(2) The set (and, sometimes, register) function will actually make the
value assignment when the module is loaded and the variable is
registered in the customization system.
(3) A docstring and default value, that may be distinct from the set
value, can be associated with the var using custom.register(), and
examined using custom.describe().
(4) A 'changefunc' can be associated with the var (via custom.register(),
that is invoked every time custom.set() (or custom.register())
causes the variable value to be assigned. This allows the package
designer to designate special accomodations for package
customization changes, eg, immediate behavior adjustments changes
on window size changes.

I have only tested this a bit, sort of as a 'proof-of-concept'. The
test function is at the bottom of the module.

(Incidentally, i enthusiastically included some 'access' controls,
only to discover that module access restrictions cause reloads of the
module to fail, if the module itself tries to redefine protected vars
or funcs, etc. See the commented out access statments if you want to
reproduce the problems.)

Here 'tis.

# Customization-variable-registry and -implementation functional interface.
# Ken.Manheimer@nist.gov, 28-Jul-1994

# Usage:
#
# User customizations:
# Use custom.set() to dictate variable settings that override package-defined
# customizable settings, whether the packages make the definitions before or
# after. custom.set() will actually set the value in the module, *if* the
# variable has been registered and the module is already loaded, or it will
# cause that value to be used when the module is loaded and the variable is
# registered.
#
# Package setup:
# Use custom.register() to define default value and docstring for variables,
# plus an optional changefunc to be executed when the registration or a
# subsequent custom.set() actually imposes the variable value in the module.
# Doing a custom.set(modname, varname) (without specifying a varvalue) after
# custom.register() will force establishment of a prior custom.set() value, or
# of the registered default if no custom.set() value has been established.
#
# Assessment:
# Use custom.describe() to obtain the registered default or set value for a
# variable, as well its docstring and changefunc, if any.
#
# Deletion:
# Finally, use custom.delete() to remove registrations for a variable, for one
# reason or another.
#
# Testing:
# See the rudimentary test function is at the bottom of this module. See it
# run. Run test function, run.

# Public functions:

# custom.register(packagename, varname,
# defaultval=None, docstring='', changefunc=None)
# custom.set(packagename, varname, newval=unique)
# custom.delete(packagename, varname)
# custom.describe(packagename, varname=None)

# custom.register() Used by packages to register publicly customizable
# variables. It establishes a default value, docstring,
# and optional var-setting function, to be called when
# custom.set or this function sets the actual value for the
# var. (This function only sets the actual value if no
# prior set or register has done so, and the module is
# loaded.)
#
# custom.set() change or invoke the current setting of a var, overriding prior
# and subsequent register settings. This is for the user to
# override package defaults, and for the package to force the
# actual module setting (and change func, if any).
#
# custom.describe() returns a description tuple for a varname, if specified,
# or a list of keys associated with packagename, if varname
# is not specified. The var description tuple consists of:
# - the varname (redundant, but what the hell);
# - a list containing the registered default, or empty list
# if none;
# - a list containing the custom.set value, or empty list if
# none;
# - the docstring
# - and the changefunc (or None, if none).
#
# changfuncs - designated functions that are automatically applied when (1) the
# specific package is already loaded, and (2) the variable with
# which the function is associated is changed via custom.set().
# This hook-function is used to take care of whatever needs be
# done to accomodate changes of the var's value. Note that the
# actual assignment in the package will *not* be done if the
# changefunc returns a null value (None, [], {}, 0, ...).

# Developers note - i haven't done anything about loading up a
# $HOME/.custom.py, though i think that's a good idea (tm).
# Developers note - session startup should 'import custom' before loading other
# modules.
# Developers note - We may want to have some analogue to emacs 'loaddefs.el',
# which gathers together standard modules' registrations, so
# they're available for examination by users (via
# custom.describe()) without having to load the vars'
# respective modules.

import sys

error = 'CustomError'

try:
#access unique: protected # access still buggy
unique = 'unique'
except AccessError:
pass

try:
#access packages: protected # access still buggy
packages
except NameError:
packages = {} # Only reinit if not already existent.
except AccessError:
pass

def register(pkgnm, varnm, defval=unique, docstr='', chngfunc=None):
# Register or change attributes for varnm in pkgnm:
# - default val, for assignment only when not overriden by setval
# - docstring, describing var
# - chngfunc, invoked when new setting for var is effected
curCV = registration(pkgnm, varnm)
if not curCV:
if type(curCV) == type({}):
packages[pkgnm] = {}
curCV = CustomVar(defval, docstr, chngfunc)
packages[pkgnm][varnm] = curCV # record in registry
if id(defval) != id(unique):
impose(curCV, pkgnm, varnm, defval) # impose in module
return pkgnm
else:
# only 'set' should pass in unique - it will need the val obj:
return curCV
else: # Already registered
prevDefVal = curCV.getDefaultVal()
curCV.setDefaultVal(defval)
curCV.setDoc(docstr)
curCV.setChngfunc(chngfunc)
# Impose if no established setval:
if not prefDefVal or (not curCV.getSetVal()):
# no prior defval, or no existing setval - impose defval:
impose(curCV, pkgnm, varnm, defval)
return varnm

def set(pkgnm, varnm, val=unique):
# Establish setval attribbute of var registration, and impose val as the
# actual value for varnm in pkgnm, if it is registered, overriding any
# prior or subsequent default settings. If val is unspecified, establish
# registered setval or default val.
curCV = registration(pkgnm, varnm)
if not curCV:
if id(val) == id(unique):
# oops - no registered value, no specified val
raise error, 'Set of no registered or specified val'
# pass unique obj so no default is registered, and we get back the var
# container:
curCV = register(pkgnm, varnm, unique)
elif id(val) == id(unique):
# Use registered set or default val
val = curCV.getSetVal()
if val: val = val[0]
else:
val = curCV.getDefaultVal()
if val: val = val[0]
else: raise error, ('Set of var that lacks specified, set, ' +
'or default val')
impose(curCV, pkgnm, varnm, val)

def delete(pkgnm, varnm):
# Entirely remove registration for var, returning 1 if it existed and 0 if
# not.
curCV = registration(pkgnm, varnm)
if curCV:
del packages[pkgnm][varnm]
return 1
else: return 0

def describe(pkgnm, varnm=None):
# Return a description tuple for a varname, if varname specified,
# or a list of keys associated with packagename, if varname not specified.
# Var description tuple consists of:
# - the pkgname (redundant, but what the hell);
# - the varname (ditto);
# - a tuple containing the registered default, or empty tuple if none;
# - a tuple containing the custom.set value, or empty tuple if none;
# - the docstring
# - and the changefunc, or None, if none.
curCV = registration(pkgnm, varnm)
if not curCV:
if varnm == None:
return None
elif type(curCV) == type({}):
return {}
else: return ()
elif varnm == None:
return curCV.keys()
else:
return (pkgnm, varnm,
curCV.getDefaultVal(), curCV.getSetVal(),
curCV.getDoc(), curCV.getChngfunc())

#access impose: protected # access still buggy
def impose(CV, pkgnm, varnm, val):
# - apply var's changefunc, if any;
# - actually assign the value to the variable;
# - if the package is loaded as a module and changefunc didn't return null,
# make the indicated assignment within the context of the module;
# - register the new value under the 'setval' attribute of the var object;
# - return whether or not the actual (vs registered) assignment was made.
chngfunc = CV.getChngfunc()
doAssign = 1
if chngfunc:
try:
# try to apply the chngfunc - the chngfunc has to watch out for
# non-existent module:
doAssign = apply(chngfunc, (pkgnm, varnm, val))
except:
raise error, ('Failed application of %s/%s chngfunc' %
(pkgnm, varnm))
if doAssign and sys.modules.has_key(pkgnm):
# Assign the new value in the context of the package:
sys.modules[pkgnm].__dict__[varnm] = val
CV.setSetVal(val)
return doAssign

#access registration: protected # access still buggy
def registration(pkgnm, varnm = None):
# if no var specified, returns package registrations dict, if
# any registered, or {} if none.
# if var specified, returns var object if var is registered in pkg,
# or {} if no registration for pkg,
# or () if var not registered among pkg registrations.
if not (packages.has_key(pkgnm)):
return {}
pkgDict = packages[pkgnm]
if not varnm:
return pkgDict
if not pkgDict.has_key(varnm):
return None
else:
return pkgDict[varnm]

class CustomVar:

#access default, set, doc, chngfunc: protected # access still buggy

defaultVal = ()
setVal = ()
doc = ''
chngfunc = None

def __init__(self, defval, docstr, chngfunc):
if id(defval) != id(unique):
# unique passed in for defval by 'set's of unregistered vars.
self.setDefaultVal(defval)
self.setDoc(docstr)
self.setChngfunc(chngfunc)
return

def setDefaultVal(self, val):
# Set the default in a tuple, so we can tell that there's a setting.
self.defaultVal = (val,)
return val
def getDefaultVal(self):
# Return a tuple containing current default value, or None.
if self.defaultVal: return self.defaultVal
else: return None
def setSetVal(self, val):
# Set the set-val in a tuple, so we can tell that there's a setting.
self.setVal = (val,)
return val
def getSetVal(self):
# Return a tuple containing current default value, or None.
if self.setVal: return self.setVal
else: return None
def setDoc(self, doc):
self.doc = doc
def getDoc(self):
return self.doc
def setChngfunc(self, func):
self.chngfunc = func
def getChngfunc(self):
return self.chngfunc
def clearChngfunc(self):
self.chngfunc = None

def prArgs(pkg, var, val):
# For exercising changefunc, assign to var in pkg.
print '(pgk: %s, var: %s, val: %s)' % (pkg, var, val)
return 1

def test():
# Test basic features, completing only if no errors.
# Very cursory testing - error conditions are not exercised, etc.

import custom # custom will be our target

try: del custom.dummy # clear out any residual dummy
except: pass

custom.delete('custom', 'dummy')

dummyVal = 'dummy val'
description = 'dummy description'

print ' - Changefunc should show a parenthesized message showing values:'
custom.register('custom', 'dummy', dummyVal, description, prArgs)

try: # Did it get set in module?
got = custom.dummy
except AttributeError:
raise error, 'registration failed to do actual assignment'
if got == dummyVal:
print ' - registration revised actual module value'
else:
raise error, 'failed module assignment - got %s' % got

print ' - changefunc should show the message again:'
newVal = 'new val for dummy'
custom.set('custom', 'dummy', newVal)
if custom.dummy == newVal:
print ' - set works'
else:
raise error, 'failed get - got %s' % custom.dummy

del custom.dummy

# Now try backwards, with non-existent module.
custom.set('nomod', 'novar', 'oy')

descr = custom.describe('nomod', 'novar')
if (descr[3][0] == 'oy'):
print ' - descr looks good'
else:
raise error, ('Descr not right - "%s" should be "%s"' %
(descr[3][0], 'oy'))

custom.register('nomod', 'novar', None, 'some default')
custom.set('nomod', 'novar', 'yop')
descr = custom.describe('nomod', 'novar')
if (descr[3][0] == 'yop'):
print ' - register and set in bogus module yields fine descr'
else:
raise error, ('Descr not right - "%s" should be "%s"' %
(descr[3][0], 'some default'))

print 'done!'