##// END OF EJS Templates
Updates to config/application.
Brian Granger -
Show More
@@ -1,230 +1,241
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 A base class for a configurable application.
3 A base class for a configurable application.
4
4
5 Authors:
5 Authors:
6
6
7 * Brian Granger
7 * Brian Granger
8 """
8 """
9
9
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11 # Copyright (C) 2008-2011 The IPython Development Team
11 # Copyright (C) 2008-2011 The IPython Development Team
12 #
12 #
13 # Distributed under the terms of the BSD License. The full license is in
13 # Distributed under the terms of the BSD License. The full license is in
14 # the file COPYING, distributed as part of this software.
14 # the file COPYING, distributed as part of this software.
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16
16
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18 # Imports
18 # Imports
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20
20
21 from copy import deepcopy
21 from copy import deepcopy
22 import logging
22 import logging
23 import sys
23 import sys
24
24
25 from IPython.config.configurable import SingletonConfigurable
25 from IPython.config.configurable import SingletonConfigurable
26 from IPython.config.loader import (
26 from IPython.config.loader import (
27 KeyValueConfigLoader, PyFileConfigLoader, Config
27 KeyValueConfigLoader, PyFileConfigLoader, Config
28 )
28 )
29
29
30 from IPython.utils.traitlets import (
30 from IPython.utils.traitlets import (
31 Unicode, List, Int, Enum, Dict
31 Unicode, List, Int, Enum, Dict
32 )
32 )
33 from IPython.utils.text import indent
33 from IPython.utils.text import indent
34
34
35 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
36 # Descriptions for
36 # Descriptions for
37 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
38
38
39 flag_description = """
39 flag_description = """
40 Flags are command-line arguments passed as '--<flag>'.
40 Flags are command-line arguments passed as '--<flag>'.
41 These take no parameters, unlike regular key-value arguments.
41 These take no parameters, unlike regular key-value arguments.
42 They are typically used for setting boolean flags, or enabling
42 They are typically used for setting boolean flags, or enabling
43 modes that involve setting multiple options together.
43 modes that involve setting multiple options together.
44 """.strip() # trim newlines of front and back
44 """.strip() # trim newlines of front and back
45
45
46 alias_description = """
46 alias_description = """
47 These are commonly set parameters, given abbreviated aliases for convenience.
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
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.
49 <name> is replaced by the real parameter for which it is an alias.
50 """.strip() # trim newlines of front and back
50 """.strip() # trim newlines of front and back
51
51
52 keyvalue_description = """
52 keyvalue_description = """
53 Parameters are set from command-line arguments of the form:
53 Parameters are set from command-line arguments of the form:
54 `Class.trait=value`. Parameters will *never* be prefixed with '-'.
54 `Class.trait=value`. Parameters will *never* be prefixed with '-'.
55 This line is evaluated in Python, so simple expressions are allowed, e.g.
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]
56 `C.a='range(3)'` For setting C.a=[0,1,2]
57 """.strip() # trim newlines of front and back
57 """.strip() # trim newlines of front and back
58
58
59 #-----------------------------------------------------------------------------
59 #-----------------------------------------------------------------------------
60 # Application class
60 # Application class
61 #-----------------------------------------------------------------------------
61 #-----------------------------------------------------------------------------
62
62
63
63
64 class ApplicationError(Exception):
65 pass
66
67
64 class Application(SingletonConfigurable):
68 class Application(SingletonConfigurable):
65 """A singleton application with full configuration support."""
69 """A singleton application with full configuration support."""
66
70
67 # The name of the application, will usually match the name of the command
71 # The name of the application, will usually match the name of the command
68 # line application
72 # line application
69 app_name = Unicode(u'application')
73 app_name = Unicode(u'application')
70
74
71 # The description of the application that is printed at the beginning
75 # The description of the application that is printed at the beginning
72 # of the help.
76 # of the help.
73 description = Unicode(u'This is an application.')
77 description = Unicode(u'This is an application.')
74 # default section descriptions
78 # default section descriptions
75 flag_description = Unicode(flag_description)
79 flag_description = Unicode(flag_description)
76 alias_description = Unicode(alias_description)
80 alias_description = Unicode(alias_description)
77 keyvalue_description = Unicode(keyvalue_description)
81 keyvalue_description = Unicode(keyvalue_description)
78
82
79
83
80 # A sequence of Configurable subclasses whose config=True attributes will
84 # A sequence of Configurable subclasses whose config=True attributes will
81 # be exposed at the command line.
85 # be exposed at the command line.
82 classes = List([])
86 classes = List([])
83
87
84 # The version string of this application.
88 # The version string of this application.
85 version = Unicode(u'0.0')
89 version = Unicode(u'0.0')
86
90
87 # The log level for the application
91 # The log level for the application
88 log_level = Enum((0,10,20,30,40,50), default_value=logging.WARN,
92 log_level = Enum((0,10,20,30,40,50), default_value=logging.WARN,
89 config=True,
93 config=True,
90 help="Set the log level (0,10,20,30,40,50).")
94 help="Set the log level (0,10,20,30,40,50).")
91
95
92 # the alias map for configurables
96 # the alias map for configurables
93 aliases = Dict(dict(log_level='Application.log_level'))
97 aliases = Dict(dict(log_level='Application.log_level'))
94
98
95 # flags for loading Configurables or store_const style flags
99 # flags for loading Configurables or store_const style flags
96 # flags are loaded from this dict by '--key' flags
100 # 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
101 # 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
102 # and the second being the help string for the flag
99 flags = Dict()
103 flags = Dict()
100
104
101
105
102 def __init__(self, **kwargs):
106 def __init__(self, **kwargs):
103 SingletonConfigurable.__init__(self, **kwargs)
107 SingletonConfigurable.__init__(self, **kwargs)
104 # Add my class to self.classes so my attributes appear in command line
108 # Add my class to self.classes so my attributes appear in command line
105 # options.
109 # options.
106 self.classes.insert(0, self.__class__)
110 self.classes.insert(0, self.__class__)
107
111
108 # ensure self.flags dict is valid
112 # ensure self.flags dict is valid
109 for key,value in self.flags.iteritems():
113 for key,value in self.flags.iteritems():
110 assert len(value) == 2, "Bad flag: %r:%s"%(key,value)
114 assert len(value) == 2, "Bad flag: %r:%s"%(key,value)
111 assert isinstance(value[0], (dict, Config)), "Bad flag: %r:%s"%(key,value)
115 assert isinstance(value[0], (dict, Config)), "Bad flag: %r:%s"%(key,value)
112 assert isinstance(value[1], basestring), "Bad flag: %r:%s"%(key,value)
116 assert isinstance(value[1], basestring), "Bad flag: %r:%s"%(key,value)
113 self.init_logging()
117 self.init_logging()
114
118
119 def _config_changed(self, name, old, new):
120 SingletonConfigurable._config_changed(self, name, old, new)
121 self.log.debug('Config changed:')
122 self.log.debug(repr(new))
123
115 def init_logging(self):
124 def init_logging(self):
116 """Start logging for this application.
125 """Start logging for this application.
117
126
118 The default is to log to stdout using a StreaHandler. The log level
127 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
128 starts at loggin.WARN, but this can be adjusted by setting the
120 ``log_level`` attribute.
129 ``log_level`` attribute.
121 """
130 """
122 self.log = logging.getLogger(self.__class__.__name__)
131 self.log = logging.getLogger(self.__class__.__name__)
123 self.log.setLevel(self.log_level)
132 self.log.setLevel(self.log_level)
124 self._log_handler = logging.StreamHandler()
133 self._log_handler = logging.StreamHandler()
125 self._log_formatter = logging.Formatter("[%(name)s] %(message)s")
134 self._log_formatter = logging.Formatter("[%(name)s] %(message)s")
126 self._log_handler.setFormatter(self._log_formatter)
135 self._log_handler.setFormatter(self._log_formatter)
127 self.log.addHandler(self._log_handler)
136 self.log.addHandler(self._log_handler)
128
137
129 def _log_level_changed(self, name, old, new):
138 def _log_level_changed(self, name, old, new):
130 """Adjust the log level when log_level is set."""
139 """Adjust the log level when log_level is set."""
131 self.log.setLevel(new)
140 self.log.setLevel(new)
132
141
133 def print_alias_help(self):
142 def print_alias_help(self):
134 """print the alias part of the help"""
143 """print the alias part of the help"""
135 if not self.aliases:
144 if not self.aliases:
136 return
145 return
137
146
138 print "Aliases"
147 print "Aliases"
139 print "-------"
148 print "-------"
140 print self.alias_description
149 print self.alias_description
141 print
150 print
142
151
143 classdict = {}
152 classdict = {}
144 for c in self.classes:
153 for c in self.classes:
145 classdict[c.__name__] = c
154 classdict[c.__name__] = c
146
155
147 for alias, longname in self.aliases.iteritems():
156 for alias, longname in self.aliases.iteritems():
148 classname, traitname = longname.split('.',1)
157 classname, traitname = longname.split('.',1)
149 cls = classdict[classname]
158 cls = classdict[classname]
150
159
151 trait = cls.class_traits(config=True)[traitname]
160 trait = cls.class_traits(config=True)[traitname]
152 help = trait.get_metadata('help')
161 help = trait.get_metadata('help')
153 print alias, "(%s)"%longname, ':', trait.__class__.__name__
162 print alias, "(%s)"%longname, ':', trait.__class__.__name__
154 if help:
163 if help:
155 print indent(help)
164 print indent(help)
156 print
165 print
157
166
158 def print_flag_help(self):
167 def print_flag_help(self):
159 """print the flag part of the help"""
168 """print the flag part of the help"""
160 if not self.flags:
169 if not self.flags:
161 return
170 return
162
171
163 print "Flags"
172 print "Flags"
164 print "-----"
173 print "-----"
165 print self.flag_description
174 print self.flag_description
166 print
175 print
167
176
168 for m, (cfg,help) in self.flags.iteritems():
177 for m, (cfg,help) in self.flags.iteritems():
169 print '--'+m
178 print '--'+m
170 print indent(help)
179 print indent(help)
171 print
180 print
172
181
173 def print_help(self):
182 def print_help(self):
174 """Print the help for each Configurable class in self.classes."""
183 """Print the help for each Configurable class in self.classes."""
175 self.print_flag_help()
184 self.print_flag_help()
176 self.print_alias_help()
185 self.print_alias_help()
177 if self.classes:
186 if self.classes:
178 print "Class parameters"
187 print "Class parameters"
179 print "----------------"
188 print "----------------"
180 print self.keyvalue_description
189 print self.keyvalue_description
181 print
190 print
182
191
183 for cls in self.classes:
192 for cls in self.classes:
184 cls.class_print_help()
193 cls.class_print_help()
185 print
194 print
186
195
187 def print_description(self):
196 def print_description(self):
188 """Print the application description."""
197 """Print the application description."""
189 print self.description
198 print self.description
190 print
199 print
191
200
192 def print_version(self):
201 def print_version(self):
193 """Print the version string."""
202 """Print the version string."""
194 print self.version
203 print self.version
195
204
196 def update_config(self, config):
205 def update_config(self, config):
197 """Fire the traits events when the config is updated."""
206 """Fire the traits events when the config is updated."""
198 # Save a copy of the current config.
207 # Save a copy of the current config.
199 newconfig = deepcopy(self.config)
208 newconfig = deepcopy(self.config)
200 # Merge the new config into the current one.
209 # Merge the new config into the current one.
201 newconfig._merge(config)
210 newconfig._merge(config)
202 # Save the combined config as self.config, which triggers the traits
211 # Save the combined config as self.config, which triggers the traits
203 # events.
212 # events.
204 self.config = config
213 self.config = config
205
214
206 def parse_command_line(self, argv=None):
215 def parse_command_line(self, argv=None):
207 """Parse the command line arguments."""
216 """Parse the command line arguments."""
208 argv = sys.argv[1:] if argv is None else argv
217 argv = sys.argv[1:] if argv is None else argv
209
218
210 if '-h' in argv or '--help' in argv:
219 if '-h' in argv or '--help' in argv:
211 self.print_description()
220 self.print_description()
212 self.print_help()
221 self.print_help()
213 sys.exit(1)
222 self.exit(1)
214
223
215 if '--version' in argv:
224 if '--version' in argv:
216 self.print_version()
225 self.print_version()
217 sys.exit(1)
226 self.exit(1)
218
227
219 loader = KeyValueConfigLoader(argv=argv, aliases=self.aliases,
228 loader = KeyValueConfigLoader(argv=argv, aliases=self.aliases,
220 flags=self.flags)
229 flags=self.flags)
221 config = loader.load_config()
230 config = loader.load_config()
222 self.update_config(config)
231 self.update_config(config)
223
232
224 def load_config_file(self, filename, path=None):
233 def load_config_file(self, filename, path=None):
225 """Load a .py based config file by filename and path."""
234 """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)
235 loader = PyFileConfigLoader(filename, path=path)
228 config = loader.load_config()
236 config = loader.load_config()
229 self.update_config(config)
237 self.update_config(config)
230
238
239 def exit(self, exit_status=0):
240 self.log.debug("Exiting application: %s" % self.name)
241 sys.exit(exit_status)
General Comments 0
You need to be logged in to leave comments. Login now