Show More
@@ -0,0 +1,230 b'' | |||||
|
1 | # encoding: utf-8 | |||
|
2 | """ | |||
|
3 | A base class for a configurable application. | |||
|
4 | ||||
|
5 | Authors: | |||
|
6 | ||||
|
7 | * Brian Granger | |||
|
8 | """ | |||
|
9 | ||||
|
10 | #----------------------------------------------------------------------------- | |||
|
11 | # Copyright (C) 2008-2011 The IPython Development Team | |||
|
12 | # | |||
|
13 | # Distributed under the terms of the BSD License. The full license is in | |||
|
14 | # the file COPYING, distributed as part of this software. | |||
|
15 | #----------------------------------------------------------------------------- | |||
|
16 | ||||
|
17 | #----------------------------------------------------------------------------- | |||
|
18 | # Imports | |||
|
19 | #----------------------------------------------------------------------------- | |||
|
20 | ||||
|
21 | from copy import deepcopy | |||
|
22 | import logging | |||
|
23 | import sys | |||
|
24 | ||||
|
25 | from IPython.config.configurable import SingletonConfigurable | |||
|
26 | from IPython.config.loader import ( | |||
|
27 | KeyValueConfigLoader, PyFileConfigLoader, Config | |||
|
28 | ) | |||
|
29 | ||||
|
30 | from IPython.utils.traitlets import ( | |||
|
31 | Unicode, List, Int, Enum, Dict | |||
|
32 | ) | |||
|
33 | from IPython.utils.text import indent | |||
|
34 | ||||
|
35 | #----------------------------------------------------------------------------- | |||
|
36 | # Descriptions for | |||
|
37 | #----------------------------------------------------------------------------- | |||
|
38 | ||||
|
39 | flag_description = """ | |||
|
40 | Flags are command-line arguments passed as '--<flag>'. | |||
|
41 | These take no parameters, unlike regular key-value arguments. | |||
|
42 | They are typically used for setting boolean flags, or enabling | |||
|
43 | modes that involve setting multiple options together. | |||
|
44 | """.strip() # trim newlines of front and back | |||
|
45 | ||||
|
46 | alias_description = """ | |||
|
47 | These are commonly set parameters, given abbreviated aliases for convenience. | |||
|
48 | They are set in the same `name=value` way as class parameters, where | |||
|
49 | <name> is replaced by the real parameter for which it is an alias. | |||
|
50 | """.strip() # trim newlines of front and back | |||
|
51 | ||||
|
52 | keyvalue_description = """ | |||
|
53 | Parameters are set from command-line arguments of the form: | |||
|
54 | `Class.trait=value`. Parameters will *never* be prefixed with '-'. | |||
|
55 | This line is evaluated in Python, so simple expressions are allowed, e.g. | |||
|
56 | `C.a='range(3)'` For setting C.a=[0,1,2] | |||
|
57 | """.strip() # trim newlines of front and back | |||
|
58 | ||||
|
59 | #----------------------------------------------------------------------------- | |||
|
60 | # Application class | |||
|
61 | #----------------------------------------------------------------------------- | |||
|
62 | ||||
|
63 | ||||
|
64 | class Application(SingletonConfigurable): | |||
|
65 | """A singleton application with full configuration support.""" | |||
|
66 | ||||
|
67 | # The name of the application, will usually match the name of the command | |||
|
68 | # line application | |||
|
69 | app_name = Unicode(u'application') | |||
|
70 | ||||
|
71 | # The description of the application that is printed at the beginning | |||
|
72 | # of the help. | |||
|
73 | description = Unicode(u'This is an application.') | |||
|
74 | # default section descriptions | |||
|
75 | flag_description = Unicode(flag_description) | |||
|
76 | alias_description = Unicode(alias_description) | |||
|
77 | keyvalue_description = Unicode(keyvalue_description) | |||
|
78 | ||||
|
79 | ||||
|
80 | # A sequence of Configurable subclasses whose config=True attributes will | |||
|
81 | # be exposed at the command line. | |||
|
82 | classes = List([]) | |||
|
83 | ||||
|
84 | # The version string of this application. | |||
|
85 | version = Unicode(u'0.0') | |||
|
86 | ||||
|
87 | # The log level for the application | |||
|
88 | log_level = Enum((0,10,20,30,40,50), default_value=logging.WARN, | |||
|
89 | config=True, | |||
|
90 | help="Set the log level (0,10,20,30,40,50).") | |||
|
91 | ||||
|
92 | # the alias map for configurables | |||
|
93 | aliases = Dict(dict(log_level='Application.log_level')) | |||
|
94 | ||||
|
95 | # flags for loading Configurables or store_const style flags | |||
|
96 | # flags are loaded from this dict by '--key' flags | |||
|
97 | # this must be a dict of two-tuples, the first element being the Config/dict | |||
|
98 | # and the second being the help string for the flag | |||
|
99 | flags = Dict() | |||
|
100 | ||||
|
101 | ||||
|
102 | def __init__(self, **kwargs): | |||
|
103 | SingletonConfigurable.__init__(self, **kwargs) | |||
|
104 | # Add my class to self.classes so my attributes appear in command line | |||
|
105 | # options. | |||
|
106 | self.classes.insert(0, self.__class__) | |||
|
107 | ||||
|
108 | # ensure self.flags dict is valid | |||
|
109 | for key,value in self.flags.iteritems(): | |||
|
110 | assert len(value) == 2, "Bad flag: %r:%s"%(key,value) | |||
|
111 | assert isinstance(value[0], (dict, Config)), "Bad flag: %r:%s"%(key,value) | |||
|
112 | assert isinstance(value[1], basestring), "Bad flag: %r:%s"%(key,value) | |||
|
113 | self.init_logging() | |||
|
114 | ||||
|
115 | def init_logging(self): | |||
|
116 | """Start logging for this application. | |||
|
117 | ||||
|
118 | The default is to log to stdout using a StreaHandler. The log level | |||
|
119 | starts at loggin.WARN, but this can be adjusted by setting the | |||
|
120 | ``log_level`` attribute. | |||
|
121 | """ | |||
|
122 | self.log = logging.getLogger(self.__class__.__name__) | |||
|
123 | self.log.setLevel(self.log_level) | |||
|
124 | self._log_handler = logging.StreamHandler() | |||
|
125 | self._log_formatter = logging.Formatter("[%(name)s] %(message)s") | |||
|
126 | self._log_handler.setFormatter(self._log_formatter) | |||
|
127 | self.log.addHandler(self._log_handler) | |||
|
128 | ||||
|
129 | def _log_level_changed(self, name, old, new): | |||
|
130 | """Adjust the log level when log_level is set.""" | |||
|
131 | self.log.setLevel(new) | |||
|
132 | ||||
|
133 | def print_alias_help(self): | |||
|
134 | """print the alias part of the help""" | |||
|
135 | if not self.aliases: | |||
|
136 | return | |||
|
137 | ||||
|
138 | print "Aliases" | |||
|
139 | print "-------" | |||
|
140 | print self.alias_description | |||
|
141 | ||||
|
142 | ||||
|
143 | classdict = {} | |||
|
144 | for c in self.classes: | |||
|
145 | classdict[c.__name__] = c | |||
|
146 | ||||
|
147 | for alias, longname in self.aliases.iteritems(): | |||
|
148 | classname, traitname = longname.split('.',1) | |||
|
149 | cls = classdict[classname] | |||
|
150 | ||||
|
151 | trait = cls.class_traits(config=True)[traitname] | |||
|
152 | help = trait.get_metadata('help') | |||
|
153 | print alias, "(%s)"%longname, ':', trait.__class__.__name__ | |||
|
154 | if help: | |||
|
155 | print indent(help) | |||
|
156 | ||||
|
157 | ||||
|
158 | def print_flag_help(self): | |||
|
159 | """print the flag part of the help""" | |||
|
160 | if not self.flags: | |||
|
161 | return | |||
|
162 | ||||
|
163 | print "Flags" | |||
|
164 | print "-----" | |||
|
165 | print self.flag_description | |||
|
166 | ||||
|
167 | ||||
|
168 | for m, (cfg,help) in self.flags.iteritems(): | |||
|
169 | print '--'+m | |||
|
170 | print indent(help) | |||
|
171 | ||||
|
172 | ||||
|
173 | def print_help(self): | |||
|
174 | """Print the help for each Configurable class in self.classes.""" | |||
|
175 | self.print_flag_help() | |||
|
176 | self.print_alias_help() | |||
|
177 | if self.classes: | |||
|
178 | print "Class parameters" | |||
|
179 | print "----------------" | |||
|
180 | print self.keyvalue_description | |||
|
181 | ||||
|
182 | ||||
|
183 | for cls in self.classes: | |||
|
184 | cls.class_print_help() | |||
|
185 | ||||
|
186 | ||||
|
187 | def print_description(self): | |||
|
188 | """Print the application description.""" | |||
|
189 | print self.description | |||
|
190 | ||||
|
191 | ||||
|
192 | def print_version(self): | |||
|
193 | """Print the version string.""" | |||
|
194 | print self.version | |||
|
195 | ||||
|
196 | def update_config(self, config): | |||
|
197 | """Fire the traits events when the config is updated.""" | |||
|
198 | # Save a copy of the current config. | |||
|
199 | newconfig = deepcopy(self.config) | |||
|
200 | # Merge the new config into the current one. | |||
|
201 | newconfig._merge(config) | |||
|
202 | # Save the combined config as self.config, which triggers the traits | |||
|
203 | # events. | |||
|
204 | self.config = config | |||
|
205 | ||||
|
206 | def parse_command_line(self, argv=None): | |||
|
207 | """Parse the command line arguments.""" | |||
|
208 | argv = sys.argv[1:] if argv is None else argv | |||
|
209 | ||||
|
210 | if '-h' in argv or '--help' in argv: | |||
|
211 | self.print_description() | |||
|
212 | self.print_help() | |||
|
213 | sys.exit(1) | |||
|
214 | ||||
|
215 | if '--version' in argv: | |||
|
216 | self.print_version() | |||
|
217 | sys.exit(1) | |||
|
218 | ||||
|
219 | loader = KeyValueConfigLoader(argv=argv, aliases=self.aliases, | |||
|
220 | flags=self.flags) | |||
|
221 | config = loader.load_config() | |||
|
222 | self.update_config(config) | |||
|
223 | ||||
|
224 | def load_config_file(self, filename, path=None): | |||
|
225 | """Load a .py based config file by filename and path.""" | |||
|
226 | # TODO: this raises IOError if filename does not exist. | |||
|
227 | loader = PyFileConfigLoader(filename, path=path) | |||
|
228 | config = loader.load_config() | |||
|
229 | self.update_config(config) | |||
|
230 |
@@ -0,0 +1,105 b'' | |||||
|
1 | """ | |||
|
2 | Tests for IPython.config.application.Application | |||
|
3 | ||||
|
4 | Authors: | |||
|
5 | ||||
|
6 | * Brian Granger | |||
|
7 | """ | |||
|
8 | ||||
|
9 | #----------------------------------------------------------------------------- | |||
|
10 | # Copyright (C) 2008-2011 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 | from unittest import TestCase | |||
|
21 | ||||
|
22 | from IPython.config.configurable import Configurable | |||
|
23 | ||||
|
24 | from IPython.config.application import ( | |||
|
25 | Application | |||
|
26 | ) | |||
|
27 | ||||
|
28 | from IPython.utils.traitlets import ( | |||
|
29 | Bool, Unicode, Int, Float, List, Dict | |||
|
30 | ) | |||
|
31 | ||||
|
32 | #----------------------------------------------------------------------------- | |||
|
33 | # Code | |||
|
34 | #----------------------------------------------------------------------------- | |||
|
35 | ||||
|
36 | class Foo(Configurable): | |||
|
37 | ||||
|
38 | i = Int(0, config=True, help="The integer i.") | |||
|
39 | j = Int(1, config=True, help="The integer j.") | |||
|
40 | name = Unicode(u'Brian', config=True, help="First name.") | |||
|
41 | ||||
|
42 | ||||
|
43 | class Bar(Configurable): | |||
|
44 | ||||
|
45 | enabled = Bool(True, config=True, help="Enable bar.") | |||
|
46 | ||||
|
47 | ||||
|
48 | class MyApp(Application): | |||
|
49 | ||||
|
50 | app_name = Unicode(u'myapp') | |||
|
51 | running = Bool(False, config=True, | |||
|
52 | help="Is the app running?") | |||
|
53 | classes = List([Bar, Foo]) | |||
|
54 | config_file = Unicode(u'', config=True, | |||
|
55 | help="Load this config file") | |||
|
56 | ||||
|
57 | aliases = Dict(dict(i='Foo.i',j='Foo.j',name='Foo.name', | |||
|
58 | enabled='Bar.enabled', log_level='MyApp.log_level')) | |||
|
59 | ||||
|
60 | flags = Dict(dict(enable=({'Bar': {'enabled' : True}}, "Set Bar.enabled to True"), | |||
|
61 | disable=({'Bar': {'enabled' : False}}, "Set Bar.enabled to False"))) | |||
|
62 | ||||
|
63 | def init_foo(self): | |||
|
64 | self.foo = Foo(config=self.config) | |||
|
65 | ||||
|
66 | def init_bar(self): | |||
|
67 | self.bar = Bar(config=self.config) | |||
|
68 | ||||
|
69 | ||||
|
70 | class TestApplication(TestCase): | |||
|
71 | ||||
|
72 | def test_basic(self): | |||
|
73 | app = MyApp() | |||
|
74 | self.assertEquals(app.app_name, u'myapp') | |||
|
75 | self.assertEquals(app.running, False) | |||
|
76 | self.assertEquals(app.classes, [MyApp,Bar,Foo]) | |||
|
77 | self.assertEquals(app.config_file, u'') | |||
|
78 | ||||
|
79 | def test_config(self): | |||
|
80 | app = MyApp() | |||
|
81 | app.parse_command_line(["i=10","Foo.j=10","enabled=False","log_level=0"]) | |||
|
82 | config = app.config | |||
|
83 | self.assertEquals(config.Foo.i, 10) | |||
|
84 | self.assertEquals(config.Foo.j, 10) | |||
|
85 | self.assertEquals(config.Bar.enabled, False) | |||
|
86 | self.assertEquals(config.MyApp.log_level,0) | |||
|
87 | ||||
|
88 | def test_config_propagation(self): | |||
|
89 | app = MyApp() | |||
|
90 | app.parse_command_line(["i=10","Foo.j=10","enabled=False","log_level=0"]) | |||
|
91 | app.init_foo() | |||
|
92 | app.init_bar() | |||
|
93 | self.assertEquals(app.foo.i, 10) | |||
|
94 | self.assertEquals(app.foo.j, 10) | |||
|
95 | self.assertEquals(app.bar.enabled, False) | |||
|
96 | ||||
|
97 | def test_alias(self): | |||
|
98 | app = MyApp() | |||
|
99 | app.parse_command_line(["--disable"]) | |||
|
100 | app.init_bar() | |||
|
101 | self.assertEquals(app.bar.enabled, False) | |||
|
102 | app.parse_command_line(["--enable"]) | |||
|
103 | app.init_bar() | |||
|
104 | self.assertEquals(app.bar.enabled, True) | |||
|
105 |
@@ -0,0 +1,96 b'' | |||||
|
1 | """A simple example of how to use IPython.config.application.Application. | |||
|
2 | ||||
|
3 | This should serve as a simple example that shows how the IPython config | |||
|
4 | system works. The main classes are: | |||
|
5 | ||||
|
6 | * IPython.config.configurable.Configurable | |||
|
7 | * IPython.config.configurable.SingletonConfigurable | |||
|
8 | * IPython.config.loader.Config | |||
|
9 | * IPython.config.application.Application | |||
|
10 | ||||
|
11 | To see the command line option help, run this program from the command line:: | |||
|
12 | ||||
|
13 | $ python appconfig.py -h | |||
|
14 | ||||
|
15 | To make one of your classes configurable (from the command line and config | |||
|
16 | files) inherit from Configurable and declare class attributes as traits (see | |||
|
17 | classes Foo and Bar below). To make the traits configurable, you will need | |||
|
18 | to set the following options: | |||
|
19 | ||||
|
20 | * ``config``: set to ``True`` to make the attribute configurable. | |||
|
21 | * ``shortname``: by default, configurable attributes are set using the syntax | |||
|
22 | "Classname.attributename". At the command line, this is a bit verbose, so | |||
|
23 | we allow "shortnames" to be declared. Setting a shortname is optional, but | |||
|
24 | when you do this, you can set the option at the command line using the | |||
|
25 | syntax: "shortname=value". | |||
|
26 | * ``help``: set the help string to display a help message when the ``-h`` | |||
|
27 | option is given at the command line. The help string should be valid ReST. | |||
|
28 | ||||
|
29 | When the config attribute of an Application is updated, it will fire all of | |||
|
30 | the trait's events for all of the config=True attributes. | |||
|
31 | """ | |||
|
32 | ||||
|
33 | import sys | |||
|
34 | ||||
|
35 | from IPython.config.configurable import Configurable | |||
|
36 | from IPython.config.application import Application | |||
|
37 | from IPython.utils.traitlets import ( | |||
|
38 | Bool, Unicode, Int, Float, List, Dict | |||
|
39 | ) | |||
|
40 | ||||
|
41 | ||||
|
42 | class Foo(Configurable): | |||
|
43 | """A class that has configurable, typed attributes. | |||
|
44 | ||||
|
45 | """ | |||
|
46 | ||||
|
47 | i = Int(0, config=True, help="The integer i.") | |||
|
48 | j = Int(1, config=True, help="The integer j.") | |||
|
49 | name = Unicode(u'Brian', config=True, help="First name.") | |||
|
50 | ||||
|
51 | ||||
|
52 | class Bar(Configurable): | |||
|
53 | ||||
|
54 | enabled = Bool(True, config=True, help="Enable bar.") | |||
|
55 | ||||
|
56 | ||||
|
57 | class MyApp(Application): | |||
|
58 | ||||
|
59 | app_name = Unicode(u'myapp') | |||
|
60 | running = Bool(False, config=True, | |||
|
61 | help="Is the app running?") | |||
|
62 | classes = List([Bar, Foo]) | |||
|
63 | config_file = Unicode(u'', config=True, | |||
|
64 | help="Load this config file") | |||
|
65 | ||||
|
66 | aliases = Dict(dict(i='Foo.i',j='Foo.j',name='Foo.name', running='MyApp.running', | |||
|
67 | enabled='Bar.enabled', log_level='MyApp.log_level')) | |||
|
68 | ||||
|
69 | flags = Dict(dict(enable=({'Bar': {'enabled' : True}}, "Enable Bar"), | |||
|
70 | disable=({'Bar': {'enabled' : False}}, "Disable Bar"), | |||
|
71 | debug=({'MyApp':{'log_level':10}}, "Set loglevel to DEBUG") | |||
|
72 | )) | |||
|
73 | ||||
|
74 | def init_foo(self): | |||
|
75 | # Pass config to other classes for them to inherit the config. | |||
|
76 | self.foo = Foo(config=self.config) | |||
|
77 | ||||
|
78 | def init_bar(self): | |||
|
79 | # Pass config to other classes for them to inherit the config. | |||
|
80 | self.bar = Bar(config=self.config) | |||
|
81 | ||||
|
82 | ||||
|
83 | ||||
|
84 | def main(): | |||
|
85 | app = MyApp() | |||
|
86 | app.parse_command_line() | |||
|
87 | if app.config_file: | |||
|
88 | app.load_config_file(app.config_file) | |||
|
89 | app.init_foo() | |||
|
90 | app.init_bar() | |||
|
91 | print "app.config:" | |||
|
92 | print app.config | |||
|
93 | ||||
|
94 | ||||
|
95 | if __name__ == "__main__": | |||
|
96 | main() |
@@ -22,11 +22,10 b' Authors:' | |||||
22 |
|
22 | |||
23 | from copy import deepcopy |
|
23 | from copy import deepcopy | |
24 | import datetime |
|
24 | import datetime | |
25 | from weakref import WeakValueDictionary |
|
|||
26 |
|
25 | |||
27 | from IPython.utils.importstring import import_item |
|
|||
28 | from loader import Config |
|
26 | from loader import Config | |
29 | from IPython.utils.traitlets import HasTraits, Instance |
|
27 | from IPython.utils.traitlets import HasTraits, Instance | |
|
28 | from IPython.utils.text import indent | |||
30 |
|
29 | |||
31 |
|
30 | |||
32 | #----------------------------------------------------------------------------- |
|
31 | #----------------------------------------------------------------------------- | |
@@ -38,6 +37,9 b' class ConfigurableError(Exception):' | |||||
38 | pass |
|
37 | pass | |
39 |
|
38 | |||
40 |
|
39 | |||
|
40 | class MultipleInstanceError(ConfigurableError): | |||
|
41 | pass | |||
|
42 | ||||
41 | #----------------------------------------------------------------------------- |
|
43 | #----------------------------------------------------------------------------- | |
42 | # Configurable implementation |
|
44 | # Configurable implementation | |
43 | #----------------------------------------------------------------------------- |
|
45 | #----------------------------------------------------------------------------- | |
@@ -137,3 +139,87 b' class Configurable(HasTraits):' | |||||
137 | # shared by all instances, effectively making it a class attribute. |
|
139 | # shared by all instances, effectively making it a class attribute. | |
138 | setattr(self, k, deepcopy(config_value)) |
|
140 | setattr(self, k, deepcopy(config_value)) | |
139 |
|
141 | |||
|
142 | @classmethod | |||
|
143 | def class_get_help(cls): | |||
|
144 | """Get the help string for this class in ReST format.""" | |||
|
145 | cls_traits = cls.class_traits(config=True) | |||
|
146 | final_help = [] | |||
|
147 | final_help.append(u'%s options' % cls.__name__) | |||
|
148 | final_help.append(len(final_help[0])*u'-') | |||
|
149 | for k, v in cls_traits.items(): | |||
|
150 | help = v.get_metadata('help') | |||
|
151 | header = "%s.%s : %s" % (cls.__name__, k, v.__class__.__name__) | |||
|
152 | final_help.append(header) | |||
|
153 | if help is not None: | |||
|
154 | final_help.append(indent(help)) | |||
|
155 | return '\n'.join(final_help) | |||
|
156 | ||||
|
157 | @classmethod | |||
|
158 | def class_print_help(cls): | |||
|
159 | print cls.class_get_help() | |||
|
160 | ||||
|
161 | ||||
|
162 | class SingletonConfigurable(Configurable): | |||
|
163 | """A configurable that only allows one instance. | |||
|
164 | ||||
|
165 | This class is for classes that should only have one instance of itself | |||
|
166 | or *any* subclass. To create and retrieve such a class use the | |||
|
167 | :meth:`SingletonConfigurable.instance` method. | |||
|
168 | """ | |||
|
169 | ||||
|
170 | _instance = None | |||
|
171 | ||||
|
172 | @classmethod | |||
|
173 | def instance(cls, *args, **kwargs): | |||
|
174 | """Returns a global instance of this class. | |||
|
175 | ||||
|
176 | This method create a new instance if none have previously been created | |||
|
177 | and returns a previously created instance is one already exists. | |||
|
178 | ||||
|
179 | The arguments and keyword arguments passed to this method are passed | |||
|
180 | on to the :meth:`__init__` method of the class upon instantiation. | |||
|
181 | ||||
|
182 | Examples | |||
|
183 | -------- | |||
|
184 | ||||
|
185 | Create a singleton class using instance, and retrieve it:: | |||
|
186 | ||||
|
187 | >>> from IPython.config.configurable import SingletonConfigurable | |||
|
188 | >>> class Foo(SingletonConfigurable): pass | |||
|
189 | >>> foo = Foo.instance() | |||
|
190 | >>> foo == Foo.instance() | |||
|
191 | True | |||
|
192 | ||||
|
193 | Create a subclass that is retrived using the base class instance:: | |||
|
194 | ||||
|
195 | >>> class Bar(SingletonConfigurable): pass | |||
|
196 | >>> class Bam(Bar): pass | |||
|
197 | >>> bam = Bam.instance() | |||
|
198 | >>> bam == Bar.instance() | |||
|
199 | True | |||
|
200 | """ | |||
|
201 | # Create and save the instance | |||
|
202 | if cls._instance is None: | |||
|
203 | inst = cls(*args, **kwargs) | |||
|
204 | # Now make sure that the instance will also be returned by | |||
|
205 | # the subclasses instance attribute. | |||
|
206 | for subclass in cls.mro(): | |||
|
207 | if issubclass(cls, subclass) and \ | |||
|
208 | issubclass(subclass, SingletonConfigurable) and \ | |||
|
209 | subclass != SingletonConfigurable: | |||
|
210 | subclass._instance = inst | |||
|
211 | else: | |||
|
212 | break | |||
|
213 | if isinstance(cls._instance, cls): | |||
|
214 | return cls._instance | |||
|
215 | else: | |||
|
216 | raise MultipleInstanceError( | |||
|
217 | 'Multiple incompatible subclass instances of ' | |||
|
218 | '%s are being created.' % cls.__name__ | |||
|
219 | ) | |||
|
220 | ||||
|
221 | @classmethod | |||
|
222 | def initialized(cls): | |||
|
223 | """Has an instance been created?""" | |||
|
224 | return hasattr(cls, "_instance") and cls._instance is not None | |||
|
225 |
@@ -1,5 +1,3 b'' | |||||
1 | # -*- coding: utf-8 -*- |
|
|||
2 | # coding: utf-8 |
|
|||
3 |
|
|
1 | """A simple configuration system. | |
4 |
|
2 | |||
5 | Authors |
|
3 | Authors | |
@@ -20,7 +18,7 b' Authors' | |||||
20 | #----------------------------------------------------------------------------- |
|
18 | #----------------------------------------------------------------------------- | |
21 |
|
19 | |||
22 | import __builtin__ |
|
20 | import __builtin__ | |
23 |
import |
|
21 | import re | |
24 | import sys |
|
22 | import sys | |
25 |
|
23 | |||
26 | from IPython.external import argparse |
|
24 | from IPython.external import argparse | |
@@ -306,8 +304,117 b' class CommandLineConfigLoader(ConfigLoader):' | |||||
306 | here. |
|
304 | here. | |
307 | """ |
|
305 | """ | |
308 |
|
306 | |||
|
307 | kv_pattern = re.compile(r'[A-Za-z]\w*(\.\w+)*\=.+') | |||
|
308 | flag_pattern = re.compile(r'\-\-\w+(\-\w)*') | |||
|
309 | ||||
|
310 | class KeyValueConfigLoader(CommandLineConfigLoader): | |||
|
311 | """A config loader that loads key value pairs from the command line. | |||
|
312 | ||||
|
313 | This allows command line options to be gives in the following form:: | |||
|
314 | ||||
|
315 | ipython Global.profile="foo" InteractiveShell.autocall=False | |||
|
316 | """ | |||
|
317 | ||||
|
318 | def __init__(self, argv=None, aliases=None, flags=None): | |||
|
319 | """Create a key value pair config loader. | |||
|
320 | ||||
|
321 | Parameters | |||
|
322 | ---------- | |||
|
323 | argv : list | |||
|
324 | A list that has the form of sys.argv[1:] which has unicode | |||
|
325 | elements of the form u"key=value". If this is None (default), | |||
|
326 | then sys.argv[1:] will be used. | |||
|
327 | aliases : dict | |||
|
328 | A dict of aliases for configurable traits. | |||
|
329 | Keys are the short aliases, Values are the resolved trait. | |||
|
330 | Of the form: `{'alias' : 'Configurable.trait'}` | |||
|
331 | flags : dict | |||
|
332 | A dict of flags, keyed by str name. Vaues can be Config objects, | |||
|
333 | dicts, or "key=value" strings. If Config or dict, when the flag | |||
|
334 | is triggered, The flag is loaded as `self.config.update(m)`. | |||
|
335 | ||||
|
336 | Returns | |||
|
337 | ------- | |||
|
338 | config : Config | |||
|
339 | The resulting Config object. | |||
|
340 | ||||
|
341 | Examples | |||
|
342 | -------- | |||
|
343 | ||||
|
344 | >>> from IPython.config.loader import KeyValueConfigLoader | |||
|
345 | >>> cl = KeyValueConfigLoader() | |||
|
346 | >>> cl.load_config(["foo='bar'","A.name='brian'","B.number=0"]) | |||
|
347 | {'A': {'name': 'brian'}, 'B': {'number': 0}, 'foo': 'bar'} | |||
|
348 | """ | |||
|
349 | if argv is None: | |||
|
350 | argv = sys.argv[1:] | |||
|
351 | self.argv = argv | |||
|
352 | self.aliases = aliases or {} | |||
|
353 | self.flags = flags or {} | |||
|
354 | ||||
|
355 | def load_config(self, argv=None, aliases=None, flags=None): | |||
|
356 | """Parse the configuration and generate the Config object. | |||
|
357 | ||||
|
358 | Parameters | |||
|
359 | ---------- | |||
|
360 | argv : list, optional | |||
|
361 | A list that has the form of sys.argv[1:] which has unicode | |||
|
362 | elements of the form u"key=value". If this is None (default), | |||
|
363 | then self.argv will be used. | |||
|
364 | aliases : dict | |||
|
365 | A dict of aliases for configurable traits. | |||
|
366 | Keys are the short aliases, Values are the resolved trait. | |||
|
367 | Of the form: `{'alias' : 'Configurable.trait'}` | |||
|
368 | flags : dict | |||
|
369 | A dict of flags, keyed by str name. Values can be Config objects | |||
|
370 | or dicts. When the flag is triggered, The config is loaded as | |||
|
371 | `self.config.update(cfg)`. | |||
|
372 | """ | |||
|
373 | from IPython.config.configurable import Configurable | |||
|
374 | ||||
|
375 | self.clear() | |||
|
376 | if argv is None: | |||
|
377 | argv = self.argv | |||
|
378 | if aliases is None: | |||
|
379 | aliases = self.aliases | |||
|
380 | if flags is None: | |||
|
381 | flags = self.flags | |||
|
382 | ||||
|
383 | for item in argv: | |||
|
384 | if kv_pattern.match(item): | |||
|
385 | lhs,rhs = item.split('=',1) | |||
|
386 | # Substitute longnames for aliases. | |||
|
387 | if lhs in aliases: | |||
|
388 | lhs = aliases[lhs] | |||
|
389 | exec_str = 'self.config.' + lhs + '=' + rhs | |||
|
390 | try: | |||
|
391 | # Try to see if regular Python syntax will work. This | |||
|
392 | # won't handle strings as the quote marks are removed | |||
|
393 | # by the system shell. | |||
|
394 | exec exec_str in locals(), globals() | |||
|
395 | except (NameError, SyntaxError): | |||
|
396 | # This case happens if the rhs is a string but without | |||
|
397 | # the quote marks. We add the quote marks and see if | |||
|
398 | # it succeeds. If it still fails, we let it raise. | |||
|
399 | exec_str = 'self.config.' + lhs + '="' + rhs + '"' | |||
|
400 | exec exec_str in locals(), globals() | |||
|
401 | elif flag_pattern.match(item): | |||
|
402 | # trim leading '--' | |||
|
403 | m = item[2:] | |||
|
404 | cfg,_ = flags.get(m, (None,None)) | |||
|
405 | if cfg is None: | |||
|
406 | raise ValueError("Unrecognized flag: %r"%item) | |||
|
407 | elif isinstance(cfg, (dict, Config)): | |||
|
408 | # update self.config with Config: | |||
|
409 | self.config.update(cfg) | |||
|
410 | else: | |||
|
411 | raise ValueError("Invalid flag: %r"%flag) | |||
|
412 | else: | |||
|
413 | raise ValueError("Invalid argument: %r"%item) | |||
|
414 | return self.config | |||
309 |
|
415 | |||
310 | class ArgParseConfigLoader(CommandLineConfigLoader): |
|
416 | class ArgParseConfigLoader(CommandLineConfigLoader): | |
|
417 | """A loader that uses the argparse module to load from the command line.""" | |||
311 |
|
418 | |||
312 | def __init__(self, argv=None, *parser_args, **parser_kw): |
|
419 | def __init__(self, argv=None, *parser_args, **parser_kw): | |
313 | """Create a config loader for use with argparse. |
|
420 | """Create a config loader for use with argparse. | |
@@ -326,6 +433,11 b' class ArgParseConfigLoader(CommandLineConfigLoader):' | |||||
326 | parser_kw : dict |
|
433 | parser_kw : dict | |
327 | A tuple of keyword arguments that will be passed to the |
|
434 | A tuple of keyword arguments that will be passed to the | |
328 | constructor of :class:`argparse.ArgumentParser`. |
|
435 | constructor of :class:`argparse.ArgumentParser`. | |
|
436 | ||||
|
437 | Returns | |||
|
438 | ------- | |||
|
439 | config : Config | |||
|
440 | The resulting Config object. | |||
329 | """ |
|
441 | """ | |
330 | super(CommandLineConfigLoader, self).__init__() |
|
442 | super(CommandLineConfigLoader, self).__init__() | |
331 | if argv == None: |
|
443 | if argv == None: | |
@@ -337,8 +449,8 b' class ArgParseConfigLoader(CommandLineConfigLoader):' | |||||
337 | kwargs.update(parser_kw) |
|
449 | kwargs.update(parser_kw) | |
338 | self.parser_kw = kwargs |
|
450 | self.parser_kw = kwargs | |
339 |
|
451 | |||
340 |
def load_config(self, arg |
|
452 | def load_config(self, argv=None): | |
341 |
"""Parse command line arguments and return as a |
|
453 | """Parse command line arguments and return as a Config object. | |
342 |
|
454 | |||
343 | Parameters |
|
455 | Parameters | |
344 | ---------- |
|
456 | ---------- | |
@@ -348,10 +460,10 b' class ArgParseConfigLoader(CommandLineConfigLoader):' | |||||
348 | arguments from. If not given, the instance's self.argv attribute |
|
460 | arguments from. If not given, the instance's self.argv attribute | |
349 | (given at construction time) is used.""" |
|
461 | (given at construction time) is used.""" | |
350 | self.clear() |
|
462 | self.clear() | |
351 |
if arg |
|
463 | if argv is None: | |
352 |
arg |
|
464 | argv = self.argv | |
353 | self._create_parser() |
|
465 | self._create_parser() | |
354 |
self._parse_args(arg |
|
466 | self._parse_args(argv) | |
355 | self._convert_to_config() |
|
467 | self._convert_to_config() | |
356 | return self.config |
|
468 | return self.config | |
357 |
|
469 |
@@ -22,10 +22,15 b' Authors:' | |||||
22 |
|
22 | |||
23 | from unittest import TestCase |
|
23 | from unittest import TestCase | |
24 |
|
24 | |||
25 |
from IPython.config.configurable import |
|
25 | from IPython.config.configurable import ( | |
|
26 | Configurable, | |||
|
27 | SingletonConfigurable | |||
|
28 | ) | |||
|
29 | ||||
26 | from IPython.utils.traitlets import ( |
|
30 | from IPython.utils.traitlets import ( | |
27 |
|
|
31 | Int, Float, Str | |
28 | ) |
|
32 | ) | |
|
33 | ||||
29 | from IPython.config.loader import Config |
|
34 | from IPython.config.loader import Config | |
30 |
|
35 | |||
31 |
|
36 | |||
@@ -35,22 +40,29 b' from IPython.config.loader import Config' | |||||
35 |
|
40 | |||
36 |
|
41 | |||
37 | class MyConfigurable(Configurable): |
|
42 | class MyConfigurable(Configurable): | |
38 | a = Int(1, config=True) |
|
43 | a = Int(1, config=True, help="The integer a.") | |
39 | b = Float(1.0, config=True) |
|
44 | b = Float(1.0, config=True, help="The integer b.") | |
40 | c = Str('no config') |
|
45 | c = Str('no config') | |
41 |
|
46 | |||
42 |
|
47 | |||
|
48 | mc_help=u"""MyConfigurable options | |||
|
49 | ---------------------- | |||
|
50 | MyConfigurable.a : Int | |||
|
51 | The integer a. | |||
|
52 | MyConfigurable.b : Float | |||
|
53 | The integer b.""" | |||
|
54 | ||||
43 | class Foo(Configurable): |
|
55 | class Foo(Configurable): | |
44 | a = Int(0, config=True) |
|
56 | a = Int(0, config=True, help="The integer a.") | |
45 | b = Str('nope', config=True) |
|
57 | b = Str('nope', config=True) | |
46 |
|
58 | |||
47 |
|
59 | |||
48 | class Bar(Foo): |
|
60 | class Bar(Foo): | |
49 | b = Str('gotit', config=False) |
|
61 | b = Str('gotit', config=False, help="The string b.") | |
50 | c = Float(config=True) |
|
62 | c = Float(config=True, help="The string c.") | |
51 |
|
63 | |||
52 |
|
64 | |||
53 |
class TestConfigurable |
|
65 | class TestConfigurable(TestCase): | |
54 |
|
66 | |||
55 | def test_default(self): |
|
67 | def test_default(self): | |
56 | c1 = Configurable() |
|
68 | c1 = Configurable() | |
@@ -122,3 +134,31 b' class TestConfigurableConfig(TestCase):' | |||||
122 | self.assertEquals(c.a, 2) |
|
134 | self.assertEquals(c.a, 2) | |
123 | self.assertEquals(c.b, 'and') |
|
135 | self.assertEquals(c.b, 'and') | |
124 | self.assertEquals(c.c, 20.0) |
|
136 | self.assertEquals(c.c, 20.0) | |
|
137 | ||||
|
138 | def test_help(self): | |||
|
139 | self.assertEquals(MyConfigurable.class_get_help(), mc_help) | |||
|
140 | ||||
|
141 | ||||
|
142 | class TestSingletonConfigurable(TestCase): | |||
|
143 | ||||
|
144 | def test_instance(self): | |||
|
145 | from IPython.config.configurable import SingletonConfigurable | |||
|
146 | class Foo(SingletonConfigurable): pass | |||
|
147 | self.assertEquals(Foo.initialized(), False) | |||
|
148 | foo = Foo.instance() | |||
|
149 | self.assertEquals(Foo.initialized(), True) | |||
|
150 | self.assertEquals(foo, Foo.instance()) | |||
|
151 | self.assertEquals(SingletonConfigurable._instance, None) | |||
|
152 | ||||
|
153 | def test_inheritance(self): | |||
|
154 | class Bar(SingletonConfigurable): pass | |||
|
155 | class Bam(Bar): pass | |||
|
156 | self.assertEquals(Bar.initialized(), False) | |||
|
157 | self.assertEquals(Bam.initialized(), False) | |||
|
158 | bam = Bam.instance() | |||
|
159 | bam == Bar.instance() | |||
|
160 | self.assertEquals(Bar.initialized(), True) | |||
|
161 | self.assertEquals(Bam.initialized(), True) | |||
|
162 | self.assertEquals(bam, Bam._instance) | |||
|
163 | self.assertEquals(bam, Bar._instance) | |||
|
164 | self.assertEquals(SingletonConfigurable._instance, None) |
@@ -24,9 +24,12 b' import os' | |||||
24 | from tempfile import mkstemp |
|
24 | from tempfile import mkstemp | |
25 | from unittest import TestCase |
|
25 | from unittest import TestCase | |
26 |
|
26 | |||
|
27 | from IPython.utils.traitlets import Int, Unicode | |||
|
28 | from IPython.config.configurable import Configurable | |||
27 | from IPython.config.loader import ( |
|
29 | from IPython.config.loader import ( | |
28 | Config, |
|
30 | Config, | |
29 |
PyFileConfigLoader, |
|
31 | PyFileConfigLoader, | |
|
32 | KeyValueConfigLoader, | |||
30 | ArgParseConfigLoader, |
|
33 | ArgParseConfigLoader, | |
31 | ConfigError |
|
34 | ConfigError | |
32 | ) |
|
35 | ) | |
@@ -38,11 +41,11 b' from IPython.config.loader import (' | |||||
38 |
|
41 | |||
39 | pyfile = """ |
|
42 | pyfile = """ | |
40 | c = get_config() |
|
43 | c = get_config() | |
41 |
c.a |
|
44 | c.a=10 | |
42 |
c.b |
|
45 | c.b=20 | |
43 |
c.Foo.Bar.value |
|
46 | c.Foo.Bar.value=10 | |
44 |
c.Foo.Bam.value |
|
47 | c.Foo.Bam.value=range(10) | |
45 |
c.D.C.value |
|
48 | c.D.C.value='hi there' | |
46 | """ |
|
49 | """ | |
47 |
|
50 | |||
48 | class TestPyFileCL(TestCase): |
|
51 | class TestPyFileCL(TestCase): | |
@@ -109,6 +112,19 b' class TestArgParseCL(TestCase):' | |||||
109 | self.assertEquals(config.Global.bam, 'wow') |
|
112 | self.assertEquals(config.Global.bam, 'wow') | |
110 |
|
113 | |||
111 |
|
114 | |||
|
115 | class TestKeyValueCL(TestCase): | |||
|
116 | ||||
|
117 | def test_basic(self): | |||
|
118 | cl = KeyValueConfigLoader() | |||
|
119 | argv = [s.strip('c.') for s in pyfile.split('\n')[2:-1]] | |||
|
120 | config = cl.load_config(argv) | |||
|
121 | self.assertEquals(config.a, 10) | |||
|
122 | self.assertEquals(config.b, 20) | |||
|
123 | self.assertEquals(config.Foo.Bar.value, 10) | |||
|
124 | self.assertEquals(config.Foo.Bam.value, range(10)) | |||
|
125 | self.assertEquals(config.D.C.value, 'hi there') | |||
|
126 | ||||
|
127 | ||||
112 | class TestConfig(TestCase): |
|
128 | class TestConfig(TestCase): | |
113 |
|
129 | |||
114 | def test_setget(self): |
|
130 | def test_setget(self): |
@@ -31,7 +31,7 b' import tempfile' | |||||
31 | import types |
|
31 | import types | |
32 | from contextlib import nested |
|
32 | from contextlib import nested | |
33 |
|
33 | |||
34 | from IPython.config.configurable import Configurable |
|
34 | from IPython.config.configurable import SingletonConfigurable | |
35 | from IPython.core import debugger, oinspect |
|
35 | from IPython.core import debugger, oinspect | |
36 | from IPython.core import history as ipcorehist |
|
36 | from IPython.core import history as ipcorehist | |
37 | from IPython.core import page |
|
37 | from IPython.core import page | |
@@ -132,9 +132,7 b' class SeparateStr(Str):' | |||||
132 | value = value.replace('\\n','\n') |
|
132 | value = value.replace('\\n','\n') | |
133 | return super(SeparateStr, self).validate(obj, value) |
|
133 | return super(SeparateStr, self).validate(obj, value) | |
134 |
|
134 | |||
135 | class MultipleInstanceError(Exception): |
|
135 | ||
136 | pass |
|
|||
137 |
|
||||
138 | class ReadlineNoRecord(object): |
|
136 | class ReadlineNoRecord(object): | |
139 | """Context manager to execute some code, then reload readline history |
|
137 | """Context manager to execute some code, then reload readline history | |
140 | so that interactive input to the code doesn't appear when pressing up.""" |
|
138 | so that interactive input to the code doesn't appear when pressing up.""" | |
@@ -181,25 +179,78 b' class ReadlineNoRecord(object):' | |||||
181 | return [ghi(x) for x in range(start, end)] |
|
179 | return [ghi(x) for x in range(start, end)] | |
182 |
|
180 | |||
183 |
|
181 | |||
|
182 | _autocall_help = """ | |||
|
183 | Make IPython automatically call any callable object even if | |||
|
184 | you didn't type explicit parentheses. For example, 'str 43' becomes 'str(43)' | |||
|
185 | automatically. The value can be '0' to disable the feature, '1' for 'smart' | |||
|
186 | autocall, where it is not applied if there are no more arguments on the line, | |||
|
187 | and '2' for 'full' autocall, where all callable objects are automatically | |||
|
188 | called (even if no arguments are present). The default is '1'. | |||
|
189 | """ | |||
|
190 | ||||
184 | #----------------------------------------------------------------------------- |
|
191 | #----------------------------------------------------------------------------- | |
185 | # Main IPython class |
|
192 | # Main IPython class | |
186 | #----------------------------------------------------------------------------- |
|
193 | #----------------------------------------------------------------------------- | |
187 |
|
194 | |||
188 | class InteractiveShell(Configurable, Magic): |
|
195 | class InteractiveShell(SingletonConfigurable, Magic): | |
189 | """An enhanced, interactive shell for Python.""" |
|
196 | """An enhanced, interactive shell for Python.""" | |
190 |
|
197 | |||
191 | _instance = None |
|
198 | _instance = None | |
192 | autocall = Enum((0,1,2), default_value=1, config=True) |
|
199 | ||
|
200 | autocall = Enum((0,1,2), default_value=1, config=True, help= | |||
|
201 | """ | |||
|
202 | Make IPython automatically call any callable object even if you didn't | |||
|
203 | type explicit parentheses. For example, 'str 43' becomes 'str(43)' | |||
|
204 | automatically. The value can be '0' to disable the feature, '1' for | |||
|
205 | 'smart' autocall, where it is not applied if there are no more | |||
|
206 | arguments on the line, and '2' for 'full' autocall, where all callable | |||
|
207 | objects are automatically called (even if no arguments are present). | |||
|
208 | The default is '1'. | |||
|
209 | """ | |||
|
210 | ) | |||
193 | # TODO: remove all autoindent logic and put into frontends. |
|
211 | # TODO: remove all autoindent logic and put into frontends. | |
194 | # We can't do this yet because even runlines uses the autoindent. |
|
212 | # We can't do this yet because even runlines uses the autoindent. | |
195 |
autoindent = CBool(True, config=True |
|
213 | autoindent = CBool(True, config=True, help= | |
196 | automagic = CBool(True, config=True) |
|
214 | """ | |
197 | cache_size = Int(1000, config=True) |
|
215 | Autoindent IPython code entered interactively. | |
198 | color_info = CBool(True, config=True) |
|
216 | """ | |
|
217 | ) | |||
|
218 | automagic = CBool(True, config=True, help= | |||
|
219 | """ | |||
|
220 | Enable magic commands to be called without the leading %. | |||
|
221 | """ | |||
|
222 | ) | |||
|
223 | cache_size = Int(1000, config=True, help= | |||
|
224 | """ | |||
|
225 | Set the size of the output cache. The default is 1000, you can | |||
|
226 | change it permanently in your config file. Setting it to 0 completely | |||
|
227 | disables the caching system, and the minimum value accepted is 20 (if | |||
|
228 | you provide a value less than 20, it is reset to 0 and a warning is | |||
|
229 | issued). This limit is defined because otherwise you'll spend more | |||
|
230 | time re-flushing a too small cache than working | |||
|
231 | """ | |||
|
232 | ) | |||
|
233 | color_info = CBool(True, config=True, help= | |||
|
234 | """ | |||
|
235 | Use colors for displaying information about objects. Because this | |||
|
236 | information is passed through a pager (like 'less'), and some pagers | |||
|
237 | get confused with color codes, this capability can be turned off. | |||
|
238 | """ | |||
|
239 | ) | |||
199 | colors = CaselessStrEnum(('NoColor','LightBG','Linux'), |
|
240 | colors = CaselessStrEnum(('NoColor','LightBG','Linux'), | |
200 | default_value=get_default_colors(), config=True) |
|
241 | default_value=get_default_colors(), config=True) | |
201 | debug = CBool(False, config=True) |
|
242 | debug = CBool(False, config=True) | |
202 |
deep_reload = CBool(False, config=True |
|
243 | deep_reload = CBool(False, config=True, help= | |
|
244 | """ | |||
|
245 | Enable deep (recursive) reloading by default. IPython can use the | |||
|
246 | deep_reload module which reloads changes in modules recursively (it | |||
|
247 | replaces the reload() function, so you don't need to change anything to | |||
|
248 | use it). deep_reload() forces a full reload of modules whose code may | |||
|
249 | have changed, which the default reload() function does not. When | |||
|
250 | deep_reload is off, IPython will use the normal reload(), but | |||
|
251 | deep_reload will still be available as dreload(). | |||
|
252 | """ | |||
|
253 | ) | |||
203 | display_formatter = Instance(DisplayFormatter) |
|
254 | display_formatter = Instance(DisplayFormatter) | |
204 | displayhook_class = Type(DisplayHook) |
|
255 | displayhook_class = Type(DisplayHook) | |
205 | display_pub_class = Type(DisplayPublisher) |
|
256 | display_pub_class = Type(DisplayPublisher) | |
@@ -217,12 +268,28 b' class InteractiveShell(Configurable, Magic):' | |||||
217 | # interactive statements or whole blocks. |
|
268 | # interactive statements or whole blocks. | |
218 | input_splitter = Instance('IPython.core.inputsplitter.IPythonInputSplitter', |
|
269 | input_splitter = Instance('IPython.core.inputsplitter.IPythonInputSplitter', | |
219 | (), {}) |
|
270 | (), {}) | |
220 |
logstart = CBool(False, config=True |
|
271 | logstart = CBool(False, config=True, help= | |
221 | logfile = Unicode('', config=True) |
|
272 | """ | |
222 | logappend = Unicode('', config=True) |
|
273 | Start logging to the default log file. | |
|
274 | """ | |||
|
275 | ) | |||
|
276 | logfile = Unicode('', config=True, help= | |||
|
277 | """ | |||
|
278 | The name of the logfile to use. | |||
|
279 | """ | |||
|
280 | ) | |||
|
281 | logappend = Unicode('', config=True, help= | |||
|
282 | """ | |||
|
283 | Start logging to the given file in append mode. | |||
|
284 | """ | |||
|
285 | ) | |||
223 | object_info_string_level = Enum((0,1,2), default_value=0, |
|
286 | object_info_string_level = Enum((0,1,2), default_value=0, | |
224 | config=True) |
|
287 | config=True) | |
225 |
pdb = CBool(False, config=True |
|
288 | pdb = CBool(False, config=True, help= | |
|
289 | """ | |||
|
290 | Automatically call the pdb debugger after every exception. | |||
|
291 | """ | |||
|
292 | ) | |||
226 |
|
293 | |||
227 | profile = Unicode('', config=True) |
|
294 | profile = Unicode('', config=True) | |
228 | prompt_in1 = Str('In [\\#]: ', config=True) |
|
295 | prompt_in1 = Str('In [\\#]: ', config=True) | |
@@ -356,31 +423,6 b' class InteractiveShell(Configurable, Magic):' | |||||
356 | self.hooks.late_startup_hook() |
|
423 | self.hooks.late_startup_hook() | |
357 | atexit.register(self.atexit_operations) |
|
424 | atexit.register(self.atexit_operations) | |
358 |
|
425 | |||
359 | @classmethod |
|
|||
360 | def instance(cls, *args, **kwargs): |
|
|||
361 | """Returns a global InteractiveShell instance.""" |
|
|||
362 | if cls._instance is None: |
|
|||
363 | inst = cls(*args, **kwargs) |
|
|||
364 | # Now make sure that the instance will also be returned by |
|
|||
365 | # the subclasses instance attribute. |
|
|||
366 | for subclass in cls.mro(): |
|
|||
367 | if issubclass(cls, subclass) and \ |
|
|||
368 | issubclass(subclass, InteractiveShell): |
|
|||
369 | subclass._instance = inst |
|
|||
370 | else: |
|
|||
371 | break |
|
|||
372 | if isinstance(cls._instance, cls): |
|
|||
373 | return cls._instance |
|
|||
374 | else: |
|
|||
375 | raise MultipleInstanceError( |
|
|||
376 | 'Multiple incompatible subclass instances of ' |
|
|||
377 | 'InteractiveShell are being created.' |
|
|||
378 | ) |
|
|||
379 |
|
||||
380 | @classmethod |
|
|||
381 | def initialized(cls): |
|
|||
382 | return hasattr(cls, "_instance") |
|
|||
383 |
|
||||
384 | def get_ipython(self): |
|
426 | def get_ipython(self): | |
385 | """Return the currently running IPython instance.""" |
|
427 | """Return the currently running IPython instance.""" | |
386 | return self |
|
428 | return self |
@@ -366,6 +366,7 b' class TestHasTraits(TestCase):' | |||||
366 | f = Float |
|
366 | f = Float | |
367 | a = A() |
|
367 | a = A() | |
368 | self.assertEquals(a.trait_names(),['i','f']) |
|
368 | self.assertEquals(a.trait_names(),['i','f']) | |
|
369 | self.assertEquals(A.class_trait_names(),['i','f']) | |||
369 |
|
370 | |||
370 | def test_trait_metadata(self): |
|
371 | def test_trait_metadata(self): | |
371 | class A(HasTraits): |
|
372 | class A(HasTraits): | |
@@ -379,6 +380,7 b' class TestHasTraits(TestCase):' | |||||
379 | f = Float |
|
380 | f = Float | |
380 | a = A() |
|
381 | a = A() | |
381 | self.assertEquals(a.traits(), dict(i=A.i, f=A.f)) |
|
382 | self.assertEquals(a.traits(), dict(i=A.i, f=A.f)) | |
|
383 | self.assertEquals(A.class_traits(), dict(i=A.i, f=A.f)) | |||
382 |
|
384 | |||
383 | def test_traits_metadata(self): |
|
385 | def test_traits_metadata(self): | |
384 | class A(HasTraits): |
|
386 | class A(HasTraits): |
@@ -515,6 +515,48 b' class HasTraits(object):' | |||||
515 | for n in names: |
|
515 | for n in names: | |
516 | self._add_notifiers(handler, n) |
|
516 | self._add_notifiers(handler, n) | |
517 |
|
517 | |||
|
518 | @classmethod | |||
|
519 | def class_trait_names(cls, **metadata): | |||
|
520 | """Get a list of all the names of this classes traits. | |||
|
521 | ||||
|
522 | This method is just like the :meth:`trait_names` method, but is unbound. | |||
|
523 | """ | |||
|
524 | return cls.class_traits(**metadata).keys() | |||
|
525 | ||||
|
526 | @classmethod | |||
|
527 | def class_traits(cls, **metadata): | |||
|
528 | """Get a list of all the traits of this class. | |||
|
529 | ||||
|
530 | This method is just like the :meth:`traits` method, but is unbound. | |||
|
531 | ||||
|
532 | The TraitTypes returned don't know anything about the values | |||
|
533 | that the various HasTrait's instances are holding. | |||
|
534 | ||||
|
535 | This follows the same algorithm as traits does and does not allow | |||
|
536 | for any simple way of specifying merely that a metadata name | |||
|
537 | exists, but has any value. This is because get_metadata returns | |||
|
538 | None if a metadata key doesn't exist. | |||
|
539 | """ | |||
|
540 | traits = dict([memb for memb in getmembers(cls) if \ | |||
|
541 | isinstance(memb[1], TraitType)]) | |||
|
542 | ||||
|
543 | if len(metadata) == 0: | |||
|
544 | return traits | |||
|
545 | ||||
|
546 | for meta_name, meta_eval in metadata.items(): | |||
|
547 | if type(meta_eval) is not FunctionType: | |||
|
548 | metadata[meta_name] = _SimpleTest(meta_eval) | |||
|
549 | ||||
|
550 | result = {} | |||
|
551 | for name, trait in traits.items(): | |||
|
552 | for meta_name, meta_eval in metadata.items(): | |||
|
553 | if not meta_eval(trait.get_metadata(meta_name)): | |||
|
554 | break | |||
|
555 | else: | |||
|
556 | result[name] = trait | |||
|
557 | ||||
|
558 | return result | |||
|
559 | ||||
518 | def trait_names(self, **metadata): |
|
560 | def trait_names(self, **metadata): | |
519 | """Get a list of all the names of this classes traits.""" |
|
561 | """Get a list of all the names of this classes traits.""" | |
520 | return self.traits(**metadata).keys() |
|
562 | return self.traits(**metadata).keys() |
General Comments 0
You need to be logged in to leave comments.
Login now