configurable.py
139 lines
| 5.7 KiB
| text/x-python
|
PythonLexer
Brian Granger
|
r2731 | #!/usr/bin/env python | ||
# encoding: utf-8 | ||||
""" | ||||
A base class for objects that are configurable. | ||||
Authors: | ||||
* Brian Granger | ||||
* Fernando Perez | ||||
""" | ||||
#----------------------------------------------------------------------------- | ||||
# Copyright (C) 2008-2010 The IPython Development Team | ||||
# | ||||
# Distributed under the terms of the BSD License. The full license is in | ||||
# the file COPYING, distributed as part of this software. | ||||
#----------------------------------------------------------------------------- | ||||
#----------------------------------------------------------------------------- | ||||
# Imports | ||||
#----------------------------------------------------------------------------- | ||||
from copy import deepcopy | ||||
import datetime | ||||
from weakref import WeakValueDictionary | ||||
from IPython.utils.importstring import import_item | ||||
from loader import Config | ||||
from IPython.utils.traitlets import HasTraits, Instance | ||||
#----------------------------------------------------------------------------- | ||||
# Helper classes for Configurables | ||||
#----------------------------------------------------------------------------- | ||||
class ConfigurableError(Exception): | ||||
pass | ||||
#----------------------------------------------------------------------------- | ||||
# Configurable implementation | ||||
#----------------------------------------------------------------------------- | ||||
class Configurable(HasTraits): | ||||
config = Instance(Config,(),{}) | ||||
created = None | ||||
Brian Granger
|
r2740 | def __init__(self, **kwargs): | ||
Brian Granger
|
r2731 | """Create a conigurable given a config config. | ||
Parameters | ||||
---------- | ||||
config : Config | ||||
If this is empty, default values are used. If config is a | ||||
:class:`Config` instance, it will be used to configure the | ||||
instance. | ||||
Notes | ||||
----- | ||||
Subclasses of Configurable must call the :meth:`__init__` method of | ||||
:class:`Configurable` *before* doing anything else and using | ||||
:func:`super`:: | ||||
class MyConfigurable(Configurable): | ||||
def __init__(self, config=None): | ||||
super(MyConfigurable, self).__init__(config) | ||||
# Then any other code you need to finish initialization. | ||||
This ensures that instances will be configured properly. | ||||
""" | ||||
Brian Granger
|
r2740 | config = kwargs.pop('config', None) | ||
Brian Granger
|
r2731 | if config is not None: | ||
# We used to deepcopy, but for now we are trying to just save | ||||
# by reference. This *could* have side effects as all components | ||||
# will share config. In fact, I did find such a side effect in | ||||
# _config_changed below. If a config attribute value was a mutable type | ||||
# all instances of a component were getting the same copy, effectively | ||||
# making that a class attribute. | ||||
# self.config = deepcopy(config) | ||||
self.config = config | ||||
Brian Granger
|
r2740 | # This should go second so individual keyword arguments override | ||
# the values in config. | ||||
super(Configurable, self).__init__(**kwargs) | ||||
Brian Granger
|
r2731 | self.created = datetime.datetime.now() | ||
#------------------------------------------------------------------------- | ||||
# Static trait notifiations | ||||
#------------------------------------------------------------------------- | ||||
def _config_changed(self, name, old, new): | ||||
"""Update all the class traits having ``config=True`` as metadata. | ||||
For any class trait with a ``config`` metadata attribute that is | ||||
``True``, we update the trait with the value of the corresponding | ||||
config entry. | ||||
""" | ||||
# Get all traits with a config metadata entry that is True | ||||
traits = self.traits(config=True) | ||||
# We auto-load config section for this class as well as any parent | ||||
# classes that are Configurable subclasses. This starts with Configurable | ||||
# and works down the mro loading the config for each section. | ||||
section_names = [cls.__name__ for cls in \ | ||||
reversed(self.__class__.__mro__) if | ||||
issubclass(cls, Configurable) and issubclass(self.__class__, cls)] | ||||
for sname in section_names: | ||||
# Don't do a blind getattr as that would cause the config to | ||||
# dynamically create the section with name self.__class__.__name__. | ||||
if new._has_section(sname): | ||||
my_config = new[sname] | ||||
for k, v in traits.items(): | ||||
# Don't allow traitlets with config=True to start with | ||||
# uppercase. Otherwise, they are confused with Config | ||||
# subsections. But, developers shouldn't have uppercase | ||||
# attributes anyways! (PEP 6) | ||||
if k[0].upper()==k[0] and not k.startswith('_'): | ||||
raise ConfigurableError('Configurable traitlets with ' | ||||
'config=True must start with a lowercase so they are ' | ||||
'not confused with Config subsections: %s.%s' % \ | ||||
(self.__class__.__name__, k)) | ||||
try: | ||||
# Here we grab the value from the config | ||||
# If k has the naming convention of a config | ||||
# section, it will be auto created. | ||||
config_value = my_config[k] | ||||
except KeyError: | ||||
pass | ||||
else: | ||||
# print "Setting %s.%s from %s.%s=%r" % \ | ||||
# (self.__class__.__name__,k,sname,k,config_value) | ||||
# We have to do a deepcopy here if we don't deepcopy the entire | ||||
# config object. If we don't, a mutable config_value will be | ||||
# shared by all instances, effectively making it a class attribute. | ||||
setattr(self, k, deepcopy(config_value)) | ||||