Package park :: Package util :: Module configure

Source Code for Module park.util.configure

  1  # This program is public domain 
  2  """ 
  3  Simple configuration system. 
  4   
  5  Allow applications and libraries to record configuration keys which can 
  6  be set by the user in the configuration file before startup, or specified 
  7  on the command line. 
  8   
  9  When running a program there are multiple sources of information that 
 10  need to be considered.  When developing the code the programmer will 
 11  usually supply default values which the user can override. 
 12   
 13  The configuration file is the traditional method for storing this information. 
 14  Historically these were sets of key:value pairs, but this has evolved over 
 15  the years to Windows [section]key:value in the .ini format.  The python 
 16  library ConfigParser understands the .ini format.  More recent systems have 
 17  moved to a hierarchical key scheme with typed values. 
 18   
 19  Generally applications have system configuration shared by all users and 
 20  specific configuration for the individual users. Information shared from 
 21  run to run, such as recent activities also needs to be considered. 
 22   
 23  On unix configuration is stored in ad hoc text files:: 
 24   
 25    /etc/apprc or /etc/app/apprc for system configuration 
 26    ~/.apprc or ~/.app/apprc for user configuration 
 27   
 28  On OS X configuration info is stored in xml-based plist files:: 
 29   
 30    /Library/Preferences/com.company.app.plist for system configuration 
 31    ~/Library/Preferences/com.company.app.plist for user configuration 
 32   
 33  On Windows configuration is stored in registry keys:: 
 34   
 35    HKEY_LOCAL_MACHINE\Software\company\app for system configuration 
 36    HKEY_CURRENT_USER\Software\company\app for user configuration 
 37   
 38  All three systems support environment variables, which can be used to 
 39  affect the runtime configuration.  While not used much for new applications, 
 40  existing packages such as mysql may require the user to set the environment. 
 41  On OS X, environment variables are set in ~/.MacOSX/environment.plist. 
 42  See <http://developer.apple.com/qa/qa2001/qa1067.html> for details. 
 43  On Windows environment variables are set in >My Computer >Properties 
 44  >Advanced >Environment variables.  On unix environment variables are 
 45  usually set in /etc/profile and ~/.profile for Bourne shell and derivatives. 
 46  Some config variables may be initialized from the environment. 
 47   
 48  Command-line flags are still common, particularly for backend software. 
 49  Individual configuration options can be set on the command line using 
 50  --key=value.  These values take precedence over any other configuration 
 51  information.  Shortcut flags, such as -v for --park.logging.verbose=True 
 52  can be configured on an application by application basis. 
 53   
 54  Note: libraries need to control their own configuration.  The user does 
 55  not want to configure park separately for every app that uses it.  Conversely, 
 56  some apps may want specialized park configuration parameters.  We can 
 57  use 'CCS' (Cascading Configuration Sheets) to handle this, so long as 
 58  libraries and apps use a unified configuration environment, with config 
 59  keys using package namespace conventions. 
 60   
 61  Note: we are not trying to solve the problem of connecting and configuring 
 62  components in an enterprise framework.  For tasks like this you will be 
 63  much better served by instantiating the components yourself from a Python 
 64  script, where you have direct access to objects and their attributes (or at 
 65  least to object proxies).  This system is better suited to setting values 
 66  which don't change much from run to run. 
 67   
 68  Example 
 69  ======= 
 70   
 71  First define the contents of the registry for the package.  For documentation 
 72  purposes this is probably best done in a separate file config.py at the 
 73  root of your package directory.  That way you can easily point your 
 74  users to the list of all registry keys that your application cares about. 
 75  If your application is plugin-based, each plugin can have its own 
 76  registry entries. 
 77   
 78  To use the configured values, simply reference them from config.  Note that 
 79  config values cannot be used at the module level since the config file may 
 80  not be loaded at the time the module is imported, and since the config value 
 81  may change while the program is running.  Similarly, config values should 
 82  not be used as defaults values for keyword parameters since these are 
 83  usually evaluated at load time. 
 84   
 85  For cascading configs, the package should trigger loading of the config 
 86  file when the module is first loaded.  The application should be sure to 
 87  import all packages before loading its own configuration file.  That 
 88  way, if the user overrides some package defaults for the particular 
 89  application (e.g., the choice of plotting package), it will not affect 
 90  other applications which use the same library. 
 91   
 92  mylib/configure.py:: 
 93      ''' 
 94      Package configuration parameters. 
 95      ''' 
 96      __all__ = [] 
 97      from park.util.configure import registry 
 98      registry.int('mylib.key','5',tip='number of keys') 
 99      ... 
100   
101      # Finally, load the configuration file from disk, if there is a 
102      # package specific configuration file. 
103      registry.load('mylib.danse.us') 
104   
105  mylib/module.py:: 
106      from park.util.configure import config 
107      ... 
108      def myfun(self, key=None): 
109          # Grab the current default value of the key from config if it 
110          # is not passed as a parameter. 
111          if not key: key = config.mylib.key.value 
112      @config.mylib.key.on_modify 
113      def update_key(self, old=None, new=None, key=None): 
114          # Track configuration updates 
115          print key,"modified from",old,"to",new 
116   
117  mylib/__init__.py file:: 
118   
119      ... 
120      import .configure 
121      ... 
122   
123   
124  myapp/configure.py:: 
125      ''' 
126      Application configuration parameters. 
127      ''' 
128      __all__ = [] 
129      from park.util.configure import registry 
130      registry.int('myapp.key','5',tip='number of keys') 
131      ... 
132   
133      # Finally, load the configuration file from disk, if there is a 
134      # package specific configuration file. 
135      registry.load('myapp.danse.us') 
136   
137   
138  myapp/myapp.py:: 
139   
140   
141      import mylib      # Force load of package registry first 
142      import configure  # Force load of application registry last 
143   
144      configure.registry.check()  #  Check the configuration values 
145   
146  """ 
147   
148  # TODO: provide a gui for editing properties 
149  # ... or just use Traits 
150  import os 
151   
152  from park.util.host import Host # Host parsing 
153  from park.util import magnitude # dimensional value parsing 
154  # TODO: add dated values as in the reduction code 
155   
156 -class Entry(object):
157 - def __init__(self, key, value, parser=None, **kw):
158 self.meta = kw 159 self.key = key 160 self.parser = parser 161 self.__value = self.parse(value) 162 self.__save = [] 163 self.__callstack = []
164 - def parse(self, value):
165 try: 166 return self.parser(value) 167 except Exception,err: 168 raise ValueError("When parsing %s='%s', %s"%(self.key,value,err))
169 - def _set_value(self, value):
170 old = self.__value 171 self.__value = self.parse(value) 172 if old == new: return 173 for fn in self.__callstack: 174 fn(key=self.key, old=old, new=new)
175 - def _get_value(self):
176 return self.__value
177 value = property(_get_value, _set_value)
178 - def __str__(self):
179 return "%s:%s"%(self.key, str(self.__value))
180 - def __enter__(self, value):
181 self.__save.push(self.__value) 182 self.__value = value
183 - def __exit__(self):
184 self.__value = self.__save.pop()
185 - def on_modify(self, fn):
186 """ 187 Register fn to be called when the config value is modified. 188 """ 189 self.__callstack.push(fn) 190 return fn
191
192 -class Node(object):
193 - def __contains__(self, key):
194 return key in self.__dict__
195 - def __getitem__(self, key):
196 return self.__dict__[key]
197 - def __setitem__(self, key, node):
198 self.__dict__[key] = node
199 - def __delitem__(self, key):
200 del self.__dict__[key]
201
202 -class Registry(object):
203 - def __init__(self, config):
204 #self.__date = time.gmtime() # For dated values 205 self.config = config
206
207 - def add(self, key, value, parser=None, **kw):
208 """ 209 Add an entry into the registry. 210 211 key is a dotted name forming a hierarchical namespace 212 value is the default 213 If 'after' is given, the configured value only applies after 214 """ 215 if parser is None: raise TypeError('parser not specified for '+key) 216 217 # Make branches 218 parts = key.split('.') 219 node = self.config 220 for sub in parts[:-1]: 221 if not sub in node: node[sub] = Node() 222 node = node[sub] 223 node[parts[-1]] = Entry(key, value, parser=parser, **kw)
224
225 - def load(self, key):
226 """ 227 Parse the config files, setting the defaults. 228 229 Key should be 'app.org.blah' where app is the name of the 230 application, org is the name of the organization and app.org.blah 231 is your primary application URL. 232 233 Configuration will be loaded from /etc/org/apprc and ~/.org/apprc 234 """ 235 # TODO: use plistlib on os x 236 # TODO: use winreg on Windows 237 parts = key.split('.') 238 app,org = parts[:2] 239 system = os.path.join('etc', org, app+'rc') 240 user = os.path.join(os.environ['HOME'], '.'+org, app+'rc') 241 for filename in [system, user]: 242 config = loadconfig(filename) 243 for key,value in config.items(): 244 self.set(key, value)
245
246 - def save(self, key):
247 """ 248 Save current settings to a config file. 249 """ 250 # TODO: preserve comments and spacing from original when overwriting 251 parts = key.split('.') 252 app,org = parts[:2] 253 system = os.path.join('etc', org, app+'rc') 254 user = os.path.join(os.environ['HOME'], '.'+org, app+'rc') 255 256 file = open(user,'wt') 257 keys = self.__items.keys() 258 keys.sort() 259 for k in keys: 260 entry = self.__items[k] 261 file.write('# '+entry.meta['tip']) 262 file.write(entry.key + ':' + str(entry.value))
263 264 # Built-in types
265 - def host(self, key, value, **kw):
266 """ 267 Specify user:password@host:port 268 """ 269 self.add(key, value, parser=Host, **kw)
270
271 - def url(self, key, value, *kw):
272 """ 273 Specify a URL. Returns a urlparse.urlparse object. 274 """ 275 import urlparse 276 register(key, value, parser=urlparse.urlparse, **kw)
277
278 - def set(self, key, value, **kw):
279 """ 280 Parse a set of values separated by spaces. 281 """ 282 self.add(key, value, parser=_parse_set, **kw)
283
284 - def str(self, key, value, **kw):
285 """ 286 Parse a string. 287 """ 288 self.add(key, value, parser=_parse_str, **kw)
289
290 - def int(self, key, value, **kw):
291 """ 292 Parse an integer. 293 """ 294 self.add(key, value, parser=int, **kw)
295
296 - def item(self, key, value, choices=None, **kw):
297 """ 298 Item chosen from a closed set. 299 """ 300 self.add(key, value, parser=Choice(choices), **kw)
301
302 - def list(self, key, value, **kw):
303 """ 304 Parse a list. 305 """ 306 self.add(key, value, parser=_parse_list, **kw)
307
308 - def expr(self, key, value, **kw):
309 """ 310 Parse a value which may be an expression. 311 """ 312 self.add(key, value, parser=park.util.math.eval, **kw)
313
314 - def units(self, key, value, dim=None, **kw):
315 """ 316 Parse a value with units. 317 """ 318 self.add(key, value, parser=Units(dim), **kw)
319 320 # Singleton global config registry. Use registry to add items to config. 321 config = Node() 322 registry = Registry(config) 323
324 -def loadconfig(filename):
325 content = {} 326 if not os.path.exists(filename): return content 327 fd = open(filename, 'rt') 328 for line in fd: 329 line = line.strip() 330 if line == '' or line.startswith('#'): continue 331 idx = line.index(':') 332 key = line[:idx] 333 value = line[idx+1:] 334 content[key] = value
335
336 -class Units(object):
337 """ 338 Retrieve value in specified units. 339 """
340 - def __init__(self, units):
341 """ 342 Units are the target units for the value. 343 """ 344 self.units = units 345 self.zero = magnitude.mg(0,units)
346 - def __call__(self, value):
347 """ 348 Convert the given string 'val units' into a value in the target units. 349 """ 350 parts = value.split() 351 number = float(parts[0]) 352 units = " ".join(parts[1:]) 353 mag = magnitude.mg(number,units) 354 self.zero + mag # Check units, raising error if non-conformant 355 return mag.toval(self.units)
356
357 -class Choice(object):
358 - def __init__(self, choices):
359 self.choices = choices
360 - def __call__(self, value):
361 if not value in self.choices: 362 raise ValueError("Item not in choices") 363 return value
364
365 -def _parse_set(value):
366 return __builtins__.set(value.split())
367
368 -def _parse_str(value):
369 return value.strip()
370