Skip to content

Commit 1e1fe08

Browse files
committed
First draft of refactored Component->Configurable.
1 parent ce832fe commit 1e1fe08

21 files changed

+503
-836
lines changed

IPython/config/configurable.py

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
#!/usr/bin/env python
2+
# encoding: utf-8
3+
"""
4+
A base class for objects that are configurable.
5+
6+
Authors:
7+
8+
* Brian Granger
9+
* Fernando Perez
10+
"""
11+
12+
#-----------------------------------------------------------------------------
13+
# Copyright (C) 2008-2010 The IPython Development Team
14+
#
15+
# Distributed under the terms of the BSD License. The full license is in
16+
# the file COPYING, distributed as part of this software.
17+
#-----------------------------------------------------------------------------
18+
19+
#-----------------------------------------------------------------------------
20+
# Imports
21+
#-----------------------------------------------------------------------------
22+
23+
from copy import deepcopy
24+
import datetime
25+
from weakref import WeakValueDictionary
26+
27+
from IPython.utils.importstring import import_item
28+
from loader import Config
29+
from IPython.utils.traitlets import HasTraits, Instance
30+
31+
32+
#-----------------------------------------------------------------------------
33+
# Helper classes for Configurables
34+
#-----------------------------------------------------------------------------
35+
36+
37+
class ConfigurableError(Exception):
38+
pass
39+
40+
41+
#-----------------------------------------------------------------------------
42+
# Configurable implementation
43+
#-----------------------------------------------------------------------------
44+
45+
46+
class Configurable(HasTraits):
47+
48+
config = Instance(Config,(),{})
49+
created = None
50+
51+
def __init__(self, config=None):
52+
"""Create a conigurable given a config config.
53+
54+
Parameters
55+
----------
56+
config : Config
57+
If this is empty, default values are used. If config is a
58+
:class:`Config` instance, it will be used to configure the
59+
instance.
60+
61+
Notes
62+
-----
63+
Subclasses of Configurable must call the :meth:`__init__` method of
64+
:class:`Configurable` *before* doing anything else and using
65+
:func:`super`::
66+
67+
class MyConfigurable(Configurable):
68+
def __init__(self, config=None):
69+
super(MyConfigurable, self).__init__(config)
70+
# Then any other code you need to finish initialization.
71+
72+
This ensures that instances will be configured properly.
73+
"""
74+
super(Configurable, self).__init__()
75+
if config is not None:
76+
# We used to deepcopy, but for now we are trying to just save
77+
# by reference. This *could* have side effects as all components
78+
# will share config. In fact, I did find such a side effect in
79+
# _config_changed below. If a config attribute value was a mutable type
80+
# all instances of a component were getting the same copy, effectively
81+
# making that a class attribute.
82+
# self.config = deepcopy(config)
83+
self.config = config
84+
self.created = datetime.datetime.now()
85+
86+
#-------------------------------------------------------------------------
87+
# Static trait notifiations
88+
#-------------------------------------------------------------------------
89+
90+
def _config_changed(self, name, old, new):
91+
"""Update all the class traits having ``config=True`` as metadata.
92+
93+
For any class trait with a ``config`` metadata attribute that is
94+
``True``, we update the trait with the value of the corresponding
95+
config entry.
96+
"""
97+
# Get all traits with a config metadata entry that is True
98+
traits = self.traits(config=True)
99+
100+
# We auto-load config section for this class as well as any parent
101+
# classes that are Configurable subclasses. This starts with Configurable
102+
# and works down the mro loading the config for each section.
103+
section_names = [cls.__name__ for cls in \
104+
reversed(self.__class__.__mro__) if
105+
issubclass(cls, Configurable) and issubclass(self.__class__, cls)]
106+
107+
for sname in section_names:
108+
# Don't do a blind getattr as that would cause the config to
109+
# dynamically create the section with name self.__class__.__name__.
110+
if new._has_section(sname):
111+
my_config = new[sname]
112+
for k, v in traits.items():
113+
# Don't allow traitlets with config=True to start with
114+
# uppercase. Otherwise, they are confused with Config
115+
# subsections. But, developers shouldn't have uppercase
116+
# attributes anyways! (PEP 6)
117+
if k[0].upper()==k[0] and not k.startswith('_'):
118+
raise ConfigurableError('Configurable traitlets with '
119+
'config=True must start with a lowercase so they are '
120+
'not confused with Config subsections: %s.%s' % \
121+
(self.__class__.__name__, k))
122+
try:
123+
# Here we grab the value from the config
124+
# If k has the naming convention of a config
125+
# section, it will be auto created.
126+
config_value = my_config[k]
127+
except KeyError:
128+
pass
129+
else:
130+
# print "Setting %s.%s from %s.%s=%r" % \
131+
# (self.__class__.__name__,k,sname,k,config_value)
132+
# We have to do a deepcopy here if we don't deepcopy the entire
133+
# config object. If we don't, a mutable config_value will be
134+
# shared by all instances, effectively making it a class attribute.
135+
setattr(self, k, deepcopy(config_value))
136+
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
#!/usr/bin/env python
2+
# encoding: utf-8
3+
"""
4+
Tests for IPython.config.configurable
5+
6+
Authors:
7+
8+
* Brian Granger
9+
* Fernando Perez (design help)
10+
"""
11+
12+
#-----------------------------------------------------------------------------
13+
# Copyright (C) 2008-2010 The IPython Development Team
14+
#
15+
# Distributed under the terms of the BSD License. The full license is in
16+
# the file COPYING, distributed as part of this software.
17+
#-----------------------------------------------------------------------------
18+
19+
#-----------------------------------------------------------------------------
20+
# Imports
21+
#-----------------------------------------------------------------------------
22+
23+
from unittest import TestCase
24+
25+
from IPython.config.configurable import Configurable, ConfigurableError
26+
from IPython.utils.traitlets import (
27+
TraitError, Int, Float, Str
28+
)
29+
from IPython.config.loader import Config
30+
31+
32+
#-----------------------------------------------------------------------------
33+
# Test cases
34+
#-----------------------------------------------------------------------------
35+
36+
37+
class TestConfigurableConfig(TestCase):
38+
39+
def test_default(self):
40+
c1 = Configurable()
41+
c2 = Configurable(config=c1.config)
42+
c3 = Configurable(config=c2.config)
43+
self.assertEquals(c1.config, c2.config)
44+
self.assertEquals(c2.config, c3.config)
45+
46+
def test_custom(self):
47+
config = Config()
48+
config.foo = 'foo'
49+
config.bar = 'bar'
50+
c1 = Configurable(config=config)
51+
c2 = Configurable(c1.config)
52+
c3 = Configurable(c2.config)
53+
self.assertEquals(c1.config, config)
54+
self.assertEquals(c2.config, config)
55+
self.assertEquals(c3.config, config)
56+
# Test that copies are not made
57+
self.assert_(c1.config is config)
58+
self.assert_(c2.config is config)
59+
self.assert_(c3.config is config)
60+
self.assert_(c1.config is c2.config)
61+
self.assert_(c2.config is c3.config)
62+
63+
def test_inheritance(self):
64+
class MyConfigurable(Configurable):
65+
a = Int(1, config=True)
66+
b = Float(1.0, config=True)
67+
c = Str('no config')
68+
config = Config()
69+
config.MyConfigurable.a = 2
70+
config.MyConfigurable.b = 2.0
71+
c1 = MyConfigurable(config=config)
72+
c2 = MyConfigurable(c1.config)
73+
self.assertEquals(c1.a, config.MyConfigurable.a)
74+
self.assertEquals(c1.b, config.MyConfigurable.b)
75+
self.assertEquals(c2.a, config.MyConfigurable.a)
76+
self.assertEquals(c2.b, config.MyConfigurable.b)
77+
78+
def test_parent(self):
79+
class Foo(Configurable):
80+
a = Int(0, config=True)
81+
b = Str('nope', config=True)
82+
class Bar(Foo):
83+
b = Str('gotit', config=False)
84+
c = Float(config=True)
85+
config = Config()
86+
config.Foo.a = 10
87+
config.Foo.b = "wow"
88+
config.Bar.b = 'later'
89+
config.Bar.c = 100.0
90+
f = Foo(config=config)
91+
b = Bar(f.config)
92+
self.assertEquals(f.a, 10)
93+
self.assertEquals(f.b, 'wow')
94+
self.assertEquals(b.b, 'gotit')
95+
self.assertEquals(b.c, 100.0)

