##// END OF EJS Templates
Merge branch 'config5' into trunk
Brian Granger -
r3866:f4ec30ec merge
parent child Browse files
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 print
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 print
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 print
167
168 for m, (cfg,help) in self.flags.iteritems():
169 print '--'+m
170 print indent(help)
171 print
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 print
182
183 for cls in self.classes:
184 cls.class_print_help()
185 print
186
187 def print_description(self):
188 """Print the application description."""
189 print self.description
190 print
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 """A simple configuration system.
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 os
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, args=None):
452 def load_config(self, argv=None):
341 """Parse command line arguments and return as a Struct.
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 args is None:
463 if argv is None:
352 args = self.argv
464 argv = self.argv
353 self._create_parser()
465 self._create_parser()
354 self._parse_args(args)
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 Configurable, ConfigurableError
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 TraitError, Int, Float, Str
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 TestConfigurableConfig(TestCase):
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 = 10
44 c.a=10
42 c.b = 20
45 c.b=20
43 c.Foo.Bar.value = 10
46 c.Foo.Bar.value=10
44 c.Foo.Bam.value = range(10)
47 c.Foo.Bam.value=range(10)
45 c.D.C.value = 'hi there'
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