##// END OF EJS Templates
First draft of refactored Component->Configurable.
Brian Granger -
Show More
@@ -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
@@ -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)
@@ -0,0 +1,124
1 # encoding: utf-8
2 """A class for managing IPython extensions.
3
4 Authors:
5
6 * Brian Granger
7 """
8
9 #-----------------------------------------------------------------------------
10 # Copyright (C) 2010 The IPython Development Team
11 #
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
14 #-----------------------------------------------------------------------------
15
16 #-----------------------------------------------------------------------------
17 # Imports
18 #-----------------------------------------------------------------------------
19
20 import os
21 import sys
22
23 from IPython.config.configurable import Configurable
24 from IPython.utils.traitlets import Instance
25
26 #-----------------------------------------------------------------------------
27 # Main class
28 #-----------------------------------------------------------------------------
29
30 class ExtensionManager(Configurable):
31
32 shell = Instance('IPython.core.iplib.InteractiveShell')
33
34 def __init__(self, shell, config=None):
35 super(ExtensionManager, self).__init__(config=config)
36 self.shell = shell
37 self.shell.on_trait_change(
38 self._on_ipython_dir_changed, 'ipython_dir'
39 )
40
41 def __del__(self):
42 self.shell.on_trait_change(
43 self._on_ipython_dir_changed, 'ipython_dir', remove=True
44 )
45
46 @property
47 def ipython_extension_dir(self):
48 return os.path.join(self.shell.ipython_dir, u'extensions')
49
50 def _on_ipython_dir_changed(self):
51 if not os.path.isdir(self.ipython_extension_dir):
52 os.makedirs(self.ipython_extension_dir, mode = 0777)
53
54 def load_extension(self, module_str):
55 """Load an IPython extension by its module name.
56
57 An IPython extension is an importable Python module that has
58 a function with the signature::
59
60 def load_ipython_extension(ipython):
61 # Do things with ipython
62
63 This function is called after your extension is imported and the
64 currently active :class:`InteractiveShell` instance is passed as
65 the only argument. You can do anything you want with IPython at
66 that point, including defining new magic and aliases, adding new
67 components, etc.
68
69 The :func:`load_ipython_extension` will be called again is you
70 load or reload the extension again. It is up to the extension
71 author to add code to manage that.
72
73 You can put your extension modules anywhere you want, as long as
74 they can be imported by Python's standard import mechanism. However,
75 to make it easy to write extensions, you can also put your extensions
76 in ``os.path.join(self.ipython_dir, 'extensions')``. This directory
77 is added to ``sys.path`` automatically.
78
79 If :func:`load_ipython_extension` returns anything, this function
80 will return that object.
81 """
82 from IPython.utils.syspathcontext import prepended_to_syspath
83
84 if module_str not in sys.modules:
85 with prepended_to_syspath(self.ipython_extension_dir):
86 __import__(module_str)
87 mod = sys.modules[module_str]
88 return self._call_load_ipython_extension(mod)
89
90 def unload_extension(self, module_str):
91 """Unload an IPython extension by its module name.
92
93 This function looks up the extension's name in ``sys.modules`` and
94 simply calls ``mod.unload_ipython_extension(self)``.
95 """
96 if module_str in sys.modules:
97 mod = sys.modules[module_str]
98 self._call_unload_ipython_extension(mod)
99
100 def reload_extension(self, module_str):
101 """Reload an IPython extension by calling reload.
102
103 If the module has not been loaded before,
104 :meth:`InteractiveShell.load_extension` is called. Otherwise
105 :func:`reload` is called and then the :func:`load_ipython_extension`
106 function of the module, if it exists is called.
107 """
108 from IPython.utils.syspathcontext import prepended_to_syspath
109
110 with prepended_to_syspath(self.ipython_extension_dir):
111 if module_str in sys.modules:
112 mod = sys.modules[module_str]
113 reload(mod)
114 self._call_load_ipython_extension(mod)
115 else:
116 self.load_extension(module_str)
117
118 def _call_load_ipython_extension(self, mod):
119 if hasattr(mod, 'load_ipython_extension'):
120 return mod.load_ipython_extension(self.shell)
121
122 def _call_unload_ipython_extension(self, mod):
123 if hasattr(mod, 'unload_ipython_extension'):
124 return mod.unload_ipython_extension(self.shell) No newline at end of file
@@ -1,10 +1,11
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 """
3 """
4 IPython's alias component
4 System command aliases.
5
5
6 Authors:
6 Authors:
7
7
8 * Fernando Perez
8 * Brian Granger
9 * Brian Granger
9 """
10 """
10
11
@@ -25,10 +26,10 import os
25 import re
26 import re
26 import sys
27 import sys
27
28
28 from IPython.core.component import Component
29 from IPython.config.configurable import Configurable
29 from IPython.core.splitinput import split_user_input
30 from IPython.core.splitinput import split_user_input
30
31
31 from IPython.utils.traitlets import List
32 from IPython.utils.traitlets import List, Instance
32 from IPython.utils.autoattr import auto_attr
33 from IPython.utils.autoattr import auto_attr
33 from IPython.utils.warn import warn, error
34 from IPython.utils.warn import warn, error
34
35
@@ -99,22 +100,18 class InvalidAliasError(AliasError):
99 #-----------------------------------------------------------------------------
100 #-----------------------------------------------------------------------------
100
101
101
102
102 class AliasManager(Component):
103 class AliasManager(Configurable):
103
104
104 default_aliases = List(default_aliases(), config=True)
105 default_aliases = List(default_aliases(), config=True)
105 user_aliases = List(default_value=[], config=True)
106 user_aliases = List(default_value=[], config=True)
107 shell = Instance('IPython.core.iplib.InteractiveShell')
106
108
107 def __init__(self, parent, config=None):
109 def __init__(self, shell, config=None):
108 super(AliasManager, self).__init__(parent, config=config)
110 super(AliasManager, self).__init__(config=config)
109 self.alias_table = {}
111 self.alias_table = {}
110 self.exclude_aliases()
112 self.exclude_aliases()
111 self.init_aliases()
113 self.init_aliases()
112
114 self.shell = shell
113 @auto_attr
114 def shell(self):
115 return Component.get_instances(
116 root=self.root,
117 klass='IPython.core.iplib.InteractiveShell')[0]
118
115
119 def __contains__(self, name):
116 def __contains__(self, name):
120 if name in self.alias_table:
117 if name in self.alias_table:
@@ -6,7 +6,7 All top-level applications should use the classes in this module for
6 handling configuration and creating componenets.
6 handling configuration and creating componenets.
7
7
8 The job of an :class:`Application` is to create the master configuration
8 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.
10
10
11 Authors:
11 Authors:
12
12
@@ -76,7 +76,7 class BaseAppConfigLoader(ArgParseConfigLoader):
76
76
77
77
78 class Application(object):
78 class Application(object):
79 """Load a config, construct components and set them running.
79 """Load a config, construct configurables and set them running.
80
80
81 The configuration of an application can be done via three different Config
81 The configuration of an application can be done via three different Config
82 objects, which are loaded and ultimately merged into a single one used
82 objects, which are loaded and ultimately merged into a single one used
@@ -113,7 +113,7 class Application(object):
113 file_config = None
113 file_config = None
114 #: Read from the system's command line flags.
114 #: Read from the system's command line flags.
115 command_line_config = None
115 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.
117 master_config = None
117 master_config = None
118 #: A reference to the argv to be used (typically ends up being sys.argv[1:])
118 #: A reference to the argv to be used (typically ends up being sys.argv[1:])
119 argv = None
119 argv = None
@@ -223,10 +223,10 class Application(object):
223 """Create defaults that can't be set elsewhere.
223 """Create defaults that can't be set elsewhere.
224
224
225 For the most part, we try to set default in the class attributes
225 For the most part, we try to set default in the class attributes
226 of Components. But, defaults the top-level Application (which is
226 of Configurables. But, defaults the top-level Application (which is
227 not a HasTraits or Component) are not set in this way. Instead
227 not a HasTraits or Configurables) are not set in this way. Instead
228 we set them here. The Global section is for variables like this that
228 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.
230 """
230 """
231 c = Config()
231 c = Config()
232 c.Global.ipython_dir = get_ipython_dir()
232 c.Global.ipython_dir = get_ipython_dir()
@@ -418,8 +418,8 class Application(object):
418 pass
418 pass
419
419
420 def construct(self):
420 def construct(self):
421 """Construct the main components that make up this app."""
421 """Construct the main objects that make up this app."""
422 self.log.debug("Constructing components for application")
422 self.log.debug("Constructing main objects for application")
423
423
424 def post_construct(self):
424 def post_construct(self):
425 """Do actions after construct, but before starting the app."""
425 """Do actions after construct, but before starting the app."""
@@ -21,10 +21,10 Authors:
21
21
22 import __builtin__
22 import __builtin__
23
23
24 from IPython.core.component import Component
24 from IPython.config.configurable import Configurable
25 from IPython.core.quitter import Quitter
25 from IPython.core.quitter import Quitter
26
26
27 from IPython.utils.autoattr import auto_attr
27 from IPython.utils.traitlets import Instance
28
28
29 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
30 # Classes and functions
30 # Classes and functions
@@ -35,20 +35,17 class __BuiltinUndefined(object): pass
35 BuiltinUndefined = __BuiltinUndefined()
35 BuiltinUndefined = __BuiltinUndefined()
36
36
37
37
38 class BuiltinTrap(Component):
38 class BuiltinTrap(Configurable):
39
39
40 def __init__(self, parent):
40 shell = Instance('IPython.core.iplib.InteractiveShell')
41 super(BuiltinTrap, self).__init__(parent, None, None)
41
42 def __init__(self, shell):
43 super(BuiltinTrap, self).__init__(None)
42 self._orig_builtins = {}
44 self._orig_builtins = {}
43 # We define this to track if a single BuiltinTrap is nested.
45 # We define this to track if a single BuiltinTrap is nested.
44 # Only turn off the trap when the outermost call to __exit__ is made.
46 # Only turn off the trap when the outermost call to __exit__ is made.
45 self._nested_level = 0
47 self._nested_level = 0
46
48 self.shell = shell
47 @auto_attr
48 def shell(self):
49 return Component.get_instances(
50 root=self.root,
51 klass='IPython.core.iplib.InteractiveShell')[0]
52
49
53 def __enter__(self):
50 def __enter__(self):
54 if self._nested_level == 0:
51 if self._nested_level == 0:
@@ -22,34 +22,28 Authors:
22
22
23 import sys
23 import sys
24
24
25 from IPython.core.component import Component
25 from IPython.config.configurable import Configurable
26
26
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28 # Classes and functions
28 # Classes and functions
29 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
30
30
31
31
32 class DisplayTrap(Component):
32 class DisplayTrap(Configurable):
33 """Object to manage sys.displayhook.
33 """Object to manage sys.displayhook.
34
34
35 This came from IPython.core.kernel.display_hook, but is simplified
35 This came from IPython.core.kernel.display_hook, but is simplified
36 (no callbacks or formatters) until more of the core is refactored.
36 (no callbacks or formatters) until more of the core is refactored.
37 """
37 """
38
38
39 def __init__(self, parent, hook):
39 def __init__(self, hook):
40 super(DisplayTrap, self).__init__(parent, None, None)
40 super(DisplayTrap, self).__init__(None)
41 self.hook = hook
41 self.hook = hook
42 self.old_hook = None
42 self.old_hook = None
43 # We define this to track if a single BuiltinTrap is nested.
43 # We define this to track if a single BuiltinTrap is nested.
44 # Only turn off the trap when the outermost call to __exit__ is made.
44 # Only turn off the trap when the outermost call to __exit__ is made.
45 self._nested_level = 0
45 self._nested_level = 0
46
46
47 # @auto_attr
48 # def shell(self):
49 # return Component.get_instances(
50 # root=self.root,
51 # klass='IPython.core.iplib.InteractiveShell')[0]
52
53 def __enter__(self):
47 def __enter__(self):
54 if self._nested_level == 0:
48 if self._nested_level == 0:
55 self.set()
49 self.set()
@@ -24,13 +24,7 has been made into a component, this module will be sent to deathrow.
24
24
25
25
26 def get():
26 def get():
27 """Get the most recently created InteractiveShell instance."""
27 """Get the global InteractiveShell instance."""
28 from IPython.core.iplib import InteractiveShell
28 from IPython.core.iplib import InteractiveShell
29 insts = InteractiveShell.get_instances()
29 return InteractiveShell.instance()
30 if len(insts)==0:
30
31 return None
32 most_recent = insts[0]
33 for inst in insts[1:]:
34 if inst.created > most_recent.created:
35 most_recent = inst
36 return most_recent
@@ -475,8 +475,8 class IPythonApp(Application):
475 # But that might be the place for them
475 # But that might be the place for them
476 sys.path.insert(0, '')
476 sys.path.insert(0, '')
477
477
478 # Create an InteractiveShell instance
478 # Create an InteractiveShell instance.
479 self.shell = InteractiveShell(None, self.master_config)
479 self.shell = InteractiveShell.instance(config=self.master_config)
480
480
481 def post_construct(self):
481 def post_construct(self):
482 """Do actions after construct, but before starting the app."""
482 """Do actions after construct, but before starting the app."""
@@ -543,7 +543,7 class IPythonApp(Application):
543 def _load_extensions(self):
543 def _load_extensions(self):
544 """Load all IPython extensions in Global.extensions.
544 """Load all IPython extensions in Global.extensions.
545
545
546 This uses the :meth:`InteractiveShell.load_extensions` to load all
546 This uses the :meth:`ExtensionManager.load_extensions` to load all
547 the extensions listed in ``self.master_config.Global.extensions``.
547 the extensions listed in ``self.master_config.Global.extensions``.
548 """
548 """
549 try:
549 try:
@@ -553,7 +553,7 class IPythonApp(Application):
553 for ext in extensions:
553 for ext in extensions:
554 try:
554 try:
555 self.log.info("Loading IPython extension: %s" % ext)
555 self.log.info("Loading IPython extension: %s" % ext)
556 self.shell.load_extension(ext)
556 self.shell.extension_manager.load_extension(ext)
557 except:
557 except:
558 self.log.warn("Error in loading extension: %s" % ext)
558 self.log.warn("Error in loading extension: %s" % ext)
559 self.shell.showtraceback()
559 self.shell.showtraceback()
@@ -1,12 +1,10
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """Main IPython class."""
3 Main IPython Component
4 """
5
3
6 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
7 # Copyright (C) 2001 Janko Hauser <jhauser@zscout.de>
5 # Copyright (C) 2001 Janko Hauser <jhauser@zscout.de>
8 # Copyright (C) 2001-2007 Fernando Perez. <fperez@colorado.edu>
6 # Copyright (C) 2001-2007 Fernando Perez. <fperez@colorado.edu>
9 # Copyright (C) 2008-2009 The IPython Development Team
7 # Copyright (C) 2008-2010 The IPython Development Team
10 #
8 #
11 # Distributed under the terms of the BSD License. The full license is in
9 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
10 # the file COPYING, distributed as part of this software.
@@ -38,9 +36,10 from IPython.core import shadowns
38 from IPython.core import ultratb
36 from IPython.core import ultratb
39 from IPython.core.alias import AliasManager
37 from IPython.core.alias import AliasManager
40 from IPython.core.builtin_trap import BuiltinTrap
38 from IPython.core.builtin_trap import BuiltinTrap
41 from IPython.core.component import Component
39 from IPython.config.configurable import Configurable
42 from IPython.core.display_trap import DisplayTrap
40 from IPython.core.display_trap import DisplayTrap
43 from IPython.core.error import TryNext, UsageError
41 from IPython.core.error import TryNext, UsageError
42 from IPython.core.extensions import ExtensionManager
44 from IPython.core.fakemodule import FakeModule, init_fakemod_dict
43 from IPython.core.fakemodule import FakeModule, init_fakemod_dict
45 from IPython.core.logger import Logger
44 from IPython.core.logger import Logger
46 from IPython.core.magic import Magic
45 from IPython.core.magic import Magic
@@ -69,7 +68,7 from IPython.utils.syspathcontext import prepended_to_syspath
69 from IPython.utils.terminal import toggle_set_term_title, set_term_title
68 from IPython.utils.terminal import toggle_set_term_title, set_term_title
70 from IPython.utils.warn import warn, error, fatal
69 from IPython.utils.warn import warn, error, fatal
71 from IPython.utils.traitlets import (
70 from IPython.utils.traitlets import (
72 Int, Str, CBool, CaselessStrEnum, Enum, List, Unicode
71 Int, Str, CBool, CaselessStrEnum, Enum, List, Unicode, Instance
73 )
72 )
74
73
75 # from IPython.utils import growl
74 # from IPython.utils import growl
@@ -196,7 +195,7 class SeparateStr(Str):
196 #-----------------------------------------------------------------------------
195 #-----------------------------------------------------------------------------
197
196
198
197
199 class InteractiveShell(Component, Magic):
198 class InteractiveShell(Configurable, Magic):
200 """An enhanced, interactive shell for Python."""
199 """An enhanced, interactive shell for Python."""
201
200
202 autocall = Enum((0,1,2), default_value=1, config=True)
201 autocall = Enum((0,1,2), default_value=1, config=True)
@@ -281,14 +280,21 class InteractiveShell(Component, Magic):
281 # Subclasses with thread support should override this as needed.
280 # Subclasses with thread support should override this as needed.
282 isthreaded = False
281 isthreaded = False
283
282
284 def __init__(self, parent=None, config=None, ipython_dir=None, usage=None,
283 # Subcomponents of InteractiveShell
284 alias_manager = Instance('IPython.core.alias.AliasManager')
285 prefilter_manager = Instance('IPython.core.prefilter.PrefilterManager')
286 builtin_trap = Instance('IPython.core.builtin_trap.BuiltinTrap')
287 display_trap = Instance('IPython.core.display_trap.DisplayTrap')
288 extension_manager = Instance('IPython.core.extensions.ExtensionManager')
289
290 def __init__(self, config=None, ipython_dir=None, usage=None,
285 user_ns=None, user_global_ns=None,
291 user_ns=None, user_global_ns=None,
286 banner1=None, banner2=None, display_banner=None,
292 banner1=None, banner2=None, display_banner=None,
287 custom_exceptions=((),None)):
293 custom_exceptions=((),None)):
288
294
289 # This is where traits with a config_key argument are updated
295 # This is where traits with a config_key argument are updated
290 # from the values on config.
296 # from the values on config.
291 super(InteractiveShell, self).__init__(parent, config=config)
297 super(InteractiveShell, self).__init__(config=config)
292
298
293 # These are relatively independent and stateless
299 # These are relatively independent and stateless
294 self.init_ipython_dir(ipython_dir)
300 self.init_ipython_dir(ipython_dir)
@@ -334,8 +340,20 class InteractiveShell(Component, Magic):
334 self.init_reload_doctest()
340 self.init_reload_doctest()
335 self.init_magics()
341 self.init_magics()
336 self.init_pdb()
342 self.init_pdb()
343 self.init_extension_manager()
337 self.hooks.late_startup_hook()
344 self.hooks.late_startup_hook()
338
345
346 @classmethod
347 def instance(cls, *args, **kwargs):
348 """Returns a global InteractiveShell instance."""
349 if not hasattr(cls, "_instance"):
350 cls._instance = cls(*args, **kwargs)
351 return cls._instance
352
353 @classmethod
354 def initialized(cls):
355 return hasattr(cls, "_instance")
356
339 def get_ipython(self):
357 def get_ipython(self):
340 """Return the currently running IPython instance."""
358 """Return the currently running IPython instance."""
341 return self
359 return self
@@ -353,12 +371,6 class InteractiveShell(Component, Magic):
353 def _ipython_dir_changed(self, name, new):
371 def _ipython_dir_changed(self, name, new):
354 if not os.path.isdir(new):
372 if not os.path.isdir(new):
355 os.makedirs(new, mode = 0777)
373 os.makedirs(new, mode = 0777)
356 if not os.path.isdir(self.ipython_extension_dir):
357 os.makedirs(self.ipython_extension_dir, mode = 0777)
358
359 @property
360 def ipython_extension_dir(self):
361 return os.path.join(self.ipython_dir, 'extensions')
362
374
363 @property
375 @property
364 def usable_screen_length(self):
376 def usable_screen_length(self):
@@ -523,7 +535,7 class InteractiveShell(Component, Magic):
523 pass
535 pass
524
536
525 def init_displayhook(self):
537 def init_displayhook(self):
526 self.display_trap = DisplayTrap(self, self.outputcache)
538 self.display_trap = DisplayTrap(self.outputcache)
527
539
528 def init_reload_doctest(self):
540 def init_reload_doctest(self):
529 # Do a proper resetting of doctest, including the necessary displayhook
541 # Do a proper resetting of doctest, including the necessary displayhook
@@ -1747,6 +1759,13 class InteractiveShell(Component, Magic):
1747 self.ns_table['alias'] = self.alias_manager.alias_table,
1759 self.ns_table['alias'] = self.alias_manager.alias_table,
1748
1760
1749 #-------------------------------------------------------------------------
1761 #-------------------------------------------------------------------------
1762 # Things related to extensions
1763 #-------------------------------------------------------------------------
1764
1765 def init_extension_manager(self):
1766 self.extension_manager = ExtensionManager(self, config=self.config)
1767
1768 #-------------------------------------------------------------------------
1750 # Things related to the running of code
1769 # Things related to the running of code
1751 #-------------------------------------------------------------------------
1770 #-------------------------------------------------------------------------
1752
1771
@@ -2340,96 +2359,6 class InteractiveShell(Component, Magic):
2340 return lineout
2359 return lineout
2341
2360
2342 #-------------------------------------------------------------------------
2361 #-------------------------------------------------------------------------
2343 # Working with components
2344 #-------------------------------------------------------------------------
2345
2346 def get_component(self, name=None, klass=None):
2347 """Fetch a component by name and klass in my tree."""
2348 c = Component.get_instances(root=self, name=name, klass=klass)
2349 if len(c) == 0:
2350 return None
2351 if len(c) == 1:
2352 return c[0]
2353 else:
2354 return c
2355
2356 #-------------------------------------------------------------------------
2357 # IPython extensions
2358 #-------------------------------------------------------------------------
2359
2360 def load_extension(self, module_str):
2361 """Load an IPython extension by its module name.
2362
2363 An IPython extension is an importable Python module that has
2364 a function with the signature::
2365
2366 def load_ipython_extension(ipython):
2367 # Do things with ipython
2368
2369 This function is called after your extension is imported and the
2370 currently active :class:`InteractiveShell` instance is passed as
2371 the only argument. You can do anything you want with IPython at
2372 that point, including defining new magic and aliases, adding new
2373 components, etc.
2374
2375 The :func:`load_ipython_extension` will be called again is you
2376 load or reload the extension again. It is up to the extension
2377 author to add code to manage that.
2378
2379 You can put your extension modules anywhere you want, as long as
2380 they can be imported by Python's standard import mechanism. However,
2381 to make it easy to write extensions, you can also put your extensions
2382 in ``os.path.join(self.ipython_dir, 'extensions')``. This directory
2383 is added to ``sys.path`` automatically.
2384
2385 If :func:`load_ipython_extension` returns anything, this function
2386 will return that object.
2387 """
2388 from IPython.utils.syspathcontext import prepended_to_syspath
2389
2390 if module_str not in sys.modules:
2391 with prepended_to_syspath(self.ipython_extension_dir):
2392 __import__(module_str)
2393 mod = sys.modules[module_str]
2394 return self._call_load_ipython_extension(mod)
2395
2396 def unload_extension(self, module_str):
2397 """Unload an IPython extension by its module name.
2398
2399 This function looks up the extension's name in ``sys.modules`` and
2400 simply calls ``mod.unload_ipython_extension(self)``.
2401 """
2402 if module_str in sys.modules:
2403 mod = sys.modules[module_str]
2404 self._call_unload_ipython_extension(mod)
2405
2406 def reload_extension(self, module_str):
2407 """Reload an IPython extension by calling reload.
2408
2409 If the module has not been loaded before,
2410 :meth:`InteractiveShell.load_extension` is called. Otherwise
2411 :func:`reload` is called and then the :func:`load_ipython_extension`
2412 function of the module, if it exists is called.
2413 """
2414 from IPython.utils.syspathcontext import prepended_to_syspath
2415
2416 with prepended_to_syspath(self.ipython_extension_dir):
2417 if module_str in sys.modules:
2418 mod = sys.modules[module_str]
2419 reload(mod)
2420 self._call_load_ipython_extension(mod)
2421 else:
2422 self.load_extension(module_str)
2423
2424 def _call_load_ipython_extension(self, mod):
2425 if hasattr(mod, 'load_ipython_extension'):
2426 return mod.load_ipython_extension(self)
2427
2428 def _call_unload_ipython_extension(self, mod):
2429 if hasattr(mod, 'unload_ipython_extension'):
2430 return mod.unload_ipython_extension(self)
2431
2432 #-------------------------------------------------------------------------
2433 # Things related to the prefilter
2362 # Things related to the prefilter
2434 #-------------------------------------------------------------------------
2363 #-------------------------------------------------------------------------
2435
2364
@@ -97,11 +97,11 def compress_dhist(dh):
97
97
98 # XXX - for some odd reason, if Magic is made a new-style class, we get errors
98 # XXX - for some odd reason, if Magic is made a new-style class, we get errors
99 # on construction of the main InteractiveShell object. Something odd is going
99 # on construction of the main InteractiveShell object. Something odd is going
100 # on with super() calls, Component and the MRO... For now leave it as-is, but
100 # on with super() calls, Configurable and the MRO... For now leave it as-is, but
101 # eventually this needs to be clarified.
101 # eventually this needs to be clarified.
102 # BG: This is because InteractiveShell inherits from this, but is itself a
102 # BG: This is because InteractiveShell inherits from this, but is itself a
103 # Component. This messes up the MRO in some way. The fix is that we need to
103 # Configurable. This messes up the MRO in some way. The fix is that we need to
104 # make Magic a component that InteractiveShell does not subclass.
104 # make Magic a configurable that InteractiveShell does not subclass.
105
105
106 class Magic:
106 class Magic:
107 """Magic functions for InteractiveShell.
107 """Magic functions for InteractiveShell.
@@ -3586,15 +3586,15 Defaulting color scheme to 'NoColor'"""
3586
3586
3587 def magic_load_ext(self, module_str):
3587 def magic_load_ext(self, module_str):
3588 """Load an IPython extension by its module name."""
3588 """Load an IPython extension by its module name."""
3589 return self.load_extension(module_str)
3589 return self.extension_manager.load_extension(module_str)
3590
3590
3591 def magic_unload_ext(self, module_str):
3591 def magic_unload_ext(self, module_str):
3592 """Unload an IPython extension by its module name."""
3592 """Unload an IPython extension by its module name."""
3593 self.unload_extension(module_str)
3593 self.extension_manager.unload_extension(module_str)
3594
3594
3595 def magic_reload_ext(self, module_str):
3595 def magic_reload_ext(self, module_str):
3596 """Reload an IPython extension by its module name."""
3596 """Reload an IPython extension by its module name."""
3597 self.reload_extension(module_str)
3597 self.extension_manager.reload_extension(module_str)
3598
3598
3599 @testdec.skip_doctest
3599 @testdec.skip_doctest
3600 def magic_install_profiles(self, s):
3600 def magic_install_profiles(self, s):
@@ -31,11 +31,11 import re
31
31
32 from IPython.core.alias import AliasManager
32 from IPython.core.alias import AliasManager
33 from IPython.core.autocall import IPyAutocall
33 from IPython.core.autocall import IPyAutocall
34 from IPython.core.component import Component
34 from IPython.config.configurable import Configurable
35 from IPython.core.splitinput import split_user_input
35 from IPython.core.splitinput import split_user_input
36 from IPython.core.page import page
36 from IPython.core.page import page
37
37
38 from IPython.utils.traitlets import List, Int, Any, Str, CBool, Bool
38 from IPython.utils.traitlets import List, Int, Any, Str, CBool, Bool, Instance
39 from IPython.utils.io import Term
39 from IPython.utils.io import Term
40 from IPython.utils.text import make_quoted_expr
40 from IPython.utils.text import make_quoted_expr
41 from IPython.utils.autoattr import auto_attr
41 from IPython.utils.autoattr import auto_attr
@@ -169,7 +169,7 class LineInfo(object):
169 #-----------------------------------------------------------------------------
169 #-----------------------------------------------------------------------------
170
170
171
171
172 class PrefilterManager(Component):
172 class PrefilterManager(Configurable):
173 """Main prefilter component.
173 """Main prefilter component.
174
174
175 The IPython prefilter is run on all user input before it is run. The
175 The IPython prefilter is run on all user input before it is run. The
@@ -210,19 +210,15 class PrefilterManager(Component):
210 """
210 """
211
211
212 multi_line_specials = CBool(True, config=True)
212 multi_line_specials = CBool(True, config=True)
213 shell = Instance('IPython.core.iplib.InteractiveShell')
213
214
214 def __init__(self, parent, config=None):
215 def __init__(self, shell, config=None):
215 super(PrefilterManager, self).__init__(parent, config=config)
216 super(PrefilterManager, self).__init__(config=config)
217 self.shell = shell
216 self.init_transformers()
218 self.init_transformers()
217 self.init_handlers()
219 self.init_handlers()
218 self.init_checkers()
220 self.init_checkers()
219
221
220 @auto_attr
221 def shell(self):
222 return Component.get_instances(
223 root=self.root,
224 klass='IPython.core.iplib.InteractiveShell')[0]
225
226 #-------------------------------------------------------------------------
222 #-------------------------------------------------------------------------
227 # API for managing transformers
223 # API for managing transformers
228 #-------------------------------------------------------------------------
224 #-------------------------------------------------------------------------
@@ -231,7 +227,7 class PrefilterManager(Component):
231 """Create the default transformers."""
227 """Create the default transformers."""
232 self._transformers = []
228 self._transformers = []
233 for transformer_cls in _default_transformers:
229 for transformer_cls in _default_transformers:
234 transformer_cls(self, config=self.config)
230 transformer_cls(self.shell, self, config=self.config)
235
231
236 def sort_transformers(self):
232 def sort_transformers(self):
237 """Sort the transformers by priority.
233 """Sort the transformers by priority.
@@ -265,7 +261,7 class PrefilterManager(Component):
265 """Create the default checkers."""
261 """Create the default checkers."""
266 self._checkers = []
262 self._checkers = []
267 for checker in _default_checkers:
263 for checker in _default_checkers:
268 checker(self, config=self.config)
264 checker(self.shell, self, config=self.config)
269
265
270 def sort_checkers(self):
266 def sort_checkers(self):
271 """Sort the checkers by priority.
267 """Sort the checkers by priority.
@@ -300,7 +296,7 class PrefilterManager(Component):
300 self._handlers = {}
296 self._handlers = {}
301 self._esc_handlers = {}
297 self._esc_handlers = {}
302 for handler in _default_handlers:
298 for handler in _default_handlers:
303 handler(self, config=self.config)
299 handler(self.shell, self, config=self.config)
304
300
305 @property
301 @property
306 def handlers(self):
302 def handlers(self):
@@ -445,28 +441,22 class PrefilterManager(Component):
445 #-----------------------------------------------------------------------------
441 #-----------------------------------------------------------------------------
446
442
447
443
448 class PrefilterTransformer(Component):
444 class PrefilterTransformer(Configurable):
449 """Transform a line of user input."""
445 """Transform a line of user input."""
450
446
451 priority = Int(100, config=True)
447 priority = Int(100, config=True)
452 shell = Any
448 # Transformers don't currently use shell or prefilter_manager, but as we
453 prefilter_manager = Any
449 # move away from checkers and handlers, they will need them.
450 shell = Instance('IPython.core.iplib.InteractiveShell')
451 prefilter_manager = Instance('IPython.core.prefilter.PrefilterManager')
454 enabled = Bool(True, config=True)
452 enabled = Bool(True, config=True)
455
453
456 def __init__(self, parent, config=None):
454 def __init__(self, shell, prefilter_manager, config=None):
457 super(PrefilterTransformer, self).__init__(parent, config=config)
455 super(PrefilterTransformer, self).__init__(config=config)
456 self.shell = shell
457 self.prefilter_manager = prefilter_manager
458 self.prefilter_manager.register_transformer(self)
458 self.prefilter_manager.register_transformer(self)
459
459
460 @auto_attr
461 def shell(self):
462 return Component.get_instances(
463 root=self.root,
464 klass='IPython.core.iplib.InteractiveShell')[0]
465
466 @auto_attr
467 def prefilter_manager(self):
468 return PrefilterManager.get_instances(root=self.root)[0]
469
470 def transform(self, line, continue_prompt):
460 def transform(self, line, continue_prompt):
471 """Transform a line, returning the new one."""
461 """Transform a line, returning the new one."""
472 return None
462 return None
@@ -561,28 +551,20 class IPyPromptTransformer(PrefilterTransformer):
561 #-----------------------------------------------------------------------------
551 #-----------------------------------------------------------------------------
562
552
563
553
564 class PrefilterChecker(Component):
554 class PrefilterChecker(Configurable):
565 """Inspect an input line and return a handler for that line."""
555 """Inspect an input line and return a handler for that line."""
566
556
567 priority = Int(100, config=True)
557 priority = Int(100, config=True)
568 shell = Any
558 shell = Instance('IPython.core.iplib.InteractiveShell')
569 prefilter_manager = Any
559 prefilter_manager = Instance('IPython.core.prefilter.PrefilterManager')
570 enabled = Bool(True, config=True)
560 enabled = Bool(True, config=True)
571
561
572 def __init__(self, parent, config=None):
562 def __init__(self, shell, prefilter_manager, config=None):
573 super(PrefilterChecker, self).__init__(parent, config=config)
563 super(PrefilterChecker, self).__init__(config=config)
564 self.shell = shell
565 self.prefilter_manager = prefilter_manager
574 self.prefilter_manager.register_checker(self)
566 self.prefilter_manager.register_checker(self)
575
567
576 @auto_attr
577 def shell(self):
578 return Component.get_instances(
579 root=self.root,
580 klass='IPython.core.iplib.InteractiveShell')[0]
581
582 @auto_attr
583 def prefilter_manager(self):
584 return PrefilterManager.get_instances(root=self.root)[0]
585
586 def check(self, line_info):
568 def check(self, line_info):
587 """Inspect line_info and return a handler instance or None."""
569 """Inspect line_info and return a handler instance or None."""
588 return None
570 return None
@@ -709,16 +691,12 class AliasChecker(PrefilterChecker):
709
691
710 priority = Int(800, config=True)
692 priority = Int(800, config=True)
711
693
712 @auto_attr
713 def alias_manager(self):
714 return AliasManager.get_instances(root=self.root)[0]
715
716 def check(self, line_info):
694 def check(self, line_info):
717 "Check if the initital identifier on the line is an alias."
695 "Check if the initital identifier on the line is an alias."
718 # Note: aliases can not contain '.'
696 # Note: aliases can not contain '.'
719 head = line_info.ifun.split('.',1)[0]
697 head = line_info.ifun.split('.',1)[0]
720 if line_info.ifun not in self.alias_manager \
698 if line_info.ifun not in self.shell.alias_manager \
721 or head not in self.alias_manager \
699 or head not in self.shell.alias_manager \
722 or is_shadowed(head, self.shell):
700 or is_shadowed(head, self.shell):
723 return None
701 return None
724
702
@@ -766,31 +744,23 class AutocallChecker(PrefilterChecker):
766 #-----------------------------------------------------------------------------
744 #-----------------------------------------------------------------------------
767
745
768
746
769 class PrefilterHandler(Component):
747 class PrefilterHandler(Configurable):
770
748
771 handler_name = Str('normal')
749 handler_name = Str('normal')
772 esc_strings = List([])
750 esc_strings = List([])
773 shell = Any
751 shell = Instance('IPython.core.iplib.InteractiveShell')
774 prefilter_manager = Any
752 prefilter_manager = Instance('IPython.core.prefilter.PrefilterManager')
775
753
776 def __init__(self, parent, config=None):
754 def __init__(self, shell, prefilter_manager, config=None):
777 super(PrefilterHandler, self).__init__(parent, config=config)
755 super(PrefilterHandler, self).__init__(config=config)
756 self.shell = shell
757 self.prefilter_manager = prefilter_manager
778 self.prefilter_manager.register_handler(
758 self.prefilter_manager.register_handler(
779 self.handler_name,
759 self.handler_name,
780 self,
760 self,
781 self.esc_strings
761 self.esc_strings
782 )
762 )
783
763
784 @auto_attr
785 def shell(self):
786 return Component.get_instances(
787 root=self.root,
788 klass='IPython.core.iplib.InteractiveShell')[0]
789
790 @auto_attr
791 def prefilter_manager(self):
792 return PrefilterManager.get_instances(root=self.root)[0]
793
794 def handle(self, line_info):
764 def handle(self, line_info):
795 # print "normal: ", line_info
765 # print "normal: ", line_info
796 """Handle normal input lines. Use as a template for handlers."""
766 """Handle normal input lines. Use as a template for handlers."""
@@ -827,13 +797,9 class AliasHandler(PrefilterHandler):
827
797
828 handler_name = Str('alias')
798 handler_name = Str('alias')
829
799
830 @auto_attr
831 def alias_manager(self):
832 return AliasManager.get_instances(root=self.root)[0]
833
834 def handle(self, line_info):
800 def handle(self, line_info):
835 """Handle alias input lines. """
801 """Handle alias input lines. """
836 transformed = self.alias_manager.expand_aliases(line_info.ifun,line_info.the_rest)
802 transformed = self.shell.alias_manager.expand_aliases(line_info.ifun,line_info.the_rest)
837 # pre is needed, because it carries the leading whitespace. Otherwise
803 # pre is needed, because it carries the leading whitespace. Otherwise
838 # aliases won't work in indented sections.
804 # aliases won't work in indented sections.
839 line_out = '%sget_ipython().system(%s)' % (line_info.pre_whitespace,
805 line_out = '%sget_ipython().system(%s)' % (line_info.pre_whitespace,
@@ -894,7 +860,7 class AutoHandler(PrefilterHandler):
894 esc_strings = List([ESC_PAREN, ESC_QUOTE, ESC_QUOTE2])
860 esc_strings = List([ESC_PAREN, ESC_QUOTE, ESC_QUOTE2])
895
861
896 def handle(self, line_info):
862 def handle(self, line_info):
897 """Hande lines which can be auto-executed, quoting if requested."""
863 """Handle lines which can be auto-executed, quoting if requested."""
898 line = line_info.line
864 line = line_info.line
899 ifun = line_info.ifun
865 ifun = line_info.ifun
900 the_rest = line_info.the_rest
866 the_rest = line_info.the_rest
@@ -77,7 +77,7 a
77 b
77 b
78
78
79 ip = get_ipython()
79 ip = get_ipython()
80 prd = ip.load_extension('pretty')
80 prd = ip.extension_manager.load_extension('pretty')
81 prd.for_type(A, a_pretty_printer)
81 prd.for_type(A, a_pretty_printer)
82 prd.for_type_by_name(B.__module__, B.__name__, b_pretty_printer)
82 prd.for_type_by_name(B.__module__, B.__name__, b_pretty_printer)
83
83
@@ -26,7 +26,7 from twisted.python import log
26
26
27 from IPython.config.loader import PyFileConfigLoader
27 from IPython.config.loader import PyFileConfigLoader
28 from IPython.core.application import Application, BaseAppConfigLoader
28 from IPython.core.application import Application, BaseAppConfigLoader
29 from IPython.core.component import Component
29 from IPython.config.configurable import Configurable
30 from IPython.core.crashhandler import CrashHandler
30 from IPython.core.crashhandler import CrashHandler
31 from IPython.core import release
31 from IPython.core import release
32 from IPython.utils.path import (
32 from IPython.utils.path import (
@@ -63,7 +63,7 class PIDFileError(Exception):
63 # Class for managing cluster directories
63 # Class for managing cluster directories
64 #-----------------------------------------------------------------------------
64 #-----------------------------------------------------------------------------
65
65
66 class ClusterDir(Component):
66 class ClusterDir(Configurable):
67 """An object to manage the cluster directory and its resources.
67 """An object to manage the cluster directory and its resources.
68
68
69 The cluster directory is used by :command:`ipcontroller`,
69 The cluster directory is used by :command:`ipcontroller`,
@@ -18,7 +18,7 configuration system.
18
18
19 import zope.interface as zi
19 import zope.interface as zi
20
20
21 from IPython.core.component import Component
21 from IPython.config.configurable import Configurable
22
22
23 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
24 # Code
24 # Code
@@ -29,7 +29,7 class IConfiguredObjectFactory(zi.Interface):
29 """I am a component that creates a configured object.
29 """I am a component that creates a configured object.
30
30
31 This class is useful if you want to configure a class that is not a
31 This class is useful if you want to configure a class that is not a
32 subclass of :class:`IPython.core.component.Component`.
32 subclass of :class:`IPython.config.configurable.Configurable`.
33 """
33 """
34
34
35 def __init__(config):
35 def __init__(config):
@@ -39,12 +39,12 class IConfiguredObjectFactory(zi.Interface):
39 """Return an instance of the configured object."""
39 """Return an instance of the configured object."""
40
40
41
41
42 class ConfiguredObjectFactory(Component):
42 class ConfiguredObjectFactory(Configurable):
43
43
44 zi.implements(IConfiguredObjectFactory)
44 zi.implements(IConfiguredObjectFactory)
45
45
46 def __init__(self, config):
46 def __init__(self, config):
47 super(ConfiguredObjectFactory, self).__init__(None, config=config)
47 super(ConfiguredObjectFactory, self).__init__(config=config)
48
48
49 def create(self):
49 def create(self):
50 raise NotImplementedError('create must be implemented in a subclass')
50 raise NotImplementedError('create must be implemented in a subclass')
@@ -63,17 +63,17 class IAdaptedConfiguredObjectFactory(zi.Interface):
63 """Return an instance of the adapted and configured object."""
63 """Return an instance of the adapted and configured object."""
64
64
65
65
66 class AdaptedConfiguredObjectFactory(Component):
66 class AdaptedConfiguredObjectFactory(Configurable):
67
67
68 # zi.implements(IAdaptedConfiguredObjectFactory)
68 # zi.implements(IAdaptedConfiguredObjectFactory)
69
69
70 def __init__(self, config, adaptee):
70 def __init__(self, config, adaptee):
71 # print
71 # print
72 # print "config pre:", config
72 # print "config pre:", config
73 super(AdaptedConfiguredObjectFactory, self).__init__(None, config=config)
73 super(AdaptedConfiguredObjectFactory, self).__init__(config=config)
74 # print
74 # print
75 # print "config post:", config
75 # print "config post:", config
76 self.adaptee = adaptee
76 self.adaptee = adaptee
77
77
78 def create(self):
78 def create(self):
79 raise NotImplementedError('create must be implemented in a subclass') No newline at end of file
79 raise NotImplementedError('create must be implemented in a subclass')
@@ -191,9 +191,9 class FCServiceFactory(AdaptedConfiguredObjectFactory):
191 """This class creates a tub with various services running in it.
191 """This class creates a tub with various services running in it.
192
192
193 The basic idea is that :meth:`create` returns a running :class:`Tub`
193 The basic idea is that :meth:`create` returns a running :class:`Tub`
194 instance that has a number of Foolscap references registered in it.
194 instance that has a number of Foolscap references registered in it. This
195 This class is a subclass of :class:`IPython.core.component.Component`
195 class is a subclass of :class:`IPython.config.configurable.Configurable`
196 so the IPython configuration and component system are used.
196 so the IPython configuration system is used.
197
197
198 Attributes
198 Attributes
199 ----------
199 ----------
@@ -19,7 +19,7 import os
19 import re
19 import re
20 import sys
20 import sys
21
21
22 from IPython.core.component import Component
22 from IPython.config.configurable import Configurable
23 from IPython.external import Itpl
23 from IPython.external import Itpl
24 from IPython.utils.traitlets import Str, Int, List, Unicode
24 from IPython.utils.traitlets import Str, Int, List, Unicode
25 from IPython.utils.path import get_ipython_module_path
25 from IPython.utils.path import get_ipython_module_path
@@ -77,7 +77,7 class UnknownStatus(LauncherError):
77 pass
77 pass
78
78
79
79
80 class BaseLauncher(Component):
80 class BaseLauncher(Configurable):
81 """An asbtraction for starting, stopping and signaling a process."""
81 """An asbtraction for starting, stopping and signaling a process."""
82
82
83 # In all of the launchers, the work_dir is where child processes will be
83 # In all of the launchers, the work_dir is where child processes will be
@@ -89,8 +89,8 class BaseLauncher(Component):
89 # the --work-dir option.
89 # the --work-dir option.
90 work_dir = Unicode(u'')
90 work_dir = Unicode(u'')
91
91
92 def __init__(self, work_dir, parent=None, name=None, config=None):
92 def __init__(self, work_dir, config=None):
93 super(BaseLauncher, self).__init__(parent, name, config)
93 super(BaseLauncher, self).__init__(config)
94 self.work_dir = work_dir
94 self.work_dir = work_dir
95 self.state = 'before' # can be before, running, after
95 self.state = 'before' # can be before, running, after
96 self.stop_deferreds = []
96 self.stop_deferreds = []
@@ -265,9 +265,9 class LocalProcessLauncher(BaseLauncher):
265 # spawnProcess.
265 # spawnProcess.
266 cmd_and_args = List([])
266 cmd_and_args = List([])
267
267
268 def __init__(self, work_dir, parent=None, name=None, config=None):
268 def __init__(self, work_dir, config=None):
269 super(LocalProcessLauncher, self).__init__(
269 super(LocalProcessLauncher, self).__init__(
270 work_dir, parent, name, config
270 work_dir, config
271 )
271 )
272 self.process_protocol = None
272 self.process_protocol = None
273 self.start_deferred = None
273 self.start_deferred = None
@@ -356,9 +356,9 class LocalEngineSetLauncher(BaseLauncher):
356 ['--log-to-file','--log-level', '40'], config=True
356 ['--log-to-file','--log-level', '40'], config=True
357 )
357 )
358
358
359 def __init__(self, work_dir, parent=None, name=None, config=None):
359 def __init__(self, work_dir, config=None):
360 super(LocalEngineSetLauncher, self).__init__(
360 super(LocalEngineSetLauncher, self).__init__(
361 work_dir, parent, name, config
361 work_dir, config
362 )
362 )
363 self.launchers = []
363 self.launchers = []
364
364
@@ -367,7 +367,7 class LocalEngineSetLauncher(BaseLauncher):
367 self.cluster_dir = unicode(cluster_dir)
367 self.cluster_dir = unicode(cluster_dir)
368 dlist = []
368 dlist = []
369 for i in range(n):
369 for i in range(n):
370 el = LocalEngineLauncher(self.work_dir, self)
370 el = LocalEngineLauncher(self.work_dir, self.config)
371 # Copy the engine args over to each engine launcher.
371 # Copy the engine args over to each engine launcher.
372 import copy
372 import copy
373 el.engine_args = copy.deepcopy(self.engine_args)
373 el.engine_args = copy.deepcopy(self.engine_args)
@@ -560,9 +560,9 class WindowsHPCLauncher(BaseLauncher):
560 scheduler = Str('', config=True)
560 scheduler = Str('', config=True)
561 job_cmd = Str(find_job_cmd(), config=True)
561 job_cmd = Str(find_job_cmd(), config=True)
562
562
563 def __init__(self, work_dir, parent=None, name=None, config=None):
563 def __init__(self, work_dir, config=None):
564 super(WindowsHPCLauncher, self).__init__(
564 super(WindowsHPCLauncher, self).__init__(
565 work_dir, parent, name, config
565 work_dir, config
566 )
566 )
567
567
568 @property
568 @property
@@ -725,9 +725,9 class BatchSystemLauncher(BaseLauncher):
725 # The full path to the instantiated batch script.
725 # The full path to the instantiated batch script.
726 batch_file = Unicode(u'')
726 batch_file = Unicode(u'')
727
727
728 def __init__(self, work_dir, parent=None, name=None, config=None):
728 def __init__(self, work_dir, config=None):
729 super(BatchSystemLauncher, self).__init__(
729 super(BatchSystemLauncher, self).__init__(
730 work_dir, parent, name, config
730 work_dir, config
731 )
731 )
732 self.batch_file = os.path.join(self.work_dir, self.batch_file_name)
732 self.batch_file = os.path.join(self.work_dir, self.batch_file_name)
733 self.context = {}
733 self.context = {}
@@ -24,14 +24,14 import uuid
24
24
25 from xml.etree import ElementTree as ET
25 from xml.etree import ElementTree as ET
26
26
27 from IPython.core.component import Component
27 from IPython.config.configurable import Configurable
28 from IPython.utils.traitlets import (
28 from IPython.utils.traitlets import (
29 Str, Int, List, Instance,
29 Str, Int, List, Instance,
30 Enum, Bool, CStr
30 Enum, Bool, CStr
31 )
31 )
32
32
33 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
34 # Job and Task Component
34 # Job and Task classes
35 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
36
36
37
37
@@ -74,7 +74,7 def find_username():
74 return '%s\\%s' % (domain, username)
74 return '%s\\%s' % (domain, username)
75
75
76
76
77 class WinHPCJob(Component):
77 class WinHPCJob(Configurable):
78
78
79 job_id = Str('')
79 job_id = Str('')
80 job_name = Str('MyJob', config=True)
80 job_name = Str('MyJob', config=True)
@@ -165,7 +165,7 class WinHPCJob(Component):
165 self.tasks.append(task)
165 self.tasks.append(task)
166
166
167
167
168 class WinHPCTask(Component):
168 class WinHPCTask(Configurable):
169
169
170 task_id = Str('')
170 task_id = Str('')
171 task_name = Str('')
171 task_name = Str('')
@@ -261,8 +261,8 class IPControllerTask(WinHPCTask):
261 unit_type = Str("Core", config=False)
261 unit_type = Str("Core", config=False)
262 work_directory = CStr('', config=False)
262 work_directory = CStr('', config=False)
263
263
264 def __init__(self, parent, name=None, config=None):
264 def __init__(self, config=None):
265 super(IPControllerTask, self).__init__(parent, name, config)
265 super(IPControllerTask, self).__init__(config)
266 the_uuid = uuid.uuid1()
266 the_uuid = uuid.uuid1()
267 self.std_out_file_path = os.path.join('log','ipcontroller-%s.out' % the_uuid)
267 self.std_out_file_path = os.path.join('log','ipcontroller-%s.out' % the_uuid)
268 self.std_err_file_path = os.path.join('log','ipcontroller-%s.err' % the_uuid)
268 self.std_err_file_path = os.path.join('log','ipcontroller-%s.err' % the_uuid)
@@ -289,8 +289,8 class IPEngineTask(WinHPCTask):
289 unit_type = Str("Core", config=False)
289 unit_type = Str("Core", config=False)
290 work_directory = CStr('', config=False)
290 work_directory = CStr('', config=False)
291
291
292 def __init__(self, parent, name=None, config=None):
292 def __init__(self, config=None):
293 super(IPEngineTask,self).__init__(parent, name, config)
293 super(IPEngineTask,self).__init__(config)
294 the_uuid = uuid.uuid1()
294 the_uuid = uuid.uuid1()
295 self.std_out_file_path = os.path.join('log','ipengine-%s.out' % the_uuid)
295 self.std_out_file_path = os.path.join('log','ipengine-%s.out' % the_uuid)
296 self.std_err_file_path = os.path.join('log','ipengine-%s.err' % the_uuid)
296 self.std_err_file_path = os.path.join('log','ipengine-%s.err' % the_uuid)
@@ -1,5 +0,0
1 #!/usr/bin/env python
2 # encoding: utf-8
3
4
5
@@ -1,346 +0,0
1 #!/usr/bin/env python
2 # encoding: utf-8
3 """
4 A lightweight component system for IPython.
5
6 Authors:
7
8 * Brian Granger
9 * Fernando Perez
10 """
11
12 #-----------------------------------------------------------------------------
13 # Copyright (C) 2008-2009 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 IPython.config.loader import Config
29 from IPython.utils.traitlets import (
30 HasTraits, MetaHasTraits, Instance, This
31 )
32
33
34 #-----------------------------------------------------------------------------
35 # Helper classes for Components
36 #-----------------------------------------------------------------------------
37
38
39 class ComponentError(Exception):
40 pass
41
42 class MetaComponentTracker(type):
43 """A metaclass that tracks instances of Components and its subclasses."""
44
45 def __init__(cls, name, bases, d):
46 super(MetaComponentTracker, cls).__init__(name, bases, d)
47 cls.__instance_refs = WeakValueDictionary()
48 cls.__numcreated = 0
49
50 def __call__(cls, *args, **kw):
51 """Called when a class is called (instantiated)!!!
52
53 When a Component or subclass is instantiated, this is called and
54 the instance is saved in a WeakValueDictionary for tracking.
55 """
56 instance = cls.__new__(cls, *args, **kw)
57
58 # Register the instance before __init__ is called so get_instances
59 # works inside __init__ methods!
60 indices = cls.register_instance(instance)
61
62 # This is in a try/except because of the __init__ method fails, the
63 # instance is discarded and shouldn't be tracked.
64 try:
65 if isinstance(instance, cls):
66 cls.__init__(instance, *args, **kw)
67 except:
68 # Unregister the instance because __init__ failed!
69 cls.unregister_instances(indices)
70 raise
71 else:
72 return instance
73
74 def register_instance(cls, instance):
75 """Register instance with cls and its subclasses."""
76 # indices is a list of the keys used to register the instance
77 # with. This list is needed if the instance needs to be unregistered.
78 indices = []
79 for c in cls.__mro__:
80 if issubclass(cls, c) and issubclass(c, Component):
81 c.__numcreated += 1
82 indices.append(c.__numcreated)
83 c.__instance_refs[c.__numcreated] = instance
84 else:
85 break
86 return indices
87
88 def unregister_instances(cls, indices):
89 """Unregister instance with cls and its subclasses."""
90 for c, index in zip(cls.__mro__, indices):
91 try:
92 del c.__instance_refs[index]
93 except KeyError:
94 pass
95
96 def clear_instances(cls):
97 """Clear all instances tracked by cls."""
98 cls.__instance_refs.clear()
99 cls.__numcreated = 0
100
101 def get_instances(cls, name=None, root=None, klass=None):
102 """Get all instances of cls and its subclasses.
103
104 Parameters
105 ----------
106 name : str
107 Limit to components with this name.
108 root : Component or subclass
109 Limit to components having this root.
110 klass : class or str
111 Limits to instances of the class or its subclasses. If a str
112 is given ut must be in the form 'foo.bar.MyClass'. The str
113 form of this argument is useful for forward declarations.
114 """
115 if klass is not None:
116 if isinstance(klass, basestring):
117 klass = import_item(klass)
118 # Limit search to instances of klass for performance
119 if issubclass(klass, Component):
120 return klass.get_instances(name=name, root=root)
121 instances = cls.__instance_refs.values()
122 if name is not None:
123 instances = [i for i in instances if i.name == name]
124 if klass is not None:
125 instances = [i for i in instances if isinstance(i, klass)]
126 if root is not None:
127 instances = [i for i in instances if i.root == root]
128 return instances
129
130 def get_instances_by_condition(cls, call, name=None, root=None,
131 klass=None):
132 """Get all instances of cls, i such that call(i)==True.
133
134 This also takes the ``name`` and ``root`` and ``classname``
135 arguments of :meth:`get_instance`
136 """
137 return [i for i in cls.get_instances(name, root, klass) if call(i)]
138
139
140 def masquerade_as(instance, cls):
141 """Let instance masquerade as an instance of cls.
142
143 Sometimes, such as in testing code, it is useful to let a class
144 masquerade as another. Python, being duck typed, allows this by
145 default. But, instances of components are tracked by their class type.
146
147 After calling this, ``cls.get_instances()`` will return ``instance``. This
148 does not, however, cause ``isinstance(instance, cls)`` to return ``True``.
149
150 Parameters
151 ----------
152 instance : an instance of a Component or Component subclass
153 The instance that will pretend to be a cls.
154 cls : subclass of Component
155 The Component subclass that instance will pretend to be.
156 """
157 cls.register_instance(instance)
158
159
160 class __ComponentNameGenerator(object):
161 """A Singleton to generate unique component names."""
162
163 def __init__(self, prefix):
164 self.prefix = prefix
165 self.i = 0
166
167 def __call__(self):
168 count = self.i
169 self.i += 1
170 return "%s%s" % (self.prefix, count)
171
172
173 ComponentNameGenerator = __ComponentNameGenerator('ipython.component')
174
175
176 class MetaComponent(MetaHasTraits, MetaComponentTracker):
177 pass
178
179
180 #-----------------------------------------------------------------------------
181 # Component implementation
182 #-----------------------------------------------------------------------------
183
184
185 class Component(HasTraits):
186
187 __metaclass__ = MetaComponent
188
189 # Traits are fun!
190 config = Instance(Config,(),{})
191 parent = This()
192 root = This()
193 created = None
194
195 def __init__(self, parent, name=None, config=None):
196 """Create a component given a parent and possibly and name and config.
197
198 Parameters
199 ----------
200 parent : Component subclass
201 The parent in the component graph. The parent is used
202 to get the root of the component graph.
203 name : str
204 The unique name of the component. If empty, then a unique
205 one will be autogenerated.
206 config : Config
207 If this is empty, self.config = parent.config, otherwise
208 self.config = config and root.config is ignored. This argument
209 should only be used to *override* the automatic inheritance of
210 parent.config. If a caller wants to modify parent.config
211 (not override), the caller should make a copy and change
212 attributes and then pass the copy to this argument.
213
214 Notes
215 -----
216 Subclasses of Component must call the :meth:`__init__` method of
217 :class:`Component` *before* doing anything else and using
218 :func:`super`::
219
220 class MyComponent(Component):
221 def __init__(self, parent, name=None, config=None):
222 super(MyComponent, self).__init__(parent, name, config)
223 # Then any other code you need to finish initialization.
224
225 This ensures that the :attr:`parent`, :attr:`name` and :attr:`config`
226 attributes are handled properly.
227 """
228 super(Component, self).__init__()
229 self._children = []
230 if name is None:
231 self.name = ComponentNameGenerator()
232 else:
233 self.name = name
234 self.root = self # This is the default, it is set when parent is set
235 self.parent = parent
236 if config is not None:
237 self.config = config
238 # We used to deepcopy, but for now we are trying to just save
239 # by reference. This *could* have side effects as all components
240 # will share config. In fact, I did find such a side effect in
241 # _config_changed below. If a config attribute value was a mutable type
242 # all instances of a component were getting the same copy, effectively
243 # making that a class attribute.
244 # self.config = deepcopy(config)
245 else:
246 if self.parent is not None:
247 self.config = self.parent.config
248 # We used to deepcopy, but for now we are trying to just save
249 # by reference. This *could* have side effects as all components
250 # will share config. In fact, I did find such a side effect in
251 # _config_changed below. If a config attribute value was a mutable type
252 # all instances of a component were getting the same copy, effectively
253 # making that a class attribute.
254 # self.config = deepcopy(self.parent.config)
255
256 self.created = datetime.datetime.now()
257
258 #-------------------------------------------------------------------------
259 # Static trait notifiations
260 #-------------------------------------------------------------------------
261
262 def _parent_changed(self, name, old, new):
263 if old is not None:
264 old._remove_child(self)
265 if new is not None:
266 new._add_child(self)
267
268 if new is None:
269 self.root = self
270 else:
271 self.root = new.root
272
273 def _root_changed(self, name, old, new):
274 if self.parent is None:
275 if not (new is self):
276 raise ComponentError("Root not self, but parent is None.")
277 else:
278 if not self.parent.root is new:
279 raise ComponentError("Error in setting the root attribute: "
280 "root != parent.root")
281
282 def _config_changed(self, name, old, new):
283 """Update all the class traits having ``config=True`` as metadata.
284
285 For any class trait with a ``config`` metadata attribute that is
286 ``True``, we update the trait with the value of the corresponding
287 config entry.
288 """
289 # Get all traits with a config metadata entry that is True
290 traits = self.traits(config=True)
291
292 # We auto-load config section for this class as well as any parent
293 # classes that are Component subclasses. This starts with Component
294 # and works down the mro loading the config for each section.
295 section_names = [cls.__name__ for cls in \
296 reversed(self.__class__.__mro__) if
297 issubclass(cls, Component) and issubclass(self.__class__, cls)]
298
299 for sname in section_names:
300 # Don't do a blind getattr as that would cause the config to
301 # dynamically create the section with name self.__class__.__name__.
302 if new._has_section(sname):
303 my_config = new[sname]
304 for k, v in traits.items():
305 # Don't allow traitlets with config=True to start with
306 # uppercase. Otherwise, they are confused with Config
307 # subsections. But, developers shouldn't have uppercase
308 # attributes anyways! (PEP 6)
309 if k[0].upper()==k[0] and not k.startswith('_'):
310 raise ComponentError('Component traitlets with '
311 'config=True must start with a lowercase so they are '
312 'not confused with Config subsections: %s.%s' % \
313 (self.__class__.__name__, k))
314 try:
315 # Here we grab the value from the config
316 # If k has the naming convention of a config
317 # section, it will be auto created.
318 config_value = my_config[k]
319 except KeyError:
320 pass
321 else:
322 # print "Setting %s.%s from %s.%s=%r" % \
323 # (self.__class__.__name__,k,sname,k,config_value)
324 # We have to do a deepcopy here if we don't deepcopy the entire
325 # config object. If we don't, a mutable config_value will be
326 # shared by all instances, effectively making it a class attribute.
327 setattr(self, k, deepcopy(config_value))
328
329 @property
330 def children(self):
331 """A list of all my child components."""
332 return self._children
333
334 def _remove_child(self, child):
335 """A private method for removing children components."""
336 if child in self._children:
337 index = self._children.index(child)
338 del self._children[index]
339
340 def _add_child(self, child):
341 """A private method for adding children components."""
342 if child not in self._children:
343 self._children.append(child)
344
345 def __repr__(self):
346 return "<%s('%s')>" % (self.__class__.__name__, self.name)
@@ -1,214 +0,0
1 #!/usr/bin/env python
2 # encoding: utf-8
3 """
4 Tests for IPython.core.component
5
6 Authors:
7
8 * Brian Granger
9 * Fernando Perez (design help)
10 """
11
12 #-----------------------------------------------------------------------------
13 # Copyright (C) 2008-2009 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.core.component import Component, ComponentError
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 TestComponentMeta(TestCase):
38
39 def test_get_instances(self):
40 class BaseComponent(Component):
41 pass
42 c1 = BaseComponent(None)
43 c2 = BaseComponent(c1)
44 self.assertEquals(BaseComponent.get_instances(),[c1,c2])
45
46 def test_get_instances_subclass(self):
47 class MyComponent(Component):
48 pass
49 class MyOtherComponent(MyComponent):
50 pass
51 c1 = MyComponent(None)
52 c2 = MyOtherComponent(c1)
53 c3 = MyOtherComponent(c2)
54 self.assertEquals(MyComponent.get_instances(), [c1, c2, c3])
55 self.assertEquals(MyOtherComponent.get_instances(), [c2, c3])
56
57 def test_get_instances_root(self):
58 class MyComponent(Component):
59 pass
60 class MyOtherComponent(MyComponent):
61 pass
62 c1 = MyComponent(None)
63 c2 = MyOtherComponent(c1)
64 c3 = MyOtherComponent(c2)
65 c4 = MyComponent(None)
66 c5 = MyComponent(c4)
67 self.assertEquals(MyComponent.get_instances(root=c1), [c1, c2, c3])
68 self.assertEquals(MyComponent.get_instances(root=c4), [c4, c5])
69
70
71 class TestComponent(TestCase):
72
73 def test_parent_child(self):
74 c1 = Component(None)
75 c2 = Component(c1)
76 c3 = Component(c1)
77 c4 = Component(c3)
78 self.assertEquals(c1.parent, None)
79 self.assertEquals(c2.parent, c1)
80 self.assertEquals(c3.parent, c1)
81 self.assertEquals(c4.parent, c3)
82 self.assertEquals(c1.children, [c2, c3])
83 self.assertEquals(c2.children, [])
84 self.assertEquals(c3.children, [c4])
85 self.assertEquals(c4.children, [])
86
87 def test_root(self):
88 c1 = Component(None)
89 c2 = Component(c1)
90 c3 = Component(c1)
91 c4 = Component(c3)
92 self.assertEquals(c1.root, c1.root)
93 self.assertEquals(c2.root, c1)
94 self.assertEquals(c3.root, c1)
95 self.assertEquals(c4.root, c1)
96
97 def test_change_parent(self):
98 c1 = Component(None)
99 c2 = Component(None)
100 c3 = Component(c1)
101 self.assertEquals(c3.root, c1)
102 self.assertEquals(c3.parent, c1)
103 self.assertEquals(c1.children,[c3])
104 c3.parent = c2
105 self.assertEquals(c3.root, c2)
106 self.assertEquals(c3.parent, c2)
107 self.assertEquals(c2.children,[c3])
108 self.assertEquals(c1.children,[])
109
110 def test_subclass_parent(self):
111 c1 = Component(None)
112 self.assertRaises(TraitError, setattr, c1, 'parent', 10)
113
114 class MyComponent(Component):
115 pass
116 c1 = Component(None)
117 c2 = MyComponent(c1)
118 self.assertEquals(MyComponent.parent.this_class, Component)
119 self.assertEquals(c2.parent, c1)
120
121 def test_bad_root(self):
122 c1 = Component(None)
123 c2 = Component(None)
124 c3 = Component(None)
125 self.assertRaises(ComponentError, setattr, c1, 'root', c2)
126 c1.parent = c2
127 self.assertEquals(c1.root, c2)
128 self.assertRaises(ComponentError, setattr, c1, 'root', c3)
129
130
131 class TestComponentConfig(TestCase):
132
133 def test_default(self):
134 c1 = Component(None)
135 c2 = Component(c1)
136 c3 = Component(c2)
137 self.assertEquals(c1.config, c2.config)
138 self.assertEquals(c2.config, c3.config)
139
140 def test_custom(self):
141 config = Config()
142 config.foo = 'foo'
143 config.bar = 'bar'
144 c1 = Component(None, config=config)
145 c2 = Component(c1)
146 c3 = Component(c2)
147 self.assertEquals(c1.config, config)
148 self.assertEquals(c2.config, config)
149 self.assertEquals(c3.config, config)
150 # Test that copies are not made
151 self.assert_(c1.config is config)
152 self.assert_(c2.config is config)
153 self.assert_(c3.config is config)
154 self.assert_(c1.config is c2.config)
155 self.assert_(c2.config is c3.config)
156
157 def test_inheritance(self):
158 class MyComponent(Component):
159 a = Int(1, config=True)
160 b = Float(1.0, config=True)
161 c = Str('no config')
162 config = Config()
163 config.MyComponent.a = 2
164 config.MyComponent.b = 2.0
165 c1 = MyComponent(None, config=config)
166 c2 = MyComponent(c1)
167 self.assertEquals(c1.a, config.MyComponent.a)
168 self.assertEquals(c1.b, config.MyComponent.b)
169 self.assertEquals(c2.a, config.MyComponent.a)
170 self.assertEquals(c2.b, config.MyComponent.b)
171 c4 = MyComponent(c2, config=Config())
172 self.assertEquals(c4.a, 1)
173 self.assertEquals(c4.b, 1.0)
174
175 def test_parent(self):
176 class Foo(Component):
177 a = Int(0, config=True)
178 b = Str('nope', config=True)
179 class Bar(Foo):
180 b = Str('gotit', config=False)
181 c = Float(config=True)
182 config = Config()
183 config.Foo.a = 10
184 config.Foo.b = "wow"
185 config.Bar.b = 'later'
186 config.Bar.c = 100.0
187 f = Foo(None, config=config)
188 b = Bar(f)
189 self.assertEquals(f.a, 10)
190 self.assertEquals(f.b, 'wow')
191 self.assertEquals(b.b, 'gotit')
192 self.assertEquals(b.c, 100.0)
193
194
195 class TestComponentName(TestCase):
196
197 def test_default(self):
198 class MyComponent(Component):
199 pass
200 c1 = Component(None)
201 c2 = MyComponent(None)
202 c3 = Component(c2)
203 self.assertNotEquals(c1.name, c2.name)
204 self.assertNotEquals(c1.name, c3.name)
205
206 def test_manual(self):
207 class MyComponent(Component):
208 pass
209 c1 = Component(None, name='foo')
210 c2 = MyComponent(None, name='bar')
211 c3 = Component(c2, name='bah')
212 self.assertEquals(c1.name, 'foo')
213 self.assertEquals(c2.name, 'bar')
214 self.assertEquals(c3.name, 'bah')
General Comments 0
You need to be logged in to leave comments. Login now