IPython/config/tests/test_imports.py

Lines changed: 0 additions & 5 deletions
This file was deleted.

IPython/core/alias.py

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
#!/usr/bin/env python
22
# encoding: utf-8
33
"""
4-
IPython's alias component
4+
System command aliases.
55
66
Authors:
77
8+
* Fernando Perez
89
* Brian Granger
910
"""
1011

@@ -25,10 +26,10 @@
2526
import re
2627
import sys
2728

28-
from IPython.core.component import Component
29+
from IPython.config.configurable import Configurable
2930
from IPython.core.splitinput import split_user_input
3031

31-
from IPython.utils.traitlets import List
32+
from IPython.utils.traitlets import List, Instance
3233
from IPython.utils.autoattr import auto_attr
3334
from IPython.utils.warn import warn, error
3435

@@ -99,22 +100,18 @@ class InvalidAliasError(AliasError):
99100
#-----------------------------------------------------------------------------
100101

101102

102-
class AliasManager(Component):
103+
class AliasManager(Configurable):
103104

104105
default_aliases = List(default_aliases(), config=True)
105106
user_aliases = List(default_value=[], config=True)
107+
shell = Instance('IPython.core.iplib.InteractiveShell')
106108

107-
def __init__(self, parent, config=None):
108-
super(AliasManager, self).__init__(parent, config=config)
109+
def __init__(self, shell, config=None):
110+
super(AliasManager, self).__init__(config=config)
109111
self.alias_table = {}
110112
self.exclude_aliases()
111113
self.init_aliases()
112-
113-
@auto_attr
114-
def shell(self):
115-
return Component.get_instances(
116-
root=self.root,
117-
klass='IPython.core.iplib.InteractiveShell')[0]
114+
self.shell = shell
118115

119116
def __contains__(self, name):
120117
if name in self.alias_table:

IPython/core/application.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
handling configuration and creating componenets.
77
88
The job of an :class:`Application` is to create the master configuration
9-
object and then create the components, passing the config to them.
9+
object and then create the configurable objects, passing the config to them.
1010
1111
Authors:
1212
@@ -76,7 +76,7 @@ def _add_arguments(self):
7676

7777

7878
class Application(object):
79-
"""Load a config, construct components and set them running.
79+
"""Load a config, construct configurables and set them running.
8080
8181
The configuration of an application can be done via three different Config
8282
objects, which are loaded and ultimately merged into a single one used
@@ -113,7 +113,7 @@ class Application(object):
113113
file_config = None
114114
#: Read from the system's command line flags.
115115
command_line_config = None
116-
#: The final config that will be passed to the component.
116+
#: The final config that will be passed to the main object.
117117
master_config = None
118118
#: A reference to the argv to be used (typically ends up being sys.argv[1:])
119119
argv = None
@@ -223,10 +223,10 @@ def create_default_config(self):
223223
"""Create defaults that can't be set elsewhere.
224224
225225
For the most part, we try to set default in the class attributes
226-
of Components. But, defaults the top-level Application (which is
227-
not a HasTraits or Component) are not set in this way. Instead
226+
of Configurables. But, defaults the top-level Application (which is
227+
not a HasTraits or Configurables) are not set in this way. Instead
228228
we set them here. The Global section is for variables like this that
229-
don't belong to a particular component.
229+
don't belong to a particular configurable.
230230
"""
231231
c = Config()
232232
c.Global.ipython_dir = get_ipython_dir()
@@ -418,8 +418,8 @@ def pre_construct(self):
418418
pass
419419

420420
def construct(self):
421-
"""Construct the main components that make up this app."""
422-
self.log.debug("Constructing components for application")
421+
"""Construct the main objects that make up this app."""
422+
self.log.debug("Constructing main objects for application")
423423

424424
def post_construct(self):
425425
"""Do actions after construct, but before starting the app."""

IPython/core/builtin_trap.py

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@
2121

2222
import __builtin__
2323

24-
from IPython.core.component import Component
24+
from IPython.config.configurable import Configurable
2525
from IPython.core.quitter import Quitter
2626

27-
from IPython.utils.autoattr import auto_attr
27+
from IPython.utils.traitlets import Instance
2828

2929
#-----------------------------------------------------------------------------
3030
# Classes and functions
@@ -35,20 +35,17 @@ class __BuiltinUndefined(object): pass
3535
BuiltinUndefined = __BuiltinUndefined()
3636

3737

38-
class BuiltinTrap(Component):
38+
class BuiltinTrap(Configurable):
3939

40-
def __init__(self, parent):
41-
super(BuiltinTrap, self).__init__(parent, None, None)
40+
shell = Instance('IPython.core.iplib.InteractiveShell')
41+
42+
def __init__(self, shell):
43+
super(BuiltinTrap, self).__init__(None)
4244
self._orig_builtins = {}
4345
# We define this to track if a single BuiltinTrap is nested.
4446
# Only turn off the trap when the outermost call to __exit__ is made.
4547
self._nested_level = 0
46-
47-
@auto_attr
48-
def shell(self):
49-
return Component.get_instances(
50-
root=self.root,
51-
klass='IPython.core.iplib.InteractiveShell')[0]
48+
self.shell = shell
5249

5350
def __enter__(self):
5451
if self._nested_level == 0:

0 commit comments

Comments
 (0)