##// END OF EJS Templates
move shortname to Application...
MinRK -
Show More
@@ -1,134 +1,190 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 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 #-----------------------------------------------------------------------------
36 38
37 39
38 40 class Application(SingletonConfigurable):
39 41 """A singleton application with full configuration support."""
40 42
41 43 # The name of the application, will usually match the name of the command
42 44 # line application
43 45 app_name = Unicode(u'application')
44 46
45 47 # The description of the application that is printed at the beginning
46 48 # of the help.
47 49 description = Unicode(u'This is an application.')
48 50
49 51 # A sequence of Configurable subclasses whose config=True attributes will
50 52 # be exposed at the command line (shortnames and help).
51 53 classes = List([])
52 54
53 55 # The version string of this application.
54 56 version = Unicode(u'0.0')
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, shortname="log_level",
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
71 90 The default is to log to stdout using a StreaHandler. The log level
72 91 starts at loggin.WARN, but this can be adjusted by setting the
73 92 ``log_level`` attribute.
74 93 """
75 94 self.log = logging.getLogger(self.__class__.__name__)
76 95 self.log.setLevel(self.log_level)
77 96 self._log_handler = logging.StreamHandler()
78 97 self._log_formatter = logging.Formatter("[%(name)s] %(message)s")
79 98 self._log_handler.setFormatter(self._log_formatter)
80 99 self.log.addHandler(self._log_handler)
81 100
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 print
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 print
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 print
91 146
92 147 def print_description(self):
93 148 """Print the application description."""
94 149 print self.description
95 150 print
96 151
97 152 def print_version(self):
98 153 """Print the version string."""
99 154 print self.version
100 155
101 156 def update_config(self, config):
102 157 """Fire the traits events when the config is updated."""
103 158 # Save a copy of the current config.
104 159 newconfig = deepcopy(self.config)
105 160 # Merge the new config into the current one.
106 161 newconfig._merge(config)
107 162 # Save the combined config as self.config, which triggers the traits
108 163 # events.
109 164 self.config = config
110 165
111 166 def parse_command_line(self, argv=None):
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)
119 174
120 175 if '--version' in argv:
121 176 self.print_version()
122 177 sys.exit(1)
123 178
124 loader = KeyValueConfigLoader(argv=argv, classes=self.classes)
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
128 184 def load_config_file(self, filename, path=None):
129 185 """Load a .py based config file by filename and path."""
130 186 # TODO: this raises IOError if filename does not exist.
131 187 loader = PyFileConfigLoader(filename, path=path)
132 188 config = loader.load_config()
133 189 self.update_config(config)
134 190
@@ -1,240 +1,225 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 """
4 4 A base class for objects that are configurable.
5 5
6 6 Authors:
7 7
8 8 * Brian Granger
9 9 * Fernando Perez
10 10 """
11 11
12 12 #-----------------------------------------------------------------------------
13 13 # Copyright (C) 2008-2010 The IPython Development Team
14 14 #
15 15 # Distributed under the terms of the BSD License. The full license is in
16 16 # the file COPYING, distributed as part of this software.
17 17 #-----------------------------------------------------------------------------
18 18
19 19 #-----------------------------------------------------------------------------
20 20 # Imports
21 21 #-----------------------------------------------------------------------------
22 22
23 23 from copy import deepcopy
24 24 import datetime
25 25
26 26 from loader import Config
27 27 from IPython.utils.traitlets import HasTraits, Instance
28 28 from IPython.utils.text import indent
29 29
30 30
31 31 #-----------------------------------------------------------------------------
32 32 # Helper classes for Configurables
33 33 #-----------------------------------------------------------------------------
34 34
35 35
36 36 class ConfigurableError(Exception):
37 37 pass
38 38
39 39
40 40 class MultipleInstanceError(ConfigurableError):
41 41 pass
42 42
43 43 #-----------------------------------------------------------------------------
44 44 # Configurable implementation
45 45 #-----------------------------------------------------------------------------
46 46
47 47
48 48 class Configurable(HasTraits):
49 49
50 50 config = Instance(Config,(),{})
51 51 created = None
52 52
53 53 def __init__(self, **kwargs):
54 54 """Create a conigurable given a config config.
55 55
56 56 Parameters
57 57 ----------
58 58 config : Config
59 59 If this is empty, default values are used. If config is a
60 60 :class:`Config` instance, it will be used to configure the
61 61 instance.
62 62
63 63 Notes
64 64 -----
65 65 Subclasses of Configurable must call the :meth:`__init__` method of
66 66 :class:`Configurable` *before* doing anything else and using
67 67 :func:`super`::
68 68
69 69 class MyConfigurable(Configurable):
70 70 def __init__(self, config=None):
71 71 super(MyConfigurable, self).__init__(config)
72 72 # Then any other code you need to finish initialization.
73 73
74 74 This ensures that instances will be configured properly.
75 75 """
76 76 config = kwargs.pop('config', None)
77 77 if config is not None:
78 78 # We used to deepcopy, but for now we are trying to just save
79 79 # by reference. This *could* have side effects as all components
80 80 # will share config. In fact, I did find such a side effect in
81 81 # _config_changed below. If a config attribute value was a mutable type
82 82 # all instances of a component were getting the same copy, effectively
83 83 # making that a class attribute.
84 84 # self.config = deepcopy(config)
85 85 self.config = config
86 86 # This should go second so individual keyword arguments override
87 87 # the values in config.
88 88 super(Configurable, self).__init__(**kwargs)
89 89 self.created = datetime.datetime.now()
90 90
91 91 #-------------------------------------------------------------------------
92 92 # Static trait notifiations
93 93 #-------------------------------------------------------------------------
94 94
95 95 def _config_changed(self, name, old, new):
96 96 """Update all the class traits having ``config=True`` as metadata.
97 97
98 98 For any class trait with a ``config`` metadata attribute that is
99 99 ``True``, we update the trait with the value of the corresponding
100 100 config entry.
101 101 """
102 102 # Get all traits with a config metadata entry that is True
103 103 traits = self.traits(config=True)
104 104
105 105 # We auto-load config section for this class as well as any parent
106 106 # classes that are Configurable subclasses. This starts with Configurable
107 107 # and works down the mro loading the config for each section.
108 108 section_names = [cls.__name__ for cls in \
109 109 reversed(self.__class__.__mro__) if
110 110 issubclass(cls, Configurable) and issubclass(self.__class__, cls)]
111 111
112 112 for sname in section_names:
113 113 # Don't do a blind getattr as that would cause the config to
114 114 # dynamically create the section with name self.__class__.__name__.
115 115 if new._has_section(sname):
116 116 my_config = new[sname]
117 117 for k, v in traits.iteritems():
118 118 # Don't allow traitlets with config=True to start with
119 119 # uppercase. Otherwise, they are confused with Config
120 120 # subsections. But, developers shouldn't have uppercase
121 121 # attributes anyways! (PEP 6)
122 122 if k[0].upper()==k[0] and not k.startswith('_'):
123 123 raise ConfigurableError('Configurable traitlets with '
124 124 'config=True must start with a lowercase so they are '
125 125 'not confused with Config subsections: %s.%s' % \
126 126 (self.__class__.__name__, k))
127 127 try:
128 128 # Here we grab the value from the config
129 129 # If k has the naming convention of a config
130 130 # section, it will be auto created.
131 131 config_value = my_config[k]
132 132 except KeyError:
133 133 pass
134 134 else:
135 135 # print "Setting %s.%s from %s.%s=%r" % \
136 136 # (self.__class__.__name__,k,sname,k,config_value)
137 137 # We have to do a deepcopy here if we don't deepcopy the entire
138 138 # config object. If we don't, a mutable config_value will be
139 139 # shared by all instances, effectively making it a class attribute.
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)
158 146 final_help = []
159 147 final_help.append(u'%s options' % cls.__name__)
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))
170 155 return '\n'.join(final_help)
171 156
172 157 @classmethod
173 158 def class_print_help(cls):
174 159 print cls.class_get_help()
175 160
176 161
177 162 class SingletonConfigurable(Configurable):
178 163 """A configurable that only allows one instance.
179 164
180 165 This class is for classes that should only have one instance of itself
181 166 or *any* subclass. To create and retrieve such a class use the
182 167 :meth:`SingletonConfigurable.instance` method.
183 168 """
184 169
185 170 _instance = None
186 171
187 172 @classmethod
188 173 def instance(cls, *args, **kwargs):
189 174 """Returns a global instance of this class.
190 175
191 176 This method create a new instance if none have previously been created
192 177 and returns a previously created instance is one already exists.
193 178
194 179 The arguments and keyword arguments passed to this method are passed
195 180 on to the :meth:`__init__` method of the class upon instantiation.
196 181
197 182 Examples
198 183 --------
199 184
200 185 Create a singleton class using instance, and retrieve it::
201 186
202 187 >>> from IPython.config.configurable import SingletonConfigurable
203 188 >>> class Foo(SingletonConfigurable): pass
204 189 >>> foo = Foo.instance()
205 190 >>> foo == Foo.instance()
206 191 True
207 192
208 193 Create a subclass that is retrived using the base class instance::
209 194
210 195 >>> class Bar(SingletonConfigurable): pass
211 196 >>> class Bam(Bar): pass
212 197 >>> bam = Bam.instance()
213 198 >>> bam == Bar.instance()
214 199 True
215 200 """
216 201 # Create and save the instance
217 202 if cls._instance is None:
218 203 inst = cls(*args, **kwargs)
219 204 # Now make sure that the instance will also be returned by
220 205 # the subclasses instance attribute.
221 206 for subclass in cls.mro():
222 207 if issubclass(cls, subclass) and \
223 208 issubclass(subclass, SingletonConfigurable) and \
224 209 subclass != SingletonConfigurable:
225 210 subclass._instance = inst
226 211 else:
227 212 break
228 213 if isinstance(cls._instance, cls):
229 214 return cls._instance
230 215 else:
231 216 raise MultipleInstanceError(
232 217 'Multiple incompatible subclass instances of '
233 218 '%s are being created.' % cls.__name__
234 219 )
235 220
236 221 @classmethod
237 222 def initialized(cls):
238 223 """Has an instance been created?"""
239 224 return hasattr(cls, "_instance") and cls._instance is not None
240 225
@@ -1,491 +1,506 b''
1 1 """A simple configuration system.
2 2
3 3 Authors
4 4 -------
5 5 * Brian Granger
6 6 * Fernando Perez
7 7 """
8 8
9 9 #-----------------------------------------------------------------------------
10 10 # Copyright (C) 2008-2009 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 import __builtin__
21 import re
21 22 import sys
22 23
23 24 from IPython.external import argparse
24 25 from IPython.utils.path import filefind
25 26
26 27 #-----------------------------------------------------------------------------
27 28 # Exceptions
28 29 #-----------------------------------------------------------------------------
29 30
30 31
31 32 class ConfigError(Exception):
32 33 pass
33 34
34 35
35 36 class ConfigLoaderError(ConfigError):
36 37 pass
37 38
38 39 #-----------------------------------------------------------------------------
39 40 # Argparse fix
40 41 #-----------------------------------------------------------------------------
41 42
42 43 # Unfortunately argparse by default prints help messages to stderr instead of
43 44 # stdout. This makes it annoying to capture long help screens at the command
44 45 # line, since one must know how to pipe stderr, which many users don't know how
45 46 # to do. So we override the print_help method with one that defaults to
46 47 # stdout and use our class instead.
47 48
48 49 class ArgumentParser(argparse.ArgumentParser):
49 50 """Simple argparse subclass that prints help to stdout by default."""
50 51
51 52 def print_help(self, file=None):
52 53 if file is None:
53 54 file = sys.stdout
54 55 return super(ArgumentParser, self).print_help(file)
55 56
56 57 print_help.__doc__ = argparse.ArgumentParser.print_help.__doc__
57 58
58 59 #-----------------------------------------------------------------------------
59 60 # Config class for holding config information
60 61 #-----------------------------------------------------------------------------
61 62
62 63
63 64 class Config(dict):
64 65 """An attribute based dict that can do smart merges."""
65 66
66 67 def __init__(self, *args, **kwds):
67 68 dict.__init__(self, *args, **kwds)
68 69 # This sets self.__dict__ = self, but it has to be done this way
69 70 # because we are also overriding __setattr__.
70 71 dict.__setattr__(self, '__dict__', self)
71 72
72 73 def _merge(self, other):
73 74 to_update = {}
74 75 for k, v in other.iteritems():
75 76 if not self.has_key(k):
76 77 to_update[k] = v
77 78 else: # I have this key
78 79 if isinstance(v, Config):
79 80 # Recursively merge common sub Configs
80 81 self[k]._merge(v)
81 82 else:
82 83 # Plain updates for non-Configs
83 84 to_update[k] = v
84 85
85 86 self.update(to_update)
86 87
87 88 def _is_section_key(self, key):
88 89 if key[0].upper()==key[0] and not key.startswith('_'):
89 90 return True
90 91 else:
91 92 return False
92 93
93 94 def __contains__(self, key):
94 95 if self._is_section_key(key):
95 96 return True
96 97 else:
97 98 return super(Config, self).__contains__(key)
98 99 # .has_key is deprecated for dictionaries.
99 100 has_key = __contains__
100 101
101 102 def _has_section(self, key):
102 103 if self._is_section_key(key):
103 104 if super(Config, self).__contains__(key):
104 105 return True
105 106 return False
106 107
107 108 def copy(self):
108 109 return type(self)(dict.copy(self))
109 110
110 111 def __copy__(self):
111 112 return self.copy()
112 113
113 114 def __deepcopy__(self, memo):
114 115 import copy
115 116 return type(self)(copy.deepcopy(self.items()))
116 117
117 118 def __getitem__(self, key):
118 119 # We cannot use directly self._is_section_key, because it triggers
119 120 # infinite recursion on top of PyPy. Instead, we manually fish the
120 121 # bound method.
121 122 is_section_key = self.__class__._is_section_key.__get__(self)
122 123
123 124 # Because we use this for an exec namespace, we need to delegate
124 125 # the lookup of names in __builtin__ to itself. This means
125 126 # that you can't have section or attribute names that are
126 127 # builtins.
127 128 try:
128 129 return getattr(__builtin__, key)
129 130 except AttributeError:
130 131 pass
131 132 if is_section_key(key):
132 133 try:
133 134 return dict.__getitem__(self, key)
134 135 except KeyError:
135 136 c = Config()
136 137 dict.__setitem__(self, key, c)
137 138 return c
138 139 else:
139 140 return dict.__getitem__(self, key)
140 141
141 142 def __setitem__(self, key, value):
142 143 # Don't allow names in __builtin__ to be modified.
143 144 if hasattr(__builtin__, key):
144 145 raise ConfigError('Config variable names cannot have the same name '
145 146 'as a Python builtin: %s' % key)
146 147 if self._is_section_key(key):
147 148 if not isinstance(value, Config):
148 149 raise ValueError('values whose keys begin with an uppercase '
149 150 'char must be Config instances: %r, %r' % (key, value))
150 151 else:
151 152 dict.__setitem__(self, key, value)
152 153
153 154 def __getattr__(self, key):
154 155 try:
155 156 return self.__getitem__(key)
156 157 except KeyError, e:
157 158 raise AttributeError(e)
158 159
159 160 def __setattr__(self, key, value):
160 161 try:
161 162 self.__setitem__(key, value)
162 163 except KeyError, e:
163 164 raise AttributeError(e)
164 165
165 166 def __delattr__(self, key):
166 167 try:
167 168 dict.__delitem__(self, key)
168 169 except KeyError, e:
169 170 raise AttributeError(e)
170 171
171 172
172 173 #-----------------------------------------------------------------------------
173 174 # Config loading classes
174 175 #-----------------------------------------------------------------------------
175 176
176 177
177 178 class ConfigLoader(object):
178 179 """A object for loading configurations from just about anywhere.
179 180
180 181 The resulting configuration is packaged as a :class:`Struct`.
181 182
182 183 Notes
183 184 -----
184 185 A :class:`ConfigLoader` does one thing: load a config from a source
185 186 (file, command line arguments) and returns the data as a :class:`Struct`.
186 187 There are lots of things that :class:`ConfigLoader` does not do. It does
187 188 not implement complex logic for finding config files. It does not handle
188 189 default values or merge multiple configs. These things need to be
189 190 handled elsewhere.
190 191 """
191 192
192 193 def __init__(self):
193 194 """A base class for config loaders.
194 195
195 196 Examples
196 197 --------
197 198
198 199 >>> cl = ConfigLoader()
199 200 >>> config = cl.load_config()
200 201 >>> config
201 202 {}
202 203 """
203 204 self.clear()
204 205
205 206 def clear(self):
206 207 self.config = Config()
207 208
208 209 def load_config(self):
209 210 """Load a config from somewhere, return a :class:`Config` instance.
210 211
211 212 Usually, this will cause self.config to be set and then returned.
212 213 However, in most cases, :meth:`ConfigLoader.clear` should be called
213 214 to erase any previous state.
214 215 """
215 216 self.clear()
216 217 return self.config
217 218
218 219
219 220 class FileConfigLoader(ConfigLoader):
220 221 """A base class for file based configurations.
221 222
222 223 As we add more file based config loaders, the common logic should go
223 224 here.
224 225 """
225 226 pass
226 227
227 228
228 229 class PyFileConfigLoader(FileConfigLoader):
229 230 """A config loader for pure python files.
230 231
231 232 This calls execfile on a plain python file and looks for attributes
232 233 that are all caps. These attribute are added to the config Struct.
233 234 """
234 235
235 236 def __init__(self, filename, path=None):
236 237 """Build a config loader for a filename and path.
237 238
238 239 Parameters
239 240 ----------
240 241 filename : str
241 242 The file name of the config file.
242 243 path : str, list, tuple
243 244 The path to search for the config file on, or a sequence of
244 245 paths to try in order.
245 246 """
246 247 super(PyFileConfigLoader, self).__init__()
247 248 self.filename = filename
248 249 self.path = path
249 250 self.full_filename = ''
250 251 self.data = None
251 252
252 253 def load_config(self):
253 254 """Load the config from a file and return it as a Struct."""
254 255 self.clear()
255 256 self._find_file()
256 257 self._read_file_as_dict()
257 258 self._convert_to_config()
258 259 return self.config
259 260
260 261 def _find_file(self):
261 262 """Try to find the file by searching the paths."""
262 263 self.full_filename = filefind(self.filename, self.path)
263 264
264 265 def _read_file_as_dict(self):
265 266 """Load the config file into self.config, with recursive loading."""
266 267 # This closure is made available in the namespace that is used
267 268 # to exec the config file. This allows users to call
268 269 # load_subconfig('myconfig.py') to load config files recursively.
269 270 # It needs to be a closure because it has references to self.path
270 271 # and self.config. The sub-config is loaded with the same path
271 272 # as the parent, but it uses an empty config which is then merged
272 273 # with the parents.
273 274 def load_subconfig(fname):
274 275 loader = PyFileConfigLoader(fname, self.path)
275 276 try:
276 277 sub_config = loader.load_config()
277 278 except IOError:
278 279 # Pass silently if the sub config is not there. This happens
279 280 # when a user us using a profile, but not the default config.
280 281 pass
281 282 else:
282 283 self.config._merge(sub_config)
283 284
284 285 # Again, this needs to be a closure and should be used in config
285 286 # files to get the config being loaded.
286 287 def get_config():
287 288 return self.config
288 289
289 290 namespace = dict(load_subconfig=load_subconfig, get_config=get_config)
290 291 fs_encoding = sys.getfilesystemencoding() or 'ascii'
291 292 conf_filename = self.full_filename.encode(fs_encoding)
292 293 execfile(conf_filename, namespace)
293 294
294 295 def _convert_to_config(self):
295 296 if self.data is None:
296 297 ConfigLoaderError('self.data does not exist')
297 298
298 299
299 300 class CommandLineConfigLoader(ConfigLoader):
300 301 """A config loader for command line arguments.
301 302
302 303 As we add more command line based loaders, the common logic should go
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.
309 312
310 313 This allows command line options to be gives in the following form::
311 314
312 315 ipython Global.profile="foo" InteractiveShell.autocall=False
313 316 """
314 317
315 def __init__(self, argv=None, classes=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
319 322 ----------
320 323 argv : list
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 -------
330 338 config : Config
331 339 The resulting Config object.
332 340
333 341 Examples
334 342 --------
335 343
336 344 >>> from IPython.config.loader import KeyValueConfigLoader
337 345 >>> cl = KeyValueConfigLoader()
338 346 >>> cl.load_config(["foo='bar'","A.name='brian'","B.number=0"])
339 347 {'A': {'name': 'brian'}, 'B': {'number': 0}, 'foo': 'bar'}
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.classes = classes
352 self.shortnames = shortnames or {}
353 self.macros = macros or {}
347 354
348 def load_config(self, argv=None, classes=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
352 359 ----------
353 360 argv : list, optional
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 classes is None:
367 classes = self.classes
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]
391 389 exec_str = 'self.config.' + lhs + '=' + rhs
392 390 try:
393 391 # Try to see if regular Python syntax will work. This
394 392 # won't handle strings as the quote marks are removed
395 393 # by the system shell.
396 394 exec exec_str in locals(), globals()
397 395 except (NameError, SyntaxError):
398 396 # This case happens if the rhs is a string but without
399 397 # the quote marks. We add the quote marks and see if
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
409 424 def __init__(self, argv=None, *parser_args, **parser_kw):
410 425 """Create a config loader for use with argparse.
411 426
412 427 Parameters
413 428 ----------
414 429
415 430 argv : optional, list
416 431 If given, used to read command-line arguments from, otherwise
417 432 sys.argv[1:] is used.
418 433
419 434 parser_args : tuple
420 435 A tuple of positional arguments that will be passed to the
421 436 constructor of :class:`argparse.ArgumentParser`.
422 437
423 438 parser_kw : dict
424 439 A tuple of keyword arguments that will be passed to the
425 440 constructor of :class:`argparse.ArgumentParser`.
426 441
427 442 Returns
428 443 -------
429 444 config : Config
430 445 The resulting Config object.
431 446 """
432 447 super(CommandLineConfigLoader, self).__init__()
433 448 if argv == None:
434 449 argv = sys.argv[1:]
435 450 self.argv = argv
436 451 self.parser_args = parser_args
437 452 self.version = parser_kw.pop("version", None)
438 453 kwargs = dict(argument_default=argparse.SUPPRESS)
439 454 kwargs.update(parser_kw)
440 455 self.parser_kw = kwargs
441 456
442 457 def load_config(self, argv=None):
443 458 """Parse command line arguments and return as a Config object.
444 459
445 460 Parameters
446 461 ----------
447 462
448 463 args : optional, list
449 464 If given, a list with the structure of sys.argv[1:] to parse
450 465 arguments from. If not given, the instance's self.argv attribute
451 466 (given at construction time) is used."""
452 467 self.clear()
453 468 if argv is None:
454 469 argv = self.argv
455 470 self._create_parser()
456 471 self._parse_args(argv)
457 472 self._convert_to_config()
458 473 return self.config
459 474
460 475 def get_extra_args(self):
461 476 if hasattr(self, 'extra_args'):
462 477 return self.extra_args
463 478 else:
464 479 return []
465 480
466 481 def _create_parser(self):
467 482 self.parser = ArgumentParser(*self.parser_args, **self.parser_kw)
468 483 self._add_arguments()
469 484
470 485 def _add_arguments(self):
471 486 raise NotImplementedError("subclasses must implement _add_arguments")
472 487
473 488 def _parse_args(self, args):
474 489 """self.parser->self.parsed_data"""
475 490 # decode sys.argv to support unicode command-line options
476 491 uargs = []
477 492 for a in args:
478 493 if isinstance(a, str):
479 494 # don't decode if we already got unicode
480 495 a = a.decode(sys.stdin.encoding or
481 496 sys.getdefaultencoding())
482 497 uargs.append(a)
483 498 self.parsed_data, self.extra_args = self.parser.parse_known_args(uargs)
484 499
485 500 def _convert_to_config(self):
486 501 """self.parsed_data->self.config"""
487 502 for k, v in vars(self.parsed_data).iteritems():
488 503 exec_str = 'self.config.' + k + '= v'
489 504 exec exec_str in locals(), globals()
490 505
491 506
@@ -1,90 +1,108 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
30 30 )
31 31
32 32 #-----------------------------------------------------------------------------
33 33 # Code
34 34 #-----------------------------------------------------------------------------
35 35
36 36 class Foo(Configurable):
37 37
38 i = Int(0, config=True, shortname='i', help="The integer i.")
39 j = Int(1, config=True, shortname='j', help="The integer j.")
40 name = Unicode(u'Brian', config=True, shortname='name', help="First name.")
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, shortname="enabled", help="Enable bar.")
45 enabled = Bool(True, config=True, help="Enable bar.")
46 46
47 47
48 48 class MyApp(Application):
49 49
50 50 app_name = Unicode(u'myapp')
51 51 running = Bool(False, config=True, shortname="running",
52 52 help="Is the app running?")
53 53 classes = List([Bar, Foo])
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
60 69 def init_bar(self):
61 70 self.bar = Bar(config=self.config)
62 71
63 72
64 73 class TestApplication(TestCase):
65 74
66 75 def test_basic(self):
67 76 app = MyApp()
68 77 self.assertEquals(app.app_name, u'myapp')
69 78 self.assertEquals(app.running, False)
70 79 self.assertEquals(app.classes, [MyApp,Bar,Foo])
71 80 self.assertEquals(app.config_file, u'')
72 81
73 82 def test_config(self):
74 83 app = MyApp()
75 84 app.parse_command_line(["i=10","Foo.j=10","enabled=False","log_level=0"])
76 85 config = app.config
77 86 self.assertEquals(config.Foo.i, 10)
78 87 self.assertEquals(config.Foo.j, 10)
79 88 self.assertEquals(config.Bar.enabled, False)
80 89 self.assertEquals(config.MyApp.log_level,0)
81 90
82 91 def test_config_propagation(self):
83 92 app = MyApp()
84 93 app.parse_command_line(["i=10","Foo.j=10","enabled=False","log_level=0"])
85 94 app.init_foo()
86 95 app.init_bar()
87 96 self.assertEquals(app.foo.i, 10)
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
@@ -1,172 +1,164 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 """
4 4 Tests for IPython.config.configurable
5 5
6 6 Authors:
7 7
8 8 * Brian Granger
9 9 * Fernando Perez (design help)
10 10 """
11 11
12 12 #-----------------------------------------------------------------------------
13 13 # Copyright (C) 2008-2010 The IPython Development Team
14 14 #
15 15 # Distributed under the terms of the BSD License. The full license is in
16 16 # the file COPYING, distributed as part of this software.
17 17 #-----------------------------------------------------------------------------
18 18
19 19 #-----------------------------------------------------------------------------
20 20 # Imports
21 21 #-----------------------------------------------------------------------------
22 22
23 23 from unittest import TestCase
24 24
25 25 from IPython.config.configurable import (
26 26 Configurable,
27 27 SingletonConfigurable
28 28 )
29 29
30 30 from IPython.utils.traitlets import (
31 31 Int, Float, Str
32 32 )
33 33
34 34 from IPython.config.loader import Config
35 35
36 36
37 37 #-----------------------------------------------------------------------------
38 38 # Test cases
39 39 #-----------------------------------------------------------------------------
40 40
41 41
42 42 class MyConfigurable(Configurable):
43 a = Int(1, config=True, shortname="a", help="The integer a.")
44 b = Float(1.0, config=True, shortname="b", help="The integer b.")
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 (shortname=a)
50 MyConfigurable.a : Int
51 51 The integer a.
52 MyConfigurable.b : Float (shortname=b)
52 MyConfigurable.b : Float
53 53 The integer b."""
54 54
55 55 class Foo(Configurable):
56 a = Int(0, config=True, shortname="a", help="The integer a.")
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, shortname="b", help="The string b.")
62 c = Float(config=True, shortname="c", help="The string c.")
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):
66 66
67 67 def test_default(self):
68 68 c1 = Configurable()
69 69 c2 = Configurable(config=c1.config)
70 70 c3 = Configurable(config=c2.config)
71 71 self.assertEquals(c1.config, c2.config)
72 72 self.assertEquals(c2.config, c3.config)
73 73
74 74 def test_custom(self):
75 75 config = Config()
76 76 config.foo = 'foo'
77 77 config.bar = 'bar'
78 78 c1 = Configurable(config=config)
79 79 c2 = Configurable(config=c1.config)
80 80 c3 = Configurable(config=c2.config)
81 81 self.assertEquals(c1.config, config)
82 82 self.assertEquals(c2.config, config)
83 83 self.assertEquals(c3.config, config)
84 84 # Test that copies are not made
85 85 self.assert_(c1.config is config)
86 86 self.assert_(c2.config is config)
87 87 self.assert_(c3.config is config)
88 88 self.assert_(c1.config is c2.config)
89 89 self.assert_(c2.config is c3.config)
90 90
91 91 def test_inheritance(self):
92 92 config = Config()
93 93 config.MyConfigurable.a = 2
94 94 config.MyConfigurable.b = 2.0
95 95 c1 = MyConfigurable(config=config)
96 96 c2 = MyConfigurable(config=c1.config)
97 97 self.assertEquals(c1.a, config.MyConfigurable.a)
98 98 self.assertEquals(c1.b, config.MyConfigurable.b)
99 99 self.assertEquals(c2.a, config.MyConfigurable.a)
100 100 self.assertEquals(c2.b, config.MyConfigurable.b)
101 101
102 102 def test_parent(self):
103 103 config = Config()
104 104 config.Foo.a = 10
105 105 config.Foo.b = "wow"
106 106 config.Bar.b = 'later'
107 107 config.Bar.c = 100.0
108 108 f = Foo(config=config)
109 109 b = Bar(config=f.config)
110 110 self.assertEquals(f.a, 10)
111 111 self.assertEquals(f.b, 'wow')
112 112 self.assertEquals(b.b, 'gotit')
113 113 self.assertEquals(b.c, 100.0)
114 114
115 115 def test_override1(self):
116 116 config = Config()
117 117 config.MyConfigurable.a = 2
118 118 config.MyConfigurable.b = 2.0
119 119 c = MyConfigurable(a=3, config=config)
120 120 self.assertEquals(c.a, 3)
121 121 self.assertEquals(c.b, config.MyConfigurable.b)
122 122 self.assertEquals(c.c, 'no config')
123 123
124 124 def test_override2(self):
125 125 config = Config()
126 126 config.Foo.a = 1
127 127 config.Bar.b = 'or' # Up above b is config=False, so this won't do it.
128 128 config.Bar.c = 10.0
129 129 c = Bar(config=config)
130 130 self.assertEquals(c.a, config.Foo.a)
131 131 self.assertEquals(c.b, 'gotit')
132 132 self.assertEquals(c.c, config.Bar.c)
133 133 c = Bar(a=2, b='and', c=20.0, config=config)
134 134 self.assertEquals(c.a, 2)
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
149 141
150 142 class TestSingletonConfigurable(TestCase):
151 143
152 144 def test_instance(self):
153 145 from IPython.config.configurable import SingletonConfigurable
154 146 class Foo(SingletonConfigurable): pass
155 147 self.assertEquals(Foo.initialized(), False)
156 148 foo = Foo.instance()
157 149 self.assertEquals(Foo.initialized(), True)
158 150 self.assertEquals(foo, Foo.instance())
159 151 self.assertEquals(SingletonConfigurable._instance, None)
160 152
161 153 def test_inheritance(self):
162 154 class Bar(SingletonConfigurable): pass
163 155 class Bam(Bar): pass
164 156 self.assertEquals(Bar.initialized(), False)
165 157 self.assertEquals(Bam.initialized(), False)
166 158 bam = Bam.instance()
167 159 bam == Bar.instance()
168 160 self.assertEquals(Bar.initialized(), True)
169 161 self.assertEquals(Bam.initialized(), True)
170 162 self.assertEquals(bam, Bam._instance)
171 163 self.assertEquals(bam, Bar._instance)
172 164 self.assertEquals(SingletonConfigurable._instance, None)
@@ -1,207 +1,190 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 """
4 4 Tests for IPython.config.loader
5 5
6 6 Authors:
7 7
8 8 * Brian Granger
9 9 * Fernando Perez (design help)
10 10 """
11 11
12 12 #-----------------------------------------------------------------------------
13 13 # Copyright (C) 2008-2009 The IPython Development Team
14 14 #
15 15 # Distributed under the terms of the BSD License. The full license is in
16 16 # the file COPYING, distributed as part of this software.
17 17 #-----------------------------------------------------------------------------
18 18
19 19 #-----------------------------------------------------------------------------
20 20 # Imports
21 21 #-----------------------------------------------------------------------------
22 22
23 23 import os
24 24 from tempfile import mkstemp
25 25 from unittest import TestCase
26 26
27 27 from IPython.utils.traitlets import Int, Unicode
28 28 from IPython.config.configurable import Configurable
29 29 from IPython.config.loader import (
30 30 Config,
31 31 PyFileConfigLoader,
32 32 KeyValueConfigLoader,
33 33 ArgParseConfigLoader,
34 34 ConfigError
35 35 )
36 36
37 37 #-----------------------------------------------------------------------------
38 38 # Actual tests
39 39 #-----------------------------------------------------------------------------
40 40
41 41
42 42 pyfile = """
43 43 c = get_config()
44 44 c.a=10
45 45 c.b=20
46 46 c.Foo.Bar.value=10
47 47 c.Foo.Bam.value=range(10)
48 48 c.D.C.value='hi there'
49 49 """
50 50
51 51 class TestPyFileCL(TestCase):
52 52
53 53 def test_basic(self):
54 54 fd, fname = mkstemp('.py')
55 55 f = os.fdopen(fd, 'w')
56 56 f.write(pyfile)
57 57 f.close()
58 58 # Unlink the file
59 59 cl = PyFileConfigLoader(fname)
60 60 config = cl.load_config()
61 61 self.assertEquals(config.a, 10)
62 62 self.assertEquals(config.b, 20)
63 63 self.assertEquals(config.Foo.Bar.value, 10)
64 64 self.assertEquals(config.Foo.Bam.value, range(10))
65 65 self.assertEquals(config.D.C.value, 'hi there')
66 66
67 67 class MyLoader1(ArgParseConfigLoader):
68 68 def _add_arguments(self):
69 69 p = self.parser
70 70 p.add_argument('-f', '--foo', dest='Global.foo', type=str)
71 71 p.add_argument('-b', dest='MyClass.bar', type=int)
72 72 p.add_argument('-n', dest='n', action='store_true')
73 73 p.add_argument('Global.bam', type=str)
74 74
75 75 class MyLoader2(ArgParseConfigLoader):
76 76 def _add_arguments(self):
77 77 subparsers = self.parser.add_subparsers(dest='subparser_name')
78 78 subparser1 = subparsers.add_parser('1')
79 79 subparser1.add_argument('-x',dest='Global.x')
80 80 subparser2 = subparsers.add_parser('2')
81 81 subparser2.add_argument('y')
82 82
83 83 class TestArgParseCL(TestCase):
84 84
85 85 def test_basic(self):
86 86 cl = MyLoader1()
87 87 config = cl.load_config('-f hi -b 10 -n wow'.split())
88 88 self.assertEquals(config.Global.foo, 'hi')
89 89 self.assertEquals(config.MyClass.bar, 10)
90 90 self.assertEquals(config.n, True)
91 91 self.assertEquals(config.Global.bam, 'wow')
92 92 config = cl.load_config(['wow'])
93 93 self.assertEquals(config.keys(), ['Global'])
94 94 self.assertEquals(config.Global.keys(), ['bam'])
95 95 self.assertEquals(config.Global.bam, 'wow')
96 96
97 97 def test_add_arguments(self):
98 98 cl = MyLoader2()
99 99 config = cl.load_config('2 frobble'.split())
100 100 self.assertEquals(config.subparser_name, '2')
101 101 self.assertEquals(config.y, 'frobble')
102 102 config = cl.load_config('1 -x frobble'.split())
103 103 self.assertEquals(config.subparser_name, '1')
104 104 self.assertEquals(config.Global.x, 'frobble')
105 105
106 106 def test_argv(self):
107 107 cl = MyLoader1(argv='-f hi -b 10 -n wow'.split())
108 108 config = cl.load_config()
109 109 self.assertEquals(config.Global.foo, 'hi')
110 110 self.assertEquals(config.MyClass.bar, 10)
111 111 self.assertEquals(config.n, True)
112 112 self.assertEquals(config.Global.bam, 'wow')
113 113
114 114
115 115 class TestKeyValueCL(TestCase):
116 116
117 117 def test_basic(self):
118 118 cl = KeyValueConfigLoader()
119 119 argv = [s.strip('c.') for s in pyfile.split('\n')[2:-1]]
120 120 config = cl.load_config(argv)
121 121 self.assertEquals(config.a, 10)
122 122 self.assertEquals(config.b, 20)
123 123 self.assertEquals(config.Foo.Bar.value, 10)
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
147 130 def test_setget(self):
148 131 c = Config()
149 132 c.a = 10
150 133 self.assertEquals(c.a, 10)
151 134 self.assertEquals(c.has_key('b'), False)
152 135
153 136 def test_auto_section(self):
154 137 c = Config()
155 138 self.assertEquals(c.has_key('A'), True)
156 139 self.assertEquals(c._has_section('A'), False)
157 140 A = c.A
158 141 A.foo = 'hi there'
159 142 self.assertEquals(c._has_section('A'), True)
160 143 self.assertEquals(c.A.foo, 'hi there')
161 144 del c.A
162 145 self.assertEquals(len(c.A.keys()),0)
163 146
164 147 def test_merge_doesnt_exist(self):
165 148 c1 = Config()
166 149 c2 = Config()
167 150 c2.bar = 10
168 151 c2.Foo.bar = 10
169 152 c1._merge(c2)
170 153 self.assertEquals(c1.Foo.bar, 10)
171 154 self.assertEquals(c1.bar, 10)
172 155 c2.Bar.bar = 10
173 156 c1._merge(c2)
174 157 self.assertEquals(c1.Bar.bar, 10)
175 158
176 159 def test_merge_exists(self):
177 160 c1 = Config()
178 161 c2 = Config()
179 162 c1.Foo.bar = 10
180 163 c1.Foo.bam = 30
181 164 c2.Foo.bar = 20
182 165 c2.Foo.wow = 40
183 166 c1._merge(c2)
184 167 self.assertEquals(c1.Foo.bam, 30)
185 168 self.assertEquals(c1.Foo.bar, 20)
186 169 self.assertEquals(c1.Foo.wow, 40)
187 170 c2.Foo.Bam.bam = 10
188 171 c1._merge(c2)
189 172 self.assertEquals(c1.Foo.Bam.bam, 10)
190 173
191 174 def test_deepcopy(self):
192 175 c1 = Config()
193 176 c1.Foo.bar = 10
194 177 c1.Foo.bam = 30
195 178 c1.a = 'asdf'
196 179 c1.b = range(10)
197 180 import copy
198 181 c2 = copy.deepcopy(c1)
199 182 self.assertEquals(c1, c2)
200 183 self.assert_(c1 is not c2)
201 184 self.assert_(c1.Foo is not c2.Foo)
202 185
203 186 def test_builtin(self):
204 187 c1 = Config()
205 188 exec 'foo = True' in c1
206 189 self.assertEquals(c1.foo, True)
207 190 self.assertRaises(ConfigError, setattr, c1, 'ValueError', 10)
@@ -1,88 +1,97 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
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, shortname='i', help="The integer i.")
48 48 j = Int(1, config=True, shortname='j', help="The integer j.")
49 49 name = Unicode(u'Brian', config=True, shortname='name', help="First name.")
50 50
51 51
52 52 class Bar(Configurable):
53 53
54 54 enabled = Bool(True, config=True, shortname="enabled", help="Enable bar.")
55 55
56 56
57 57 class MyApp(Application):
58 58
59 59 app_name = Unicode(u'myapp')
60 60 running = Bool(False, config=True, shortname="running",
61 61 help="Is the app running?")
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.
68 77 self.foo = Foo(config=self.config)
69 78
70 79 def init_bar(self):
71 80 # Pass config to other classes for them to inherit the config.
72 81 self.bar = Bar(config=self.config)
73 82
74 83
75 84
76 85 def main():
77 86 app = MyApp()
78 87 app.parse_command_line()
79 88 if app.config_file:
80 89 app.load_config_file(app.config_file)
81 90 app.init_foo()
82 91 app.init_bar()
83 92 print "app.config:"
84 93 print app.config
85 94
86 95
87 96 if __name__ == "__main__":
88 97 main()
General Comments 0
You need to be logged in to leave comments. Login now