##// END OF EJS Templates
Fixing bugs in BaseIPythonApplication....
Brian Granger -
Show More
@@ -1,241 +1,241 b''
1 1 # encoding: utf-8
2 2 """
3 3 A base class for a configurable application.
4 4
5 5 Authors:
6 6
7 7 * Brian Granger
8 8 """
9 9
10 10 #-----------------------------------------------------------------------------
11 11 # Copyright (C) 2008-2011 The IPython Development Team
12 12 #
13 13 # Distributed under the terms of the BSD License. The full license is in
14 14 # the file COPYING, distributed as part of this software.
15 15 #-----------------------------------------------------------------------------
16 16
17 17 #-----------------------------------------------------------------------------
18 18 # Imports
19 19 #-----------------------------------------------------------------------------
20 20
21 21 from copy import deepcopy
22 22 import logging
23 23 import sys
24 24
25 25 from IPython.config.configurable import SingletonConfigurable
26 26 from IPython.config.loader import (
27 27 KeyValueConfigLoader, PyFileConfigLoader, Config
28 28 )
29 29
30 30 from IPython.utils.traitlets import (
31 31 Unicode, List, Int, Enum, Dict
32 32 )
33 33 from IPython.utils.text import indent
34 34
35 35 #-----------------------------------------------------------------------------
36 36 # Descriptions for
37 37 #-----------------------------------------------------------------------------
38 38
39 39 flag_description = """
40 40 Flags are command-line arguments passed as '--<flag>'.
41 41 These take no parameters, unlike regular key-value arguments.
42 42 They are typically used for setting boolean flags, or enabling
43 43 modes that involve setting multiple options together.
44 44 """.strip() # trim newlines of front and back
45 45
46 46 alias_description = """
47 47 These are commonly set parameters, given abbreviated aliases for convenience.
48 48 They are set in the same `name=value` way as class parameters, where
49 49 <name> is replaced by the real parameter for which it is an alias.
50 50 """.strip() # trim newlines of front and back
51 51
52 52 keyvalue_description = """
53 53 Parameters are set from command-line arguments of the form:
54 54 `Class.trait=value`. Parameters will *never* be prefixed with '-'.
55 55 This line is evaluated in Python, so simple expressions are allowed, e.g.
56 56 `C.a='range(3)'` For setting C.a=[0,1,2]
57 57 """.strip() # trim newlines of front and back
58 58
59 59 #-----------------------------------------------------------------------------
60 60 # Application class
61 61 #-----------------------------------------------------------------------------
62 62
63 63
64 64 class ApplicationError(Exception):
65 65 pass
66 66
67 67
68 68 class Application(SingletonConfigurable):
69 69 """A singleton application with full configuration support."""
70 70
71 71 # The name of the application, will usually match the name of the command
72 72 # line application
73 app_name = Unicode(u'application')
73 name = Unicode(u'application')
74 74
75 75 # The description of the application that is printed at the beginning
76 76 # of the help.
77 77 description = Unicode(u'This is an application.')
78 78 # default section descriptions
79 79 flag_description = Unicode(flag_description)
80 80 alias_description = Unicode(alias_description)
81 81 keyvalue_description = Unicode(keyvalue_description)
82 82
83 83
84 84 # A sequence of Configurable subclasses whose config=True attributes will
85 85 # be exposed at the command line.
86 86 classes = List([])
87 87
88 88 # The version string of this application.
89 89 version = Unicode(u'0.0')
90 90
91 91 # The log level for the application
92 92 log_level = Enum((0,10,20,30,40,50), default_value=logging.WARN,
93 93 config=True,
94 94 help="Set the log level (0,10,20,30,40,50).")
95 95
96 96 # the alias map for configurables
97 97 aliases = Dict(dict(log_level='Application.log_level'))
98 98
99 99 # flags for loading Configurables or store_const style flags
100 100 # flags are loaded from this dict by '--key' flags
101 101 # this must be a dict of two-tuples, the first element being the Config/dict
102 102 # and the second being the help string for the flag
103 103 flags = Dict()
104 104
105 105
106 106 def __init__(self, **kwargs):
107 107 SingletonConfigurable.__init__(self, **kwargs)
108 108 # Add my class to self.classes so my attributes appear in command line
109 109 # options.
110 110 self.classes.insert(0, self.__class__)
111 111
112 112 # ensure self.flags dict is valid
113 113 for key,value in self.flags.iteritems():
114 114 assert len(value) == 2, "Bad flag: %r:%s"%(key,value)
115 115 assert isinstance(value[0], (dict, Config)), "Bad flag: %r:%s"%(key,value)
116 116 assert isinstance(value[1], basestring), "Bad flag: %r:%s"%(key,value)
117 117 self.init_logging()
118 118
119 119 def _config_changed(self, name, old, new):
120 120 SingletonConfigurable._config_changed(self, name, old, new)
121 121 self.log.debug('Config changed:')
122 122 self.log.debug(repr(new))
123 123
124 124 def init_logging(self):
125 125 """Start logging for this application.
126 126
127 127 The default is to log to stdout using a StreaHandler. The log level
128 128 starts at loggin.WARN, but this can be adjusted by setting the
129 129 ``log_level`` attribute.
130 130 """
131 131 self.log = logging.getLogger(self.__class__.__name__)
132 132 self.log.setLevel(self.log_level)
133 133 self._log_handler = logging.StreamHandler()
134 134 self._log_formatter = logging.Formatter("[%(name)s] %(message)s")
135 135 self._log_handler.setFormatter(self._log_formatter)
136 136 self.log.addHandler(self._log_handler)
137 137
138 138 def _log_level_changed(self, name, old, new):
139 139 """Adjust the log level when log_level is set."""
140 140 self.log.setLevel(new)
141 141
142 142 def print_alias_help(self):
143 143 """print the alias part of the help"""
144 144 if not self.aliases:
145 145 return
146 146
147 147 print "Aliases"
148 148 print "-------"
149 149 print self.alias_description
150 150 print
151 151
152 152 classdict = {}
153 153 for c in self.classes:
154 154 classdict[c.__name__] = c
155 155
156 156 for alias, longname in self.aliases.iteritems():
157 157 classname, traitname = longname.split('.',1)
158 158 cls = classdict[classname]
159 159
160 160 trait = cls.class_traits(config=True)[traitname]
161 161 help = trait.get_metadata('help')
162 162 print alias, "(%s)"%longname, ':', trait.__class__.__name__
163 163 if help:
164 164 print indent(help)
165 165 print
166 166
167 167 def print_flag_help(self):
168 168 """print the flag part of the help"""
169 169 if not self.flags:
170 170 return
171 171
172 172 print "Flags"
173 173 print "-----"
174 174 print self.flag_description
175 175 print
176 176
177 177 for m, (cfg,help) in self.flags.iteritems():
178 178 print '--'+m
179 179 print indent(help)
180 180 print
181 181
182 182 def print_help(self):
183 183 """Print the help for each Configurable class in self.classes."""
184 184 self.print_flag_help()
185 185 self.print_alias_help()
186 186 if self.classes:
187 187 print "Class parameters"
188 188 print "----------------"
189 189 print self.keyvalue_description
190 190 print
191 191
192 192 for cls in self.classes:
193 193 cls.class_print_help()
194 194 print
195 195
196 196 def print_description(self):
197 197 """Print the application description."""
198 198 print self.description
199 199 print
200 200
201 201 def print_version(self):
202 202 """Print the version string."""
203 203 print self.version
204 204
205 205 def update_config(self, config):
206 206 """Fire the traits events when the config is updated."""
207 207 # Save a copy of the current config.
208 208 newconfig = deepcopy(self.config)
209 209 # Merge the new config into the current one.
210 210 newconfig._merge(config)
211 211 # Save the combined config as self.config, which triggers the traits
212 212 # events.
213 213 self.config = newconfig
214 214
215 215 def parse_command_line(self, argv=None):
216 216 """Parse the command line arguments."""
217 217 argv = sys.argv[1:] if argv is None else argv
218 218
219 219 if '-h' in argv or '--help' in argv:
220 220 self.print_description()
221 221 self.print_help()
222 222 self.exit(1)
223 223
224 224 if '--version' in argv:
225 225 self.print_version()
226 226 self.exit(1)
227 227
228 228 loader = KeyValueConfigLoader(argv=argv, aliases=self.aliases,
229 229 flags=self.flags)
230 230 config = loader.load_config()
231 231 self.update_config(config)
232 232
233 233 def load_config_file(self, filename, path=None):
234 234 """Load a .py based config file by filename and path."""
235 235 loader = PyFileConfigLoader(filename, path=path)
236 236 config = loader.load_config()
237 237 self.update_config(config)
238 238
239 239 def exit(self, exit_status=0):
240 240 self.log.debug("Exiting application: %s" % self.name)
241 241 sys.exit(exit_status)
@@ -1,105 +1,105 b''
1 1 """
2 2 Tests for IPython.config.application.Application
3 3
4 4 Authors:
5 5
6 6 * Brian Granger
7 7 """
8 8
9 9 #-----------------------------------------------------------------------------
10 10 # Copyright (C) 2008-2011 The IPython Development Team
11 11 #
12 12 # Distributed under the terms of the BSD License. The full license is in
13 13 # the file COPYING, distributed as part of this software.
14 14 #-----------------------------------------------------------------------------
15 15
16 16 #-----------------------------------------------------------------------------
17 17 # Imports
18 18 #-----------------------------------------------------------------------------
19 19
20 20 from unittest import TestCase
21 21
22 22 from IPython.config.configurable import Configurable
23 23
24 24 from IPython.config.application import (
25 25 Application
26 26 )
27 27
28 28 from IPython.utils.traitlets import (
29 29 Bool, Unicode, Int, Float, List, Dict
30 30 )
31 31
32 32 #-----------------------------------------------------------------------------
33 33 # Code
34 34 #-----------------------------------------------------------------------------
35 35
36 36 class Foo(Configurable):
37 37
38 38 i = Int(0, config=True, help="The integer i.")
39 39 j = Int(1, config=True, help="The integer j.")
40 40 name = Unicode(u'Brian', config=True, help="First name.")
41 41
42 42
43 43 class Bar(Configurable):
44 44
45 45 enabled = Bool(True, config=True, help="Enable bar.")
46 46
47 47
48 48 class MyApp(Application):
49 49
50 app_name = Unicode(u'myapp')
50 name = Unicode(u'myapp')
51 51 running = Bool(False, config=True,
52 52 help="Is the app running?")
53 53 classes = List([Bar, Foo])
54 54 config_file = Unicode(u'', config=True,
55 55 help="Load this config file")
56 56
57 57 aliases = Dict(dict(i='Foo.i',j='Foo.j',name='Foo.name',
58 58 enabled='Bar.enabled', log_level='MyApp.log_level'))
59 59
60 60 flags = Dict(dict(enable=({'Bar': {'enabled' : True}}, "Set Bar.enabled to True"),
61 61 disable=({'Bar': {'enabled' : False}}, "Set Bar.enabled to False")))
62 62
63 63 def init_foo(self):
64 64 self.foo = Foo(config=self.config)
65 65
66 66 def init_bar(self):
67 67 self.bar = Bar(config=self.config)
68 68
69 69
70 70 class TestApplication(TestCase):
71 71
72 72 def test_basic(self):
73 73 app = MyApp()
74 self.assertEquals(app.app_name, u'myapp')
74 self.assertEquals(app.name, u'myapp')
75 75 self.assertEquals(app.running, False)
76 76 self.assertEquals(app.classes, [MyApp,Bar,Foo])
77 77 self.assertEquals(app.config_file, u'')
78 78
79 79 def test_config(self):
80 80 app = MyApp()
81 81 app.parse_command_line(["i=10","Foo.j=10","enabled=False","log_level=0"])
82 82 config = app.config
83 83 self.assertEquals(config.Foo.i, 10)
84 84 self.assertEquals(config.Foo.j, 10)
85 85 self.assertEquals(config.Bar.enabled, False)
86 86 self.assertEquals(config.MyApp.log_level,0)
87 87
88 88 def test_config_propagation(self):
89 89 app = MyApp()
90 90 app.parse_command_line(["i=10","Foo.j=10","enabled=False","log_level=0"])
91 91 app.init_foo()
92 92 app.init_bar()
93 93 self.assertEquals(app.foo.i, 10)
94 94 self.assertEquals(app.foo.j, 10)
95 95 self.assertEquals(app.bar.enabled, False)
96 96
97 97 def test_alias(self):
98 98 app = MyApp()
99 99 app.parse_command_line(["--disable"])
100 100 app.init_bar()
101 101 self.assertEquals(app.bar.enabled, False)
102 102 app.parse_command_line(["--enable"])
103 103 app.init_bar()
104 104 self.assertEquals(app.bar.enabled, True)
105 105
@@ -1,132 +1,133 b''
1 1 # encoding: utf-8
2 2 """
3 3 An application for IPython.
4 4
5 5 All top-level applications should use the classes in this module for
6 6 handling configuration and creating componenets.
7 7
8 8 The job of an :class:`Application` is to create the master configuration
9 9 object and then create the configurable objects, passing the config to them.
10 10
11 11 Authors:
12 12
13 13 * Brian Granger
14 14 * Fernando Perez
15 15
16 16 Notes
17 17 -----
18 18 """
19 19
20 20 #-----------------------------------------------------------------------------
21 21 # Copyright (C) 2008-2009 The IPython Development Team
22 22 #
23 23 # Distributed under the terms of the BSD License. The full license is in
24 24 # the file COPYING, distributed as part of this software.
25 25 #-----------------------------------------------------------------------------
26 26
27 27 #-----------------------------------------------------------------------------
28 28 # Imports
29 29 #-----------------------------------------------------------------------------
30 30
31 31 import os
32 32 import sys
33 33
34 34 from IPython.config.application import Application
35 35 from IPython.core import release, crashhandler
36 36 from IPython.utils.path import get_ipython_dir, get_ipython_package_dir
37 37 from IPython.utils.traitlets import Tuple, Unicode, Type
38 38
39 39 #-----------------------------------------------------------------------------
40 40 # Classes and functions
41 41 #-----------------------------------------------------------------------------
42 42
43 43
44 44 class BaseIPythonApplication(Application):
45 45
46 app_name = Unicode(u'ipython')
46 name = Unicode(u'ipython')
47 47 description = Unicode(u'IPython: an enhanced interactive Python shell.')
48 48 version = Unicode(release.version)
49 49
50 50 # The name of the default config file. Track separately from the actual
51 51 # name because some logic happens only if we aren't using the default.
52 52 default_config_file_name = Unicode(u'ipython_config.py')
53 53
54 54 # The directory that contains IPython's builtin profiles.
55 55 builtin_profile_dir = Unicode(
56 56 os.path.join(get_ipython_package_dir(), u'config', u'profile')
57 57 )
58 58
59 config_file_paths = Tuple(Unicode)
59 config_file_paths = Tuple(Unicode, Unicode, Unicode)
60 60 def _config_file_paths_default(self):
61 61 return (os.getcwdu(), self.ipython_dir, self.builtin_profile_dir)
62 62
63 63 profile_name = Unicode(u'', config=True,
64 64 help="""The IPython profile to use."""
65 65 )
66 66
67 67 ipython_dir = Unicode(get_ipython_dir(), config=True, help=
68 68 """
69 69 The name of the IPython directory. This directory is used for logging
70 70 configuration (through profiles), history storage, etc. The default
71 71 is usually $HOME/.ipython. This options can also be specified through
72 72 the environment variable IPYTHON_DIR.
73 73 """
74 74 )
75 75
76 76 # The class to use as the crash handler.
77 77 crash_handler_class = Type(crashhandler.CrashHandler)
78 78
79 79 #-------------------------------------------------------------------------
80 80 # Various stages of Application creation
81 81 #-------------------------------------------------------------------------
82 82
83 83 def init_crash_handler(self):
84 84 """Create a crash handler, typically setting sys.excepthook to it."""
85 85 self.crash_handler = self.crash_handler_class(self)
86 86 sys.excepthook = self.crash_handler
87 87
88 88 def _ipython_dir_changed(self, name, old, new):
89 89 if old in sys.path:
90 90 sys.path.remove(old)
91 91 sys.path.append(os.path.abspath(new))
92 92 if not os.path.isdir(new):
93 93 os.makedirs(new, mode=0777)
94 94 self.config_file_paths = (os.getcwdu(), new, self.builtin_profile_dir)
95 95 self.log.debug("IPYTHON_DIR set to: %s" % new)
96 96
97 97 @property
98 98 def config_file_name(self):
99 99 """Find the config file name for this application."""
100 100 if self.profile_name:
101 101 name_parts = self.default_config_file_name.split('.')
102 102 name_parts.insert(1, u'_' + self.profile_name + u'.')
103 103 return ''.join(name_parts)
104 104 else:
105 105 return self.default_config_file_name
106 106
107 107 def load_config_file(self, suppress_errors=True):
108 108 """Load the config file.
109 109
110 110 By default, errors in loading config are handled, and a warning
111 111 printed on screen. For testing, the suppress_errors option is set
112 112 to False, so errors will make tests fail.
113 113 """
114 114 self.log.debug("Attempting to load config file: %s" %
115 115 self.config_file_name)
116 116 try:
117 117 Application.load_config_file(
118 118 self,
119 119 self.config_file_name,
120 120 path=self.config_file_paths
121 121 )
122 122 except IOError:
123 123 # Only warn if the default config file was NOT being used.
124 124 if not self.config_file_name == self.default_config_file_name:
125 125 self.log.warn("Config file not found, skipping: %s" %
126 126 self.config_file_name, exc_info=True)
127 127 except:
128 if not suppress_errors: # For testing purposes
128 # For testing purposes.
129 if not suppress_errors:
129 130 raise
130 131 self.log.warn("Error loading config file: %s" %
131 132 self.config_file_name, exc_info=True)
132 133
@@ -1,96 +1,96 b''
1 1 """A simple example of how to use IPython.config.application.Application.
2 2
3 3 This should serve as a simple example that shows how the IPython config
4 4 system works. The main classes are:
5 5
6 6 * IPython.config.configurable.Configurable
7 7 * IPython.config.configurable.SingletonConfigurable
8 8 * IPython.config.loader.Config
9 9 * IPython.config.application.Application
10 10
11 11 To see the command line option help, run this program from the command line::
12 12
13 13 $ python appconfig.py -h
14 14
15 15 To make one of your classes configurable (from the command line and config
16 16 files) inherit from Configurable and declare class attributes as traits (see
17 17 classes Foo and Bar below). To make the traits configurable, you will need
18 18 to set the following options:
19 19
20 20 * ``config``: set to ``True`` to make the attribute configurable.
21 21 * ``shortname``: by default, configurable attributes are set using the syntax
22 22 "Classname.attributename". At the command line, this is a bit verbose, so
23 23 we allow "shortnames" to be declared. Setting a shortname is optional, but
24 24 when you do this, you can set the option at the command line using the
25 25 syntax: "shortname=value".
26 26 * ``help``: set the help string to display a help message when the ``-h``
27 27 option is given at the command line. The help string should be valid ReST.
28 28
29 29 When the config attribute of an Application is updated, it will fire all of
30 30 the trait's events for all of the config=True attributes.
31 31 """
32 32
33 33 import sys
34 34
35 35 from IPython.config.configurable import Configurable
36 36 from IPython.config.application import Application
37 37 from IPython.utils.traitlets import (
38 38 Bool, Unicode, Int, Float, List, Dict
39 39 )
40 40
41 41
42 42 class Foo(Configurable):
43 43 """A class that has configurable, typed attributes.
44 44
45 45 """
46 46
47 47 i = Int(0, config=True, help="The integer i.")
48 48 j = Int(1, config=True, help="The integer j.")
49 49 name = Unicode(u'Brian', config=True, help="First name.")
50 50
51 51
52 52 class Bar(Configurable):
53 53
54 54 enabled = Bool(True, config=True, help="Enable bar.")
55 55
56 56
57 57 class MyApp(Application):
58 58
59 app_name = Unicode(u'myapp')
59 name = Unicode(u'myapp')
60 60 running = Bool(False, config=True,
61 61 help="Is the app running?")
62 62 classes = List([Bar, Foo])
63 63 config_file = Unicode(u'', config=True,
64 64 help="Load this config file")
65 65
66 66 aliases = Dict(dict(i='Foo.i',j='Foo.j',name='Foo.name', running='MyApp.running',
67 67 enabled='Bar.enabled', log_level='MyApp.log_level'))
68 68
69 69 flags = Dict(dict(enable=({'Bar': {'enabled' : True}}, "Enable Bar"),
70 70 disable=({'Bar': {'enabled' : False}}, "Disable Bar"),
71 71 debug=({'MyApp':{'log_level':10}}, "Set loglevel to DEBUG")
72 72 ))
73 73
74 74 def init_foo(self):
75 75 # Pass config to other classes for them to inherit the config.
76 76 self.foo = Foo(config=self.config)
77 77
78 78 def init_bar(self):
79 79 # Pass config to other classes for them to inherit the config.
80 80 self.bar = Bar(config=self.config)
81 81
82 82
83 83
84 84 def main():
85 85 app = MyApp()
86 86 app.parse_command_line()
87 87 if app.config_file:
88 88 app.load_config_file(app.config_file)
89 89 app.init_foo()
90 90 app.init_bar()
91 91 print "app.config:"
92 92 print app.config
93 93
94 94
95 95 if __name__ == "__main__":
96 96 main()
General Comments 0
You need to be logged in to leave comments. Login now