Show More
@@ -23,13 +23,15 b' import logging' | |||
|
23 | 23 | import sys |
|
24 | 24 | |
|
25 | 25 | from IPython.config.configurable import SingletonConfigurable |
|
26 | from IPython.utils.traitlets import ( | |
|
27 | Unicode, List, Int, Enum | |
|
28 | ) | |
|
29 | 26 | from IPython.config.loader import ( |
|
30 | 27 | KeyValueConfigLoader, PyFileConfigLoader |
|
31 | 28 | ) |
|
32 | 29 | |
|
30 | from IPython.utils.traitlets import ( | |
|
31 | Unicode, List, Int, Enum, Dict | |
|
32 | ) | |
|
33 | from IPython.utils.text import indent | |
|
34 | ||
|
33 | 35 | #----------------------------------------------------------------------------- |
|
34 | 36 | # Application class |
|
35 | 37 | #----------------------------------------------------------------------------- |
@@ -55,16 +57,33 b' class Application(SingletonConfigurable):' | |||
|
55 | 57 | |
|
56 | 58 | # The log level for the application |
|
57 | 59 | log_level = Enum((0,10,20,30,40,50), default_value=logging.WARN, |
|
58 |
config=True, |
|
|
60 | config=True, | |
|
59 | 61 | help="Set the log level (0,10,20,30,40,50).") |
|
62 | ||
|
63 | # the shortname map for configurables | |
|
64 | shortnames = Dict(dict(log_level='Application.log_level')) | |
|
65 | ||
|
66 | # macros for loading Configurables or store_const style flags | |
|
67 | # macros are loaded from this dict by '--key' flags | |
|
68 | macros = Dict() | |
|
69 | # macro_help dict keys must match macros | |
|
70 | macro_help = Dict() | |
|
71 | ||
|
60 | 72 | |
|
61 | 73 | def __init__(self, **kwargs): |
|
62 | 74 | SingletonConfigurable.__init__(self, **kwargs) |
|
63 | 75 | # Add my class to self.classes so my attributes appear in command line |
|
64 | 76 | # options. |
|
65 | 77 | self.classes.insert(0, self.__class__) |
|
78 | ||
|
79 | # check that macro_help has the right keys | |
|
80 | # there is probably a better way to do this that doesn't use 2 dicts | |
|
81 | keys = set(self.macros.keys()) | |
|
82 | badkeys = keys.difference_update(set(self.macro_help.keys())) | |
|
83 | if badkeys: | |
|
84 | raise KeyError("macro %r has no help in `macro_help`!"%badkeys.pop()) | |
|
66 | 85 | self.init_logging() |
|
67 | ||
|
86 | ||
|
68 | 87 | def init_logging(self): |
|
69 | 88 | """Start logging for this application. |
|
70 | 89 | |
@@ -82,9 +101,45 b' class Application(SingletonConfigurable):' | |||
|
82 | 101 | def _log_level_changed(self, name, old, new): |
|
83 | 102 | """Adjust the log level when log_level is set.""" |
|
84 | 103 | self.log.setLevel(new) |
|
85 | ||
|
104 | ||
|
105 | def print_shortname_help(self): | |
|
106 | """print the shortname part of the help""" | |
|
107 | if not self.shortnames: | |
|
108 | return | |
|
109 | ||
|
110 | print "Aliases" | |
|
111 | print "-------" | |
|
112 | classdict = {} | |
|
113 | for c in self.classes: | |
|
114 | classdict[c.__name__] = c | |
|
115 | for shortname, longname in self.shortnames.iteritems(): | |
|
116 | classname, traitname = longname.split('.',1) | |
|
117 | cls = classdict[classname] | |
|
118 | ||
|
119 | trait = cls.class_traits(config=True)[traitname] | |
|
120 | help = trait.get_metadata('help') | |
|
121 | print shortname, "(%s)"%longname, ':', trait.__class__.__name__ | |
|
122 | if help: | |
|
123 | print indent(help) | |
|
124 | ||
|
125 | ||
|
126 | def print_macro_help(self): | |
|
127 | """print the the macro part of the help""" | |
|
128 | if not self.macros: | |
|
129 | return | |
|
130 | ||
|
131 | print "Flags" | |
|
132 | print "-----" | |
|
133 | ||
|
134 | for m, cfg in self.macros.iteritems(): | |
|
135 | print '--'+m | |
|
136 | print indent(self.macro_help[m]) | |
|
137 | ||
|
138 | ||
|
86 | 139 | def print_help(self): |
|
87 | 140 | """Print the help for each Configurable class in self.classes.""" |
|
141 | self.print_macro_help() | |
|
142 | self.print_shortname_help() | |
|
88 | 143 | for cls in self.classes: |
|
89 | 144 | cls.class_print_help() |
|
90 | 145 | |
@@ -112,7 +167,7 b' class Application(SingletonConfigurable):' | |||
|
112 | 167 | """Parse the command line arguments.""" |
|
113 | 168 | argv = sys.argv[1:] if argv is None else argv |
|
114 | 169 | |
|
115 | if '-h' in argv or '--h' in argv: | |
|
170 | if '-h' in argv or '--help' in argv: | |
|
116 | 171 | self.print_description() |
|
117 | 172 | self.print_help() |
|
118 | 173 | sys.exit(1) |
@@ -121,7 +176,8 b' class Application(SingletonConfigurable):' | |||
|
121 | 176 | self.print_version() |
|
122 | 177 | sys.exit(1) |
|
123 | 178 | |
|
124 |
loader = KeyValueConfigLoader(argv=argv, |
|
|
179 | loader = KeyValueConfigLoader(argv=argv, shortnames=self.shortnames, | |
|
180 | macros=self.macros) | |
|
125 | 181 | config = loader.load_config() |
|
126 | 182 | self.update_config(config) |
|
127 | 183 |
@@ -140,18 +140,6 b' class Configurable(HasTraits):' | |||
|
140 | 140 | setattr(self, k, deepcopy(config_value)) |
|
141 | 141 | |
|
142 | 142 | @classmethod |
|
143 | def class_get_shortnames(cls): | |
|
144 | """Return the shortname to fullname dict for config=True traits.""" | |
|
145 | cls_traits = cls.class_traits(config=True) | |
|
146 | shortnames = {} | |
|
147 | for k, v in cls_traits.items(): | |
|
148 | shortname = v.get_metadata('shortname') | |
|
149 | if shortname is not None: | |
|
150 | longname = cls.__name__ + '.' + k | |
|
151 | shortnames[shortname] = longname | |
|
152 | return shortnames | |
|
153 | ||
|
154 | @classmethod | |
|
155 | 143 | def class_get_help(cls): |
|
156 | 144 | """Get the help string for this class in ReST format.""" |
|
157 | 145 | cls_traits = cls.class_traits(config=True) |
@@ -160,10 +148,7 b' class Configurable(HasTraits):' | |||
|
160 | 148 | final_help.append(len(final_help[0])*u'-') |
|
161 | 149 | for k, v in cls_traits.items(): |
|
162 | 150 | help = v.get_metadata('help') |
|
163 | shortname = v.get_metadata('shortname') | |
|
164 | 151 | header = "%s.%s : %s" % (cls.__name__, k, v.__class__.__name__) |
|
165 | if shortname is not None: | |
|
166 | header += " (shortname=" + shortname + ")" | |
|
167 | 152 | final_help.append(header) |
|
168 | 153 | if help is not None: |
|
169 | 154 | final_help.append(indent(help)) |
@@ -18,6 +18,7 b' Authors' | |||
|
18 | 18 | #----------------------------------------------------------------------------- |
|
19 | 19 | |
|
20 | 20 | import __builtin__ |
|
21 | import re | |
|
21 | 22 | import sys |
|
22 | 23 | |
|
23 | 24 | from IPython.external import argparse |
@@ -303,6 +304,8 b' class CommandLineConfigLoader(ConfigLoader):' | |||
|
303 | 304 | here. |
|
304 | 305 | """ |
|
305 | 306 | |
|
307 | kv_pattern = re.compile(r'[A-Za-z]\w*(\.\w+)*\=.+') | |
|
308 | macro_pattern = re.compile(r'\-\-\w+(\-\w)*') | |
|
306 | 309 | |
|
307 | 310 | class KeyValueConfigLoader(CommandLineConfigLoader): |
|
308 | 311 | """A config loader that loads key value pairs from the command line. |
@@ -312,7 +315,7 b' class KeyValueConfigLoader(CommandLineConfigLoader):' | |||
|
312 | 315 | ipython Global.profile="foo" InteractiveShell.autocall=False |
|
313 | 316 | """ |
|
314 | 317 | |
|
315 |
def __init__(self, argv=None, |
|
|
318 | def __init__(self, argv=None, shortnames=None, macros=None): | |
|
316 | 319 | """Create a key value pair config loader. |
|
317 | 320 | |
|
318 | 321 | Parameters |
@@ -321,9 +324,14 b' class KeyValueConfigLoader(CommandLineConfigLoader):' | |||
|
321 | 324 | A list that has the form of sys.argv[1:] which has unicode |
|
322 | 325 | elements of the form u"key=value". If this is None (default), |
|
323 | 326 | then sys.argv[1:] will be used. |
|
324 | classes : (list, tuple) of Configurables | |
|
325 | A sequence of Configurable classes that will be used to map | |
|
326 | shortnames to longnames. | |
|
327 | shortnames : dict | |
|
328 | A dict of aliases for configurable traits. | |
|
329 | Keys are the short aliases, Values are the resolved trait. | |
|
330 | Of the form: `{'shortname' : 'Configurable.trait'}` | |
|
331 | macros : dict | |
|
332 | A dict of macros, keyed by str name. Vaues can be Config objects, | |
|
333 | dicts, or "key=value" strings. If Config or dict, when the macro | |
|
334 | is triggered, The macro is loaded as `self.config.update(m)`. | |
|
327 | 335 | |
|
328 | 336 | Returns |
|
329 | 337 | ------- |
@@ -340,12 +348,11 b' class KeyValueConfigLoader(CommandLineConfigLoader):' | |||
|
340 | 348 | """ |
|
341 | 349 | if argv is None: |
|
342 | 350 | argv = sys.argv[1:] |
|
343 | if classes is None: | |
|
344 | classes = () | |
|
345 | 351 | self.argv = argv |
|
346 |
self. |
|
|
352 | self.shortnames = shortnames or {} | |
|
353 | self.macros = macros or {} | |
|
347 | 354 | |
|
348 |
def load_config(self, argv=None, |
|
|
355 | def load_config(self, argv=None, shortnames=None, macros=None): | |
|
349 | 356 | """Parse the configuration and generate the Config object. |
|
350 | 357 | |
|
351 | 358 | Parameters |
@@ -354,37 +361,28 b' class KeyValueConfigLoader(CommandLineConfigLoader):' | |||
|
354 | 361 | A list that has the form of sys.argv[1:] which has unicode |
|
355 | 362 | elements of the form u"key=value". If this is None (default), |
|
356 | 363 | then self.argv will be used. |
|
357 | classes : (list, tuple) of Configurables | |
|
358 | A sequence of Configurable classes that will be used to map | |
|
359 | shortnames to longnames. | |
|
364 | shortnames : dict | |
|
365 | A dict of aliases for configurable traits. | |
|
366 | Keys are the short aliases, Values are the resolved trait. | |
|
367 | Of the form: `{'shortname' : 'Configurable.trait'}` | |
|
368 | macros : dict | |
|
369 | A dict of macros, keyed by str name. Vaues can be Config objects, | |
|
370 | dicts, or "key=value" strings. If Config or dict, when the macro | |
|
371 | is triggered, The macro is loaded as `self.config.update(m)`. | |
|
360 | 372 | """ |
|
361 | 373 | from IPython.config.configurable import Configurable |
|
362 | 374 | |
|
363 | 375 | self.clear() |
|
364 | 376 | if argv is None: |
|
365 | 377 | argv = self.argv |
|
366 |
if |
|
|
367 |
|
|
|
368 | ||
|
369 | # Create the mapping between shortnames and longnames. | |
|
370 | shortnames = {} | |
|
371 | for cls in classes: | |
|
372 | if issubclass(cls, Configurable): | |
|
373 | sn = cls.class_get_shortnames() | |
|
374 | # Check for duplicate shortnames and raise if found. | |
|
375 | for k, v in sn.items(): | |
|
376 | if k in shortnames: | |
|
377 | raise KeyError( | |
|
378 | 'Duplicate shortname: both %s and %s use the shortname: "%s"' %\ | |
|
379 | (v, shortnames[k], k) | |
|
380 | ) | |
|
381 | shortnames.update(sn) | |
|
378 | if shortnames is None: | |
|
379 | shortnames = self.shortnames | |
|
380 | if macros is None: | |
|
381 | macros = self.macros | |
|
382 | 382 | |
|
383 | 383 | for item in argv: |
|
384 | pair = tuple(item.split("=")) | |
|
385 | if len(pair) == 2: | |
|
386 | lhs = pair[0] | |
|
387 | rhs = pair[1] | |
|
384 | if kv_pattern.match(item): | |
|
385 | lhs,rhs = item.split('=',1) | |
|
388 | 386 | # Substitute longnames for shortnames. |
|
389 | 387 | if lhs in shortnames: |
|
390 | 388 | lhs = shortnames[lhs] |
@@ -400,9 +398,26 b' class KeyValueConfigLoader(CommandLineConfigLoader):' | |||
|
400 | 398 | # it succeeds. If it still fails, we let it raise. |
|
401 | 399 | exec_str = 'self.config.' + lhs + '="' + rhs + '"' |
|
402 | 400 | exec exec_str in locals(), globals() |
|
401 | elif macro_pattern.match(item): | |
|
402 | # trim leading '--' | |
|
403 | m = item[2:] | |
|
404 | macro = macros.get(m, None) | |
|
405 | if macro is None: | |
|
406 | raise ValueError("Unrecognized argument: %r"%item) | |
|
407 | macro = macros[m] | |
|
408 | if isinstance(macro, basestring): | |
|
409 | # macro is simply a 'Class.trait=value' string | |
|
410 | exec_str = 'self.config.' + macro | |
|
411 | exec exec_str in locals(), globals() | |
|
412 | elif isinstance(macro, (dict, Configurable)): | |
|
413 | # update self.config with Config: | |
|
414 | self.config.update(macros[m]) | |
|
415 | else: | |
|
416 | raise ValueError("Invalid macro: %r"%macro) | |
|
417 | else: | |
|
418 | raise ValueError("Invalid argument: %r"%item) | |
|
403 | 419 | return self.config |
|
404 | 420 | |
|
405 | ||
|
406 | 421 | class ArgParseConfigLoader(CommandLineConfigLoader): |
|
407 | 422 | """A loader that uses the argparse module to load from the command line.""" |
|
408 | 423 |
@@ -35,14 +35,14 b' from IPython.utils.traitlets import (' | |||
|
35 | 35 | |
|
36 | 36 | class Foo(Configurable): |
|
37 | 37 | |
|
38 |
i = Int(0, config=True, |
|
|
39 |
j = Int(1, config=True, |
|
|
40 |
name = Unicode(u'Brian', config=True, |
|
|
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 | 41 | |
|
42 | 42 | |
|
43 | 43 | class Bar(Configurable): |
|
44 | 44 | |
|
45 |
enabled = Bool(True, config=True, |
|
|
45 | enabled = Bool(True, config=True, help="Enable bar.") | |
|
46 | 46 | |
|
47 | 47 | |
|
48 | 48 | class MyApp(Application): |
@@ -54,6 +54,15 b' class MyApp(Application):' | |||
|
54 | 54 | config_file = Unicode(u'', config=True, shortname="config_file", |
|
55 | 55 | help="Load this config file") |
|
56 | 56 | |
|
57 | shortnames = dict(i='Foo.i',j='Foo.j',name='Foo.name', | |
|
58 | enabled='Bar.enabled', log_level='MyApp.log_level') | |
|
59 | ||
|
60 | macros = dict(enable='Bar.enabled=True', disable='Bar.enabled=False') | |
|
61 | ||
|
62 | macro_help = dict( | |
|
63 | enable="""Enable bar""", | |
|
64 | disable="""Disable bar""" | |
|
65 | ) | |
|
57 | 66 | def init_foo(self): |
|
58 | 67 | self.foo = Foo(config=self.config) |
|
59 | 68 | |
@@ -88,3 +97,12 b' class TestApplication(TestCase):' | |||
|
88 | 97 | self.assertEquals(app.foo.j, 10) |
|
89 | 98 | self.assertEquals(app.bar.enabled, False) |
|
90 | 99 | |
|
100 | def test_macro(self): | |
|
101 | app = MyApp() | |
|
102 | app.parse_command_line(["--disable"]) | |
|
103 | app.init_bar() | |
|
104 | self.assertEquals(app.bar.enabled, False) | |
|
105 | app.parse_command_line(["--enable"]) | |
|
106 | app.init_bar() | |
|
107 | self.assertEquals(app.bar.enabled, True) | |
|
108 |
@@ -40,26 +40,26 b' from IPython.config.loader import Config' | |||
|
40 | 40 | |
|
41 | 41 | |
|
42 | 42 | class MyConfigurable(Configurable): |
|
43 |
a = Int(1, config=True, |
|
|
44 |
b = Float(1.0, config=True, |
|
|
43 | a = Int(1, config=True, help="The integer a.") | |
|
44 | b = Float(1.0, config=True, help="The integer b.") | |
|
45 | 45 | c = Str('no config') |
|
46 | 46 | |
|
47 | 47 | |
|
48 | 48 | mc_help=u"""MyConfigurable options |
|
49 | 49 | ---------------------- |
|
50 |
MyConfigurable.a : Int |
|
|
50 | MyConfigurable.a : Int | |
|
51 | 51 | The integer a. |
|
52 |
MyConfigurable.b : Float |
|
|
52 | MyConfigurable.b : Float | |
|
53 | 53 | The integer b.""" |
|
54 | 54 | |
|
55 | 55 | class Foo(Configurable): |
|
56 |
a = Int(0, config=True, |
|
|
56 | a = Int(0, config=True, help="The integer a.") | |
|
57 | 57 | b = Str('nope', config=True) |
|
58 | 58 | |
|
59 | 59 | |
|
60 | 60 | class Bar(Foo): |
|
61 |
b = Str('gotit', config=False, |
|
|
62 |
c = Float(config=True, |
|
|
61 | b = Str('gotit', config=False, help="The string b.") | |
|
62 | c = Float(config=True, help="The string c.") | |
|
63 | 63 | |
|
64 | 64 | |
|
65 | 65 | class TestConfigurable(TestCase): |
@@ -135,14 +135,6 b' class TestConfigurable(TestCase):' | |||
|
135 | 135 | self.assertEquals(c.b, 'and') |
|
136 | 136 | self.assertEquals(c.c, 20.0) |
|
137 | 137 | |
|
138 | def test_shortnames(self): | |
|
139 | sn = MyConfigurable.class_get_shortnames() | |
|
140 | self.assertEquals(sn, {'a': 'MyConfigurable.a', 'b': 'MyConfigurable.b'}) | |
|
141 | sn = Foo.class_get_shortnames() | |
|
142 | self.assertEquals(sn, {'a': 'Foo.a'}) | |
|
143 | sn = Bar.class_get_shortnames() | |
|
144 | self.assertEquals(sn, {'a': 'Bar.a', 'c': 'Bar.c'}) | |
|
145 | ||
|
146 | 138 | def test_help(self): |
|
147 | 139 | self.assertEquals(MyConfigurable.class_get_help(), mc_help) |
|
148 | 140 |
@@ -124,23 +124,6 b' class TestKeyValueCL(TestCase):' | |||
|
124 | 124 | self.assertEquals(config.Foo.Bam.value, range(10)) |
|
125 | 125 | self.assertEquals(config.D.C.value, 'hi there') |
|
126 | 126 | |
|
127 | def test_shortname(self): | |
|
128 | class Foo(Configurable): | |
|
129 | i = Int(0, config=True, shortname="i") | |
|
130 | s = Unicode('hi', config=True, shortname="s") | |
|
131 | cl = KeyValueConfigLoader() | |
|
132 | config = cl.load_config(["i=20", "s=there"], classes=[Foo]) | |
|
133 | self.assertEquals(config.Foo.i, 20) | |
|
134 | self.assertEquals(config.Foo.s, "there") | |
|
135 | ||
|
136 | def test_duplicate(self): | |
|
137 | class Foo(Configurable): | |
|
138 | i = Int(0, config=True, shortname="i") | |
|
139 | class Bar(Configurable): | |
|
140 | i = Int(0, config=True, shortname="i") | |
|
141 | cl = KeyValueConfigLoader() | |
|
142 | self.assertRaises(KeyError, cl.load_config, ["i=20", "s=there"], classes=[Foo, Bar]) | |
|
143 | ||
|
144 | 127 | |
|
145 | 128 | class TestConfig(TestCase): |
|
146 | 129 |
@@ -62,6 +62,15 b' class MyApp(Application):' | |||
|
62 | 62 | classes = List([Bar, Foo]) |
|
63 | 63 | config_file = Unicode(u'', config=True, shortname="config_file", |
|
64 | 64 | help="Load this config file") |
|
65 | ||
|
66 | shortnames = dict(i='Foo.i',j='Foo.j',name='Foo.name', | |
|
67 | enabled='Bar.enabled') | |
|
68 | ||
|
69 | macros = dict(enable='Bar.enabled=True', disable='Bar.enabled=False') | |
|
70 | macro_help = dict( | |
|
71 | enable="""Set Bar.enabled to True""", | |
|
72 | disable="""Set Bar.enabled to False""" | |
|
73 | ) | |
|
65 | 74 | |
|
66 | 75 | def init_foo(self): |
|
67 | 76 | # Pass config to other classes for them to inherit the config. |
General Comments 0
You need to be logged in to leave comments.
Login now