##// END OF EJS Templates
add LazyConfigValue...
MinRK -
Show More
@@ -1,382 +1,387 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 A base class for objects that are configurable.
3 A base class for objects that are configurable.
4
4
5 Inheritance diagram:
5 Inheritance diagram:
6
6
7 .. inheritance-diagram:: IPython.config.configurable
7 .. inheritance-diagram:: IPython.config.configurable
8 :parts: 3
8 :parts: 3
9
9
10 Authors:
10 Authors:
11
11
12 * Brian Granger
12 * Brian Granger
13 * Fernando Perez
13 * Fernando Perez
14 * Min RK
14 * Min RK
15 """
15 """
16
16
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18 # Copyright (C) 2008-2011 The IPython Development Team
18 # Copyright (C) 2008-2011 The IPython Development Team
19 #
19 #
20 # Distributed under the terms of the BSD License. The full license is in
20 # Distributed under the terms of the BSD License. The full license is in
21 # the file COPYING, distributed as part of this software.
21 # the file COPYING, distributed as part of this software.
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23
23
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25 # Imports
25 # Imports
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27
27
28 import datetime
28 import datetime
29 from copy import deepcopy
29 from copy import deepcopy
30
30
31 from loader import Config
31 from .loader import Config, LazyConfigValue
32 from IPython.utils.traitlets import HasTraits, Instance
32 from IPython.utils.traitlets import HasTraits, Instance
33 from IPython.utils.text import indent, wrap_paragraphs
33 from IPython.utils.text import indent, wrap_paragraphs
34
34
35
35
36 #-----------------------------------------------------------------------------
36 #-----------------------------------------------------------------------------
37 # Helper classes for Configurables
37 # Helper classes for Configurables
38 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
39
39
40
40
41 class ConfigurableError(Exception):
41 class ConfigurableError(Exception):
42 pass
42 pass
43
43
44
44
45 class MultipleInstanceError(ConfigurableError):
45 class MultipleInstanceError(ConfigurableError):
46 pass
46 pass
47
47
48 #-----------------------------------------------------------------------------
48 #-----------------------------------------------------------------------------
49 # Configurable implementation
49 # Configurable implementation
50 #-----------------------------------------------------------------------------
50 #-----------------------------------------------------------------------------
51
51
52 class Configurable(HasTraits):
52 class Configurable(HasTraits):
53
53
54 config = Instance(Config, (), {})
54 config = Instance(Config, (), {})
55 parent = Instance('IPython.config.configurable.Configurable')
55 parent = Instance('IPython.config.configurable.Configurable')
56 created = None
56 created = None
57
57
58 def __init__(self, **kwargs):
58 def __init__(self, **kwargs):
59 """Create a configurable given a config config.
59 """Create a configurable given a config config.
60
60
61 Parameters
61 Parameters
62 ----------
62 ----------
63 config : Config
63 config : Config
64 If this is empty, default values are used. If config is a
64 If this is empty, default values are used. If config is a
65 :class:`Config` instance, it will be used to configure the
65 :class:`Config` instance, it will be used to configure the
66 instance.
66 instance.
67 parent : Configurable instance, optional
67 parent : Configurable instance, optional
68 The parent Configurable instance of this object.
68 The parent Configurable instance of this object.
69
69
70 Notes
70 Notes
71 -----
71 -----
72 Subclasses of Configurable must call the :meth:`__init__` method of
72 Subclasses of Configurable must call the :meth:`__init__` method of
73 :class:`Configurable` *before* doing anything else and using
73 :class:`Configurable` *before* doing anything else and using
74 :func:`super`::
74 :func:`super`::
75
75
76 class MyConfigurable(Configurable):
76 class MyConfigurable(Configurable):
77 def __init__(self, config=None):
77 def __init__(self, config=None):
78 super(MyConfigurable, self).__init__(config=config)
78 super(MyConfigurable, self).__init__(config=config)
79 # Then any other code you need to finish initialization.
79 # Then any other code you need to finish initialization.
80
80
81 This ensures that instances will be configured properly.
81 This ensures that instances will be configured properly.
82 """
82 """
83 parent = kwargs.pop('parent', None)
83 parent = kwargs.pop('parent', None)
84 if parent is not None:
84 if parent is not None:
85 # config is implied from parent
85 # config is implied from parent
86 if kwargs.get('config', None) is None:
86 if kwargs.get('config', None) is None:
87 kwargs['config'] = parent.config
87 kwargs['config'] = parent.config
88 self.parent = parent
88 self.parent = parent
89
89
90 config = kwargs.pop('config', None)
90 config = kwargs.pop('config', None)
91 if config is not None:
91 if config is not None:
92 # We used to deepcopy, but for now we are trying to just save
92 # We used to deepcopy, but for now we are trying to just save
93 # by reference. This *could* have side effects as all components
93 # by reference. This *could* have side effects as all components
94 # will share config. In fact, I did find such a side effect in
94 # will share config. In fact, I did find such a side effect in
95 # _config_changed below. If a config attribute value was a mutable type
95 # _config_changed below. If a config attribute value was a mutable type
96 # all instances of a component were getting the same copy, effectively
96 # all instances of a component were getting the same copy, effectively
97 # making that a class attribute.
97 # making that a class attribute.
98 # self.config = deepcopy(config)
98 # self.config = deepcopy(config)
99 self.config = config
99 self.config = config
100 # This should go second so individual keyword arguments override
100 # This should go second so individual keyword arguments override
101 # the values in config.
101 # the values in config.
102 super(Configurable, self).__init__(**kwargs)
102 super(Configurable, self).__init__(**kwargs)
103 self.created = datetime.datetime.now()
103 self.created = datetime.datetime.now()
104
104
105 #-------------------------------------------------------------------------
105 #-------------------------------------------------------------------------
106 # Static trait notifiations
106 # Static trait notifiations
107 #-------------------------------------------------------------------------
107 #-------------------------------------------------------------------------
108
108
109 @classmethod
109 @classmethod
110 def section_names(cls):
110 def section_names(cls):
111 """return section names as a list"""
111 """return section names as a list"""
112 return [c.__name__ for c in reversed(cls.__mro__) if
112 return [c.__name__ for c in reversed(cls.__mro__) if
113 issubclass(c, Configurable) and issubclass(cls, c)
113 issubclass(c, Configurable) and issubclass(cls, c)
114 ]
114 ]
115
115
116 def _find_my_config(self, cfg):
116 def _find_my_config(self, cfg):
117 """extract my config from a global Config object
117 """extract my config from a global Config object
118
118
119 will construct a Config object of only the config values that apply to me
119 will construct a Config object of only the config values that apply to me
120 based on my mro(), as well as those of my parent(s) if they exist.
120 based on my mro(), as well as those of my parent(s) if they exist.
121
121
122 If I am Bar and my parent is Foo, and their parent is Tim,
122 If I am Bar and my parent is Foo, and their parent is Tim,
123 this will return merge following config sections, in this order::
123 this will return merge following config sections, in this order::
124
124
125 [Bar, Foo.bar, Tim.Foo.Bar]
125 [Bar, Foo.bar, Tim.Foo.Bar]
126
126
127 With the last item being the highest priority.
127 With the last item being the highest priority.
128 """
128 """
129 cfgs = [cfg]
129 cfgs = [cfg]
130 if self.parent:
130 if self.parent:
131 cfgs.append(self.parent._find_my_config(cfg))
131 cfgs.append(self.parent._find_my_config(cfg))
132 my_config = Config()
132 my_config = Config()
133 for c in cfgs:
133 for c in cfgs:
134 for sname in self.section_names():
134 for sname in self.section_names():
135 # Don't do a blind getattr as that would cause the config to
135 # Don't do a blind getattr as that would cause the config to
136 # dynamically create the section with name Class.__name__.
136 # dynamically create the section with name Class.__name__.
137 if c._has_section(sname):
137 if c._has_section(sname):
138 my_config.merge(c[sname])
138 my_config.merge(c[sname])
139 return my_config
139 return my_config
140
140
141 def _load_config(self, cfg, section_names=None, traits=None):
141 def _load_config(self, cfg, section_names=None, traits=None):
142 """load traits from a Config object"""
142 """load traits from a Config object"""
143
143
144 if traits is None:
144 if traits is None:
145 traits = self.traits(config=True)
145 traits = self.traits(config=True)
146 if section_names is None:
146 if section_names is None:
147 section_names = self.section_names()
147 section_names = self.section_names()
148
148
149 my_config = self._find_my_config(cfg)
149 my_config = self._find_my_config(cfg)
150 for name, config_value in my_config.iteritems():
150 for name, config_value in my_config.iteritems():
151 if name in traits:
151 if name in traits:
152 if isinstance(config_value, LazyConfigValue):
153 # ConfigValue is a wrapper for using append / update on containers
154 # without having to copy the
155 initial = getattr(self, name)
156 config_value = config_value.get_value(initial)
152 # We have to do a deepcopy here if we don't deepcopy the entire
157 # We have to do a deepcopy here if we don't deepcopy the entire
153 # config object. If we don't, a mutable config_value will be
158 # config object. If we don't, a mutable config_value will be
154 # shared by all instances, effectively making it a class attribute.
159 # shared by all instances, effectively making it a class attribute.
155 setattr(self, name, deepcopy(config_value))
160 setattr(self, name, deepcopy(config_value))
156
161
157 def _config_changed(self, name, old, new):
162 def _config_changed(self, name, old, new):
158 """Update all the class traits having ``config=True`` as metadata.
163 """Update all the class traits having ``config=True`` as metadata.
159
164
160 For any class trait with a ``config`` metadata attribute that is
165 For any class trait with a ``config`` metadata attribute that is
161 ``True``, we update the trait with the value of the corresponding
166 ``True``, we update the trait with the value of the corresponding
162 config entry.
167 config entry.
163 """
168 """
164 # Get all traits with a config metadata entry that is True
169 # Get all traits with a config metadata entry that is True
165 traits = self.traits(config=True)
170 traits = self.traits(config=True)
166
171
167 # We auto-load config section for this class as well as any parent
172 # We auto-load config section for this class as well as any parent
168 # classes that are Configurable subclasses. This starts with Configurable
173 # classes that are Configurable subclasses. This starts with Configurable
169 # and works down the mro loading the config for each section.
174 # and works down the mro loading the config for each section.
170 section_names = self.section_names()
175 section_names = self.section_names()
171 self._load_config(new, traits=traits, section_names=section_names)
176 self._load_config(new, traits=traits, section_names=section_names)
172
177
173 def update_config(self, config):
178 def update_config(self, config):
174 """Fire the traits events when the config is updated."""
179 """Fire the traits events when the config is updated."""
175 # Save a copy of the current config.
180 # Save a copy of the current config.
176 newconfig = deepcopy(self.config)
181 newconfig = deepcopy(self.config)
177 # Merge the new config into the current one.
182 # Merge the new config into the current one.
178 newconfig.merge(config)
183 newconfig.merge(config)
179 # Save the combined config as self.config, which triggers the traits
184 # Save the combined config as self.config, which triggers the traits
180 # events.
185 # events.
181 self.config = newconfig
186 self.config = newconfig
182
187
183 @classmethod
188 @classmethod
184 def class_get_help(cls, inst=None):
189 def class_get_help(cls, inst=None):
185 """Get the help string for this class in ReST format.
190 """Get the help string for this class in ReST format.
186
191
187 If `inst` is given, it's current trait values will be used in place of
192 If `inst` is given, it's current trait values will be used in place of
188 class defaults.
193 class defaults.
189 """
194 """
190 assert inst is None or isinstance(inst, cls)
195 assert inst is None or isinstance(inst, cls)
191 final_help = []
196 final_help = []
192 final_help.append(u'%s options' % cls.__name__)
197 final_help.append(u'%s options' % cls.__name__)
193 final_help.append(len(final_help[0])*u'-')
198 final_help.append(len(final_help[0])*u'-')
194 for k, v in sorted(cls.class_traits(config=True).iteritems()):
199 for k, v in sorted(cls.class_traits(config=True).iteritems()):
195 help = cls.class_get_trait_help(v, inst)
200 help = cls.class_get_trait_help(v, inst)
196 final_help.append(help)
201 final_help.append(help)
197 return '\n'.join(final_help)
202 return '\n'.join(final_help)
198
203
199 @classmethod
204 @classmethod
200 def class_get_trait_help(cls, trait, inst=None):
205 def class_get_trait_help(cls, trait, inst=None):
201 """Get the help string for a single trait.
206 """Get the help string for a single trait.
202
207
203 If `inst` is given, it's current trait values will be used in place of
208 If `inst` is given, it's current trait values will be used in place of
204 the class default.
209 the class default.
205 """
210 """
206 assert inst is None or isinstance(inst, cls)
211 assert inst is None or isinstance(inst, cls)
207 lines = []
212 lines = []
208 header = "--%s.%s=<%s>" % (cls.__name__, trait.name, trait.__class__.__name__)
213 header = "--%s.%s=<%s>" % (cls.__name__, trait.name, trait.__class__.__name__)
209 lines.append(header)
214 lines.append(header)
210 if inst is not None:
215 if inst is not None:
211 lines.append(indent('Current: %r' % getattr(inst, trait.name), 4))
216 lines.append(indent('Current: %r' % getattr(inst, trait.name), 4))
212 else:
217 else:
213 try:
218 try:
214 dvr = repr(trait.get_default_value())
219 dvr = repr(trait.get_default_value())
215 except Exception:
220 except Exception:
216 dvr = None # ignore defaults we can't construct
221 dvr = None # ignore defaults we can't construct
217 if dvr is not None:
222 if dvr is not None:
218 if len(dvr) > 64:
223 if len(dvr) > 64:
219 dvr = dvr[:61]+'...'
224 dvr = dvr[:61]+'...'
220 lines.append(indent('Default: %s' % dvr, 4))
225 lines.append(indent('Default: %s' % dvr, 4))
221 if 'Enum' in trait.__class__.__name__:
226 if 'Enum' in trait.__class__.__name__:
222 # include Enum choices
227 # include Enum choices
223 lines.append(indent('Choices: %r' % (trait.values,)))
228 lines.append(indent('Choices: %r' % (trait.values,)))
224
229
225 help = trait.get_metadata('help')
230 help = trait.get_metadata('help')
226 if help is not None:
231 if help is not None:
227 help = '\n'.join(wrap_paragraphs(help, 76))
232 help = '\n'.join(wrap_paragraphs(help, 76))
228 lines.append(indent(help, 4))
233 lines.append(indent(help, 4))
229 return '\n'.join(lines)
234 return '\n'.join(lines)
230
235
231 @classmethod
236 @classmethod
232 def class_print_help(cls, inst=None):
237 def class_print_help(cls, inst=None):
233 """Get the help string for a single trait and print it."""
238 """Get the help string for a single trait and print it."""
234 print cls.class_get_help(inst)
239 print cls.class_get_help(inst)
235
240
236 @classmethod
241 @classmethod
237 def class_config_section(cls):
242 def class_config_section(cls):
238 """Get the config class config section"""
243 """Get the config class config section"""
239 def c(s):
244 def c(s):
240 """return a commented, wrapped block."""
245 """return a commented, wrapped block."""
241 s = '\n\n'.join(wrap_paragraphs(s, 78))
246 s = '\n\n'.join(wrap_paragraphs(s, 78))
242
247
243 return '# ' + s.replace('\n', '\n# ')
248 return '# ' + s.replace('\n', '\n# ')
244
249
245 # section header
250 # section header
246 breaker = '#' + '-'*78
251 breaker = '#' + '-'*78
247 s = "# %s configuration" % cls.__name__
252 s = "# %s configuration" % cls.__name__
248 lines = [breaker, s, breaker, '']
253 lines = [breaker, s, breaker, '']
249 # get the description trait
254 # get the description trait
250 desc = cls.class_traits().get('description')
255 desc = cls.class_traits().get('description')
251 if desc:
256 if desc:
252 desc = desc.default_value
257 desc = desc.default_value
253 else:
258 else:
254 # no description trait, use __doc__
259 # no description trait, use __doc__
255 desc = getattr(cls, '__doc__', '')
260 desc = getattr(cls, '__doc__', '')
256 if desc:
261 if desc:
257 lines.append(c(desc))
262 lines.append(c(desc))
258 lines.append('')
263 lines.append('')
259
264
260 parents = []
265 parents = []
261 for parent in cls.mro():
266 for parent in cls.mro():
262 # only include parents that are not base classes
267 # only include parents that are not base classes
263 # and are not the class itself
268 # and are not the class itself
264 # and have some configurable traits to inherit
269 # and have some configurable traits to inherit
265 if parent is not cls and issubclass(parent, Configurable) and \
270 if parent is not cls and issubclass(parent, Configurable) and \
266 parent.class_traits(config=True):
271 parent.class_traits(config=True):
267 parents.append(parent)
272 parents.append(parent)
268
273
269 if parents:
274 if parents:
270 pstr = ', '.join([ p.__name__ for p in parents ])
275 pstr = ', '.join([ p.__name__ for p in parents ])
271 lines.append(c('%s will inherit config from: %s'%(cls.__name__, pstr)))
276 lines.append(c('%s will inherit config from: %s'%(cls.__name__, pstr)))
272 lines.append('')
277 lines.append('')
273
278
274 for name, trait in cls.class_traits(config=True).iteritems():
279 for name, trait in cls.class_traits(config=True).iteritems():
275 help = trait.get_metadata('help') or ''
280 help = trait.get_metadata('help') or ''
276 lines.append(c(help))
281 lines.append(c(help))
277 lines.append('# c.%s.%s = %r'%(cls.__name__, name, trait.get_default_value()))
282 lines.append('# c.%s.%s = %r'%(cls.__name__, name, trait.get_default_value()))
278 lines.append('')
283 lines.append('')
279 return '\n'.join(lines)
284 return '\n'.join(lines)
280
285
281
286
282
287
283 class SingletonConfigurable(Configurable):
288 class SingletonConfigurable(Configurable):
284 """A configurable that only allows one instance.
289 """A configurable that only allows one instance.
285
290
286 This class is for classes that should only have one instance of itself
291 This class is for classes that should only have one instance of itself
287 or *any* subclass. To create and retrieve such a class use the
292 or *any* subclass. To create and retrieve such a class use the
288 :meth:`SingletonConfigurable.instance` method.
293 :meth:`SingletonConfigurable.instance` method.
289 """
294 """
290
295
291 _instance = None
296 _instance = None
292
297
293 @classmethod
298 @classmethod
294 def _walk_mro(cls):
299 def _walk_mro(cls):
295 """Walk the cls.mro() for parent classes that are also singletons
300 """Walk the cls.mro() for parent classes that are also singletons
296
301
297 For use in instance()
302 For use in instance()
298 """
303 """
299
304
300 for subclass in cls.mro():
305 for subclass in cls.mro():
301 if issubclass(cls, subclass) and \
306 if issubclass(cls, subclass) and \
302 issubclass(subclass, SingletonConfigurable) and \
307 issubclass(subclass, SingletonConfigurable) and \
303 subclass != SingletonConfigurable:
308 subclass != SingletonConfigurable:
304 yield subclass
309 yield subclass
305
310
306 @classmethod
311 @classmethod
307 def clear_instance(cls):
312 def clear_instance(cls):
308 """unset _instance for this class and singleton parents.
313 """unset _instance for this class and singleton parents.
309 """
314 """
310 if not cls.initialized():
315 if not cls.initialized():
311 return
316 return
312 for subclass in cls._walk_mro():
317 for subclass in cls._walk_mro():
313 if isinstance(subclass._instance, cls):
318 if isinstance(subclass._instance, cls):
314 # only clear instances that are instances
319 # only clear instances that are instances
315 # of the calling class
320 # of the calling class
316 subclass._instance = None
321 subclass._instance = None
317
322
318 @classmethod
323 @classmethod
319 def instance(cls, *args, **kwargs):
324 def instance(cls, *args, **kwargs):
320 """Returns a global instance of this class.
325 """Returns a global instance of this class.
321
326
322 This method create a new instance if none have previously been created
327 This method create a new instance if none have previously been created
323 and returns a previously created instance is one already exists.
328 and returns a previously created instance is one already exists.
324
329
325 The arguments and keyword arguments passed to this method are passed
330 The arguments and keyword arguments passed to this method are passed
326 on to the :meth:`__init__` method of the class upon instantiation.
331 on to the :meth:`__init__` method of the class upon instantiation.
327
332
328 Examples
333 Examples
329 --------
334 --------
330
335
331 Create a singleton class using instance, and retrieve it::
336 Create a singleton class using instance, and retrieve it::
332
337
333 >>> from IPython.config.configurable import SingletonConfigurable
338 >>> from IPython.config.configurable import SingletonConfigurable
334 >>> class Foo(SingletonConfigurable): pass
339 >>> class Foo(SingletonConfigurable): pass
335 >>> foo = Foo.instance()
340 >>> foo = Foo.instance()
336 >>> foo == Foo.instance()
341 >>> foo == Foo.instance()
337 True
342 True
338
343
339 Create a subclass that is retrived using the base class instance::
344 Create a subclass that is retrived using the base class instance::
340
345
341 >>> class Bar(SingletonConfigurable): pass
346 >>> class Bar(SingletonConfigurable): pass
342 >>> class Bam(Bar): pass
347 >>> class Bam(Bar): pass
343 >>> bam = Bam.instance()
348 >>> bam = Bam.instance()
344 >>> bam == Bar.instance()
349 >>> bam == Bar.instance()
345 True
350 True
346 """
351 """
347 # Create and save the instance
352 # Create and save the instance
348 if cls._instance is None:
353 if cls._instance is None:
349 inst = cls(*args, **kwargs)
354 inst = cls(*args, **kwargs)
350 # Now make sure that the instance will also be returned by
355 # Now make sure that the instance will also be returned by
351 # parent classes' _instance attribute.
356 # parent classes' _instance attribute.
352 for subclass in cls._walk_mro():
357 for subclass in cls._walk_mro():
353 subclass._instance = inst
358 subclass._instance = inst
354
359
355 if isinstance(cls._instance, cls):
360 if isinstance(cls._instance, cls):
356 return cls._instance
361 return cls._instance
357 else:
362 else:
358 raise MultipleInstanceError(
363 raise MultipleInstanceError(
359 'Multiple incompatible subclass instances of '
364 'Multiple incompatible subclass instances of '
360 '%s are being created.' % cls.__name__
365 '%s are being created.' % cls.__name__
361 )
366 )
362
367
363 @classmethod
368 @classmethod
364 def initialized(cls):
369 def initialized(cls):
365 """Has an instance been created?"""
370 """Has an instance been created?"""
366 return hasattr(cls, "_instance") and cls._instance is not None
371 return hasattr(cls, "_instance") and cls._instance is not None
367
372
368
373
369 class LoggingConfigurable(Configurable):
374 class LoggingConfigurable(Configurable):
370 """A parent class for Configurables that log.
375 """A parent class for Configurables that log.
371
376
372 Subclasses have a log trait, and the default behavior
377 Subclasses have a log trait, and the default behavior
373 is to get the logger from the currently running Application
378 is to get the logger from the currently running Application
374 via Application.instance().log.
379 via Application.instance().log.
375 """
380 """
376
381
377 log = Instance('logging.Logger')
382 log = Instance('logging.Logger')
378 def _log_default(self):
383 def _log_default(self):
379 from IPython.config.application import Application
384 from IPython.config.application import Application
380 return Application.instance().log
385 return Application.instance().log
381
386
382
387
@@ -1,718 +1,812 b''
1 """A simple configuration system.
1 """A simple configuration system.
2
2
3 Inheritance diagram:
3 Inheritance diagram:
4
4
5 .. inheritance-diagram:: IPython.config.loader
5 .. inheritance-diagram:: IPython.config.loader
6 :parts: 3
6 :parts: 3
7
7
8 Authors
8 Authors
9 -------
9 -------
10 * Brian Granger
10 * Brian Granger
11 * Fernando Perez
11 * Fernando Perez
12 * Min RK
12 * Min RK
13 """
13 """
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Copyright (C) 2008-2011 The IPython Development Team
16 # Copyright (C) 2008-2011 The IPython Development Team
17 #
17 #
18 # Distributed under the terms of the BSD License. The full license is in
18 # Distributed under the terms of the BSD License. The full license is in
19 # the file COPYING, distributed as part of this software.
19 # the file COPYING, distributed as part of this software.
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21
21
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23 # Imports
23 # Imports
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25
25
26 import __builtin__ as builtin_mod
26 import __builtin__ as builtin_mod
27 import argparse
27 import argparse
28 import copy
28 import os
29 import os
29 import re
30 import re
30 import sys
31 import sys
31
32
32 from IPython.utils.path import filefind, get_ipython_dir
33 from IPython.utils.path import filefind, get_ipython_dir
33 from IPython.utils import py3compat, warn
34 from IPython.utils import py3compat, warn
34 from IPython.utils.encoding import DEFAULT_ENCODING
35 from IPython.utils.encoding import DEFAULT_ENCODING
36 from IPython.utils.traitlets import HasTraits, List, Any, TraitError
35
37
36 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
37 # Exceptions
39 # Exceptions
38 #-----------------------------------------------------------------------------
40 #-----------------------------------------------------------------------------
39
41
40
42
41 class ConfigError(Exception):
43 class ConfigError(Exception):
42 pass
44 pass
43
45
44 class ConfigLoaderError(ConfigError):
46 class ConfigLoaderError(ConfigError):
45 pass
47 pass
46
48
47 class ConfigFileNotFound(ConfigError):
49 class ConfigFileNotFound(ConfigError):
48 pass
50 pass
49
51
50 class ArgumentError(ConfigLoaderError):
52 class ArgumentError(ConfigLoaderError):
51 pass
53 pass
52
54
53 #-----------------------------------------------------------------------------
55 #-----------------------------------------------------------------------------
54 # Argparse fix
56 # Argparse fix
55 #-----------------------------------------------------------------------------
57 #-----------------------------------------------------------------------------
56
58
57 # Unfortunately argparse by default prints help messages to stderr instead of
59 # Unfortunately argparse by default prints help messages to stderr instead of
58 # stdout. This makes it annoying to capture long help screens at the command
60 # stdout. This makes it annoying to capture long help screens at the command
59 # line, since one must know how to pipe stderr, which many users don't know how
61 # line, since one must know how to pipe stderr, which many users don't know how
60 # to do. So we override the print_help method with one that defaults to
62 # to do. So we override the print_help method with one that defaults to
61 # stdout and use our class instead.
63 # stdout and use our class instead.
62
64
63 class ArgumentParser(argparse.ArgumentParser):
65 class ArgumentParser(argparse.ArgumentParser):
64 """Simple argparse subclass that prints help to stdout by default."""
66 """Simple argparse subclass that prints help to stdout by default."""
65
67
66 def print_help(self, file=None):
68 def print_help(self, file=None):
67 if file is None:
69 if file is None:
68 file = sys.stdout
70 file = sys.stdout
69 return super(ArgumentParser, self).print_help(file)
71 return super(ArgumentParser, self).print_help(file)
70
72
71 print_help.__doc__ = argparse.ArgumentParser.print_help.__doc__
73 print_help.__doc__ = argparse.ArgumentParser.print_help.__doc__
72
74
73 #-----------------------------------------------------------------------------
75 #-----------------------------------------------------------------------------
74 # Config class for holding config information
76 # Config class for holding config information
75 #-----------------------------------------------------------------------------
77 #-----------------------------------------------------------------------------
76
78
79 class LazyConfigValue(HasTraits):
80 """Proxy object for exposing methods on configurable containers
81
82 Exposes:
83
84 - append, extend, insert on lists
85 - update on dicts
86 - update, add on sets
87 """
88
89 _value = None
90
91 # list methods
92 _extend = List()
93
94 def append(self, obj):
95 self._extend.append(obj)
96
97 def extend(self, other):
98 self._extend.extend(other)
99
100 _inserts = List()
101 def insert(self, index, other):
102 if not isinstance(index, int):
103 raise TypeError("An integer is required")
104 self._inserts.append((index, other))
105
106 # dict methods
107 # update is used for both dict and set
108 _update = Any()
109 def update(self, other):
110 if self._update is None:
111 if isinstance(other, dict):
112 self._update = {}
113 else:
114 self._update = set()
115 self._update.update(other)
116
117 # set methods
118 def add(self, obj):
119 self.update({obj})
120
121 def get_value(self, initial):
122 """construct the value from the initial one
123
124 after applying any insert / extend / update changes
125 """
126 if self._value is not None:
127 return self._value
128 value = copy.deepcopy(initial)
129 if isinstance(value, list):
130 for idx, obj in self._inserts:
131 value.insert(idx, obj)
132 value.extend(self._extend)
133 elif isinstance(value, dict):
134 if self._update:
135 value.update(self._update)
136 elif isinstance(value, set):
137 if self._update:
138 value.update(self._update)
139 self._value = value
140 return value
141
142 def to_dict(self):
143 """return JSONable dict form of my data
144
145 Currently update as dict or set, extend as list, and inserts as list of tuples.
146 """
147 d = {}
148 if self._update:
149 d['update'] = self._update
150 if self._extend:
151 d['extend'] = self._extend
152 elif self._inserts:
153 d['inserts'] = self._inserts
154 return d
155
77
156
78 class Config(dict):
157 class Config(dict):
79 """An attribute based dict that can do smart merges."""
158 """An attribute based dict that can do smart merges."""
80
159
81 def __init__(self, *args, **kwds):
160 def __init__(self, *args, **kwds):
82 dict.__init__(self, *args, **kwds)
161 dict.__init__(self, *args, **kwds)
83 # This sets self.__dict__ = self, but it has to be done this way
162 # This sets self.__dict__ = self, but it has to be done this way
84 # because we are also overriding __setattr__.
163 # because we are also overriding __setattr__.
85 dict.__setattr__(self, '__dict__', self)
164 dict.__setattr__(self, '__dict__', self)
86 self._ensure_subconfig()
165 self._ensure_subconfig()
87
166
88 def _ensure_subconfig(self):
167 def _ensure_subconfig(self):
89 """ensure that sub-dicts that should be Config objects are
168 """ensure that sub-dicts that should be Config objects are
90
169
91 casts dicts that are under section keys to Config objects,
170 casts dicts that are under section keys to Config objects,
92 which is necessary for constructing Config objects from dict literals.
171 which is necessary for constructing Config objects from dict literals.
93 """
172 """
94 for key in self:
173 for key in self:
95 obj = self[key]
174 obj = self[key]
96 if self._is_section_key(key) \
175 if self._is_section_key(key) \
97 and isinstance(obj, dict) \
176 and isinstance(obj, dict) \
98 and not isinstance(obj, Config):
177 and not isinstance(obj, Config):
99 dict.__setattr__(self, key, Config(obj))
178 dict.__setattr__(self, key, Config(obj))
100
179
101 def _merge(self, other):
180 def _merge(self, other):
102 """deprecated alias, use Config.merge()"""
181 """deprecated alias, use Config.merge()"""
103 self.merge(other)
182 self.merge(other)
104
183
105 def merge(self, other):
184 def merge(self, other):
106 """merge another config object into this one"""
185 """merge another config object into this one"""
107 to_update = {}
186 to_update = {}
108 for k, v in other.iteritems():
187 for k, v in other.iteritems():
109 if k not in self:
188 if k not in self:
110 to_update[k] = v
189 to_update[k] = v
111 else: # I have this key
190 else: # I have this key
112 if isinstance(v, Config) and isinstance(self[k], Config):
191 if isinstance(v, Config) and isinstance(self[k], Config):
113 # Recursively merge common sub Configs
192 # Recursively merge common sub Configs
114 self[k].merge(v)
193 self[k].merge(v)
115 else:
194 else:
116 # Plain updates for non-Configs
195 # Plain updates for non-Configs
117 to_update[k] = v
196 to_update[k] = v
118
197
119 self.update(to_update)
198 self.update(to_update)
120
199
121 def _is_section_key(self, key):
200 def _is_section_key(self, key):
122 if key[0].upper()==key[0] and not key.startswith('_'):
201 if key[0].upper()==key[0] and not key.startswith('_'):
123 return True
202 return True
124 else:
203 else:
125 return False
204 return False
126
205
127 def __contains__(self, key):
206 def __contains__(self, key):
207 # allow nested contains of the form `Section.key in config`
208 if '.' in key:
209 first, remainder = key.split('.', 1)
210 if first not in self:
211 return False
212 return remainder in self[first]
213
214 # we always have Sections
128 if self._is_section_key(key):
215 if self._is_section_key(key):
129 return True
216 return True
130 else:
217 else:
131 return super(Config, self).__contains__(key)
218 return super(Config, self).__contains__(key)
132 # .has_key is deprecated for dictionaries.
219 # .has_key is deprecated for dictionaries.
133 has_key = __contains__
220 has_key = __contains__
134
221
135 def _has_section(self, key):
222 def _has_section(self, key):
136 if self._is_section_key(key):
223 if self._is_section_key(key):
137 if super(Config, self).__contains__(key):
224 if super(Config, self).__contains__(key):
138 return True
225 return True
139 return False
226 return False
140
227
141 def copy(self):
228 def copy(self):
142 return type(self)(dict.copy(self))
229 return type(self)(dict.copy(self))
143
230
144 def __copy__(self):
231 def __copy__(self):
145 return self.copy()
232 return self.copy()
146
233
147 def __deepcopy__(self, memo):
234 def __deepcopy__(self, memo):
148 import copy
235 import copy
149 return type(self)(copy.deepcopy(self.items()))
236 return type(self)(copy.deepcopy(self.items()))
150
237
151 def __getitem__(self, key):
238 def __getitem__(self, key):
152 # We cannot use directly self._is_section_key, because it triggers
239 # We cannot use directly self._is_section_key, because it triggers
153 # infinite recursion on top of PyPy. Instead, we manually fish the
240 # infinite recursion on top of PyPy. Instead, we manually fish the
154 # bound method.
241 # bound method.
155 is_section_key = self.__class__._is_section_key.__get__(self)
242 is_section_key = self.__class__._is_section_key.__get__(self)
156
243
157 # Because we use this for an exec namespace, we need to delegate
244 # Because we use this for an exec namespace, we need to delegate
158 # the lookup of names in __builtin__ to itself. This means
245 # the lookup of names in __builtin__ to itself. This means
159 # that you can't have section or attribute names that are
246 # that you can't have section or attribute names that are
160 # builtins.
247 # builtins.
161 try:
248 try:
162 return getattr(builtin_mod, key)
249 return getattr(builtin_mod, key)
163 except AttributeError:
250 except AttributeError:
164 pass
251 pass
165 if is_section_key(key):
252 if is_section_key(key):
166 try:
253 try:
167 return dict.__getitem__(self, key)
254 return dict.__getitem__(self, key)
168 except KeyError:
255 except KeyError:
169 c = Config()
256 c = Config()
170 dict.__setitem__(self, key, c)
257 dict.__setitem__(self, key, c)
171 return c
258 return c
172 else:
259 else:
260 try:
173 return dict.__getitem__(self, key)
261 return dict.__getitem__(self, key)
262 except KeyError:
263 # undefined
264 v = LazyConfigValue()
265 dict.__setitem__(self, key, v)
266 return v
267
174
268
175 def __setitem__(self, key, value):
269 def __setitem__(self, key, value):
176 if self._is_section_key(key):
270 if self._is_section_key(key):
177 if not isinstance(value, Config):
271 if not isinstance(value, Config):
178 raise ValueError('values whose keys begin with an uppercase '
272 raise ValueError('values whose keys begin with an uppercase '
179 'char must be Config instances: %r, %r' % (key, value))
273 'char must be Config instances: %r, %r' % (key, value))
180 else:
274 else:
181 dict.__setitem__(self, key, value)
275 dict.__setitem__(self, key, value)
182
276
183 def __getattr__(self, key):
277 def __getattr__(self, key):
184 try:
278 try:
185 return self.__getitem__(key)
279 return self.__getitem__(key)
186 except KeyError as e:
280 except KeyError as e:
187 raise AttributeError(e)
281 raise AttributeError(e)
188
282
189 def __setattr__(self, key, value):
283 def __setattr__(self, key, value):
190 try:
284 try:
191 self.__setitem__(key, value)
285 self.__setitem__(key, value)
192 except KeyError as e:
286 except KeyError as e:
193 raise AttributeError(e)
287 raise AttributeError(e)
194
288
195 def __delattr__(self, key):
289 def __delattr__(self, key):
196 try:
290 try:
197 dict.__delitem__(self, key)
291 dict.__delitem__(self, key)
198 except KeyError as e:
292 except KeyError as e:
199 raise AttributeError(e)
293 raise AttributeError(e)
200
294
201
295
202 #-----------------------------------------------------------------------------
296 #-----------------------------------------------------------------------------
203 # Config loading classes
297 # Config loading classes
204 #-----------------------------------------------------------------------------
298 #-----------------------------------------------------------------------------
205
299
206
300
207 class ConfigLoader(object):
301 class ConfigLoader(object):
208 """A object for loading configurations from just about anywhere.
302 """A object for loading configurations from just about anywhere.
209
303
210 The resulting configuration is packaged as a :class:`Struct`.
304 The resulting configuration is packaged as a :class:`Struct`.
211
305
212 Notes
306 Notes
213 -----
307 -----
214 A :class:`ConfigLoader` does one thing: load a config from a source
308 A :class:`ConfigLoader` does one thing: load a config from a source
215 (file, command line arguments) and returns the data as a :class:`Struct`.
309 (file, command line arguments) and returns the data as a :class:`Struct`.
216 There are lots of things that :class:`ConfigLoader` does not do. It does
310 There are lots of things that :class:`ConfigLoader` does not do. It does
217 not implement complex logic for finding config files. It does not handle
311 not implement complex logic for finding config files. It does not handle
218 default values or merge multiple configs. These things need to be
312 default values or merge multiple configs. These things need to be
219 handled elsewhere.
313 handled elsewhere.
220 """
314 """
221
315
222 def __init__(self):
316 def __init__(self):
223 """A base class for config loaders.
317 """A base class for config loaders.
224
318
225 Examples
319 Examples
226 --------
320 --------
227
321
228 >>> cl = ConfigLoader()
322 >>> cl = ConfigLoader()
229 >>> config = cl.load_config()
323 >>> config = cl.load_config()
230 >>> config
324 >>> config
231 {}
325 {}
232 """
326 """
233 self.clear()
327 self.clear()
234
328
235 def clear(self):
329 def clear(self):
236 self.config = Config()
330 self.config = Config()
237
331
238 def load_config(self):
332 def load_config(self):
239 """Load a config from somewhere, return a :class:`Config` instance.
333 """Load a config from somewhere, return a :class:`Config` instance.
240
334
241 Usually, this will cause self.config to be set and then returned.
335 Usually, this will cause self.config to be set and then returned.
242 However, in most cases, :meth:`ConfigLoader.clear` should be called
336 However, in most cases, :meth:`ConfigLoader.clear` should be called
243 to erase any previous state.
337 to erase any previous state.
244 """
338 """
245 self.clear()
339 self.clear()
246 return self.config
340 return self.config
247
341
248
342
249 class FileConfigLoader(ConfigLoader):
343 class FileConfigLoader(ConfigLoader):
250 """A base class for file based configurations.
344 """A base class for file based configurations.
251
345
252 As we add more file based config loaders, the common logic should go
346 As we add more file based config loaders, the common logic should go
253 here.
347 here.
254 """
348 """
255 pass
349 pass
256
350
257
351
258 class PyFileConfigLoader(FileConfigLoader):
352 class PyFileConfigLoader(FileConfigLoader):
259 """A config loader for pure python files.
353 """A config loader for pure python files.
260
354
261 This calls execfile on a plain python file and looks for attributes
355 This calls execfile on a plain python file and looks for attributes
262 that are all caps. These attribute are added to the config Struct.
356 that are all caps. These attribute are added to the config Struct.
263 """
357 """
264
358
265 def __init__(self, filename, path=None):
359 def __init__(self, filename, path=None):
266 """Build a config loader for a filename and path.
360 """Build a config loader for a filename and path.
267
361
268 Parameters
362 Parameters
269 ----------
363 ----------
270 filename : str
364 filename : str
271 The file name of the config file.
365 The file name of the config file.
272 path : str, list, tuple
366 path : str, list, tuple
273 The path to search for the config file on, or a sequence of
367 The path to search for the config file on, or a sequence of
274 paths to try in order.
368 paths to try in order.
275 """
369 """
276 super(PyFileConfigLoader, self).__init__()
370 super(PyFileConfigLoader, self).__init__()
277 self.filename = filename
371 self.filename = filename
278 self.path = path
372 self.path = path
279 self.full_filename = ''
373 self.full_filename = ''
280 self.data = None
374 self.data = None
281
375
282 def load_config(self):
376 def load_config(self):
283 """Load the config from a file and return it as a Struct."""
377 """Load the config from a file and return it as a Struct."""
284 self.clear()
378 self.clear()
285 try:
379 try:
286 self._find_file()
380 self._find_file()
287 except IOError as e:
381 except IOError as e:
288 raise ConfigFileNotFound(str(e))
382 raise ConfigFileNotFound(str(e))
289 self._read_file_as_dict()
383 self._read_file_as_dict()
290 self._convert_to_config()
384 self._convert_to_config()
291 return self.config
385 return self.config
292
386
293 def _find_file(self):
387 def _find_file(self):
294 """Try to find the file by searching the paths."""
388 """Try to find the file by searching the paths."""
295 self.full_filename = filefind(self.filename, self.path)
389 self.full_filename = filefind(self.filename, self.path)
296
390
297 def _read_file_as_dict(self):
391 def _read_file_as_dict(self):
298 """Load the config file into self.config, with recursive loading."""
392 """Load the config file into self.config, with recursive loading."""
299 # This closure is made available in the namespace that is used
393 # This closure is made available in the namespace that is used
300 # to exec the config file. It allows users to call
394 # to exec the config file. It allows users to call
301 # load_subconfig('myconfig.py') to load config files recursively.
395 # load_subconfig('myconfig.py') to load config files recursively.
302 # It needs to be a closure because it has references to self.path
396 # It needs to be a closure because it has references to self.path
303 # and self.config. The sub-config is loaded with the same path
397 # and self.config. The sub-config is loaded with the same path
304 # as the parent, but it uses an empty config which is then merged
398 # as the parent, but it uses an empty config which is then merged
305 # with the parents.
399 # with the parents.
306
400
307 # If a profile is specified, the config file will be loaded
401 # If a profile is specified, the config file will be loaded
308 # from that profile
402 # from that profile
309
403
310 def load_subconfig(fname, profile=None):
404 def load_subconfig(fname, profile=None):
311 # import here to prevent circular imports
405 # import here to prevent circular imports
312 from IPython.core.profiledir import ProfileDir, ProfileDirError
406 from IPython.core.profiledir import ProfileDir, ProfileDirError
313 if profile is not None:
407 if profile is not None:
314 try:
408 try:
315 profile_dir = ProfileDir.find_profile_dir_by_name(
409 profile_dir = ProfileDir.find_profile_dir_by_name(
316 get_ipython_dir(),
410 get_ipython_dir(),
317 profile,
411 profile,
318 )
412 )
319 except ProfileDirError:
413 except ProfileDirError:
320 return
414 return
321 path = profile_dir.location
415 path = profile_dir.location
322 else:
416 else:
323 path = self.path
417 path = self.path
324 loader = PyFileConfigLoader(fname, path)
418 loader = PyFileConfigLoader(fname, path)
325 try:
419 try:
326 sub_config = loader.load_config()
420 sub_config = loader.load_config()
327 except ConfigFileNotFound:
421 except ConfigFileNotFound:
328 # Pass silently if the sub config is not there. This happens
422 # Pass silently if the sub config is not there. This happens
329 # when a user s using a profile, but not the default config.
423 # when a user s using a profile, but not the default config.
330 pass
424 pass
331 else:
425 else:
332 self.config.merge(sub_config)
426 self.config.merge(sub_config)
333
427
334 # Again, this needs to be a closure and should be used in config
428 # Again, this needs to be a closure and should be used in config
335 # files to get the config being loaded.
429 # files to get the config being loaded.
336 def get_config():
430 def get_config():
337 return self.config
431 return self.config
338
432
339 namespace = dict(
433 namespace = dict(
340 load_subconfig=load_subconfig,
434 load_subconfig=load_subconfig,
341 get_config=get_config,
435 get_config=get_config,
342 __file__=self.full_filename,
436 __file__=self.full_filename,
343 )
437 )
344 fs_encoding = sys.getfilesystemencoding() or 'ascii'
438 fs_encoding = sys.getfilesystemencoding() or 'ascii'
345 conf_filename = self.full_filename.encode(fs_encoding)
439 conf_filename = self.full_filename.encode(fs_encoding)
346 py3compat.execfile(conf_filename, namespace)
440 py3compat.execfile(conf_filename, namespace)
347
441
348 def _convert_to_config(self):
442 def _convert_to_config(self):
349 if self.data is None:
443 if self.data is None:
350 ConfigLoaderError('self.data does not exist')
444 ConfigLoaderError('self.data does not exist')
351
445
352
446
353 class CommandLineConfigLoader(ConfigLoader):
447 class CommandLineConfigLoader(ConfigLoader):
354 """A config loader for command line arguments.
448 """A config loader for command line arguments.
355
449
356 As we add more command line based loaders, the common logic should go
450 As we add more command line based loaders, the common logic should go
357 here.
451 here.
358 """
452 """
359
453
360 def _exec_config_str(self, lhs, rhs):
454 def _exec_config_str(self, lhs, rhs):
361 """execute self.config.<lhs> = <rhs>
455 """execute self.config.<lhs> = <rhs>
362
456
363 * expands ~ with expanduser
457 * expands ~ with expanduser
364 * tries to assign with raw eval, otherwise assigns with just the string,
458 * tries to assign with raw eval, otherwise assigns with just the string,
365 allowing `--C.a=foobar` and `--C.a="foobar"` to be equivalent. *Not*
459 allowing `--C.a=foobar` and `--C.a="foobar"` to be equivalent. *Not*
366 equivalent are `--C.a=4` and `--C.a='4'`.
460 equivalent are `--C.a=4` and `--C.a='4'`.
367 """
461 """
368 rhs = os.path.expanduser(rhs)
462 rhs = os.path.expanduser(rhs)
369 try:
463 try:
370 # Try to see if regular Python syntax will work. This
464 # Try to see if regular Python syntax will work. This
371 # won't handle strings as the quote marks are removed
465 # won't handle strings as the quote marks are removed
372 # by the system shell.
466 # by the system shell.
373 value = eval(rhs)
467 value = eval(rhs)
374 except (NameError, SyntaxError):
468 except (NameError, SyntaxError):
375 # This case happens if the rhs is a string.
469 # This case happens if the rhs is a string.
376 value = rhs
470 value = rhs
377
471
378 exec u'self.config.%s = value' % lhs
472 exec u'self.config.%s = value' % lhs
379
473
380 def _load_flag(self, cfg):
474 def _load_flag(self, cfg):
381 """update self.config from a flag, which can be a dict or Config"""
475 """update self.config from a flag, which can be a dict or Config"""
382 if isinstance(cfg, (dict, Config)):
476 if isinstance(cfg, (dict, Config)):
383 # don't clobber whole config sections, update
477 # don't clobber whole config sections, update
384 # each section from config:
478 # each section from config:
385 for sec,c in cfg.iteritems():
479 for sec,c in cfg.iteritems():
386 self.config[sec].update(c)
480 self.config[sec].update(c)
387 else:
481 else:
388 raise TypeError("Invalid flag: %r" % cfg)
482 raise TypeError("Invalid flag: %r" % cfg)
389
483
390 # raw --identifier=value pattern
484 # raw --identifier=value pattern
391 # but *also* accept '-' as wordsep, for aliases
485 # but *also* accept '-' as wordsep, for aliases
392 # accepts: --foo=a
486 # accepts: --foo=a
393 # --Class.trait=value
487 # --Class.trait=value
394 # --alias-name=value
488 # --alias-name=value
395 # rejects: -foo=value
489 # rejects: -foo=value
396 # --foo
490 # --foo
397 # --Class.trait
491 # --Class.trait
398 kv_pattern = re.compile(r'\-\-[A-Za-z][\w\-]*(\.[\w\-]+)*\=.*')
492 kv_pattern = re.compile(r'\-\-[A-Za-z][\w\-]*(\.[\w\-]+)*\=.*')
399
493
400 # just flags, no assignments, with two *or one* leading '-'
494 # just flags, no assignments, with two *or one* leading '-'
401 # accepts: --foo
495 # accepts: --foo
402 # -foo-bar-again
496 # -foo-bar-again
403 # rejects: --anything=anything
497 # rejects: --anything=anything
404 # --two.word
498 # --two.word
405
499
406 flag_pattern = re.compile(r'\-\-?\w+[\-\w]*$')
500 flag_pattern = re.compile(r'\-\-?\w+[\-\w]*$')
407
501
408 class KeyValueConfigLoader(CommandLineConfigLoader):
502 class KeyValueConfigLoader(CommandLineConfigLoader):
409 """A config loader that loads key value pairs from the command line.
503 """A config loader that loads key value pairs from the command line.
410
504
411 This allows command line options to be gives in the following form::
505 This allows command line options to be gives in the following form::
412
506
413 ipython --profile="foo" --InteractiveShell.autocall=False
507 ipython --profile="foo" --InteractiveShell.autocall=False
414 """
508 """
415
509
416 def __init__(self, argv=None, aliases=None, flags=None):
510 def __init__(self, argv=None, aliases=None, flags=None):
417 """Create a key value pair config loader.
511 """Create a key value pair config loader.
418
512
419 Parameters
513 Parameters
420 ----------
514 ----------
421 argv : list
515 argv : list
422 A list that has the form of sys.argv[1:] which has unicode
516 A list that has the form of sys.argv[1:] which has unicode
423 elements of the form u"key=value". If this is None (default),
517 elements of the form u"key=value". If this is None (default),
424 then sys.argv[1:] will be used.
518 then sys.argv[1:] will be used.
425 aliases : dict
519 aliases : dict
426 A dict of aliases for configurable traits.
520 A dict of aliases for configurable traits.
427 Keys are the short aliases, Values are the resolved trait.
521 Keys are the short aliases, Values are the resolved trait.
428 Of the form: `{'alias' : 'Configurable.trait'}`
522 Of the form: `{'alias' : 'Configurable.trait'}`
429 flags : dict
523 flags : dict
430 A dict of flags, keyed by str name. Vaues can be Config objects,
524 A dict of flags, keyed by str name. Vaues can be Config objects,
431 dicts, or "key=value" strings. If Config or dict, when the flag
525 dicts, or "key=value" strings. If Config or dict, when the flag
432 is triggered, The flag is loaded as `self.config.update(m)`.
526 is triggered, The flag is loaded as `self.config.update(m)`.
433
527
434 Returns
528 Returns
435 -------
529 -------
436 config : Config
530 config : Config
437 The resulting Config object.
531 The resulting Config object.
438
532
439 Examples
533 Examples
440 --------
534 --------
441
535
442 >>> from IPython.config.loader import KeyValueConfigLoader
536 >>> from IPython.config.loader import KeyValueConfigLoader
443 >>> cl = KeyValueConfigLoader()
537 >>> cl = KeyValueConfigLoader()
444 >>> d = cl.load_config(["--A.name='brian'","--B.number=0"])
538 >>> d = cl.load_config(["--A.name='brian'","--B.number=0"])
445 >>> sorted(d.items())
539 >>> sorted(d.items())
446 [('A', {'name': 'brian'}), ('B', {'number': 0})]
540 [('A', {'name': 'brian'}), ('B', {'number': 0})]
447 """
541 """
448 self.clear()
542 self.clear()
449 if argv is None:
543 if argv is None:
450 argv = sys.argv[1:]
544 argv = sys.argv[1:]
451 self.argv = argv
545 self.argv = argv
452 self.aliases = aliases or {}
546 self.aliases = aliases or {}
453 self.flags = flags or {}
547 self.flags = flags or {}
454
548
455
549
456 def clear(self):
550 def clear(self):
457 super(KeyValueConfigLoader, self).clear()
551 super(KeyValueConfigLoader, self).clear()
458 self.extra_args = []
552 self.extra_args = []
459
553
460
554
461 def _decode_argv(self, argv, enc=None):
555 def _decode_argv(self, argv, enc=None):
462 """decode argv if bytes, using stin.encoding, falling back on default enc"""
556 """decode argv if bytes, using stin.encoding, falling back on default enc"""
463 uargv = []
557 uargv = []
464 if enc is None:
558 if enc is None:
465 enc = DEFAULT_ENCODING
559 enc = DEFAULT_ENCODING
466 for arg in argv:
560 for arg in argv:
467 if not isinstance(arg, unicode):
561 if not isinstance(arg, unicode):
468 # only decode if not already decoded
562 # only decode if not already decoded
469 arg = arg.decode(enc)
563 arg = arg.decode(enc)
470 uargv.append(arg)
564 uargv.append(arg)
471 return uargv
565 return uargv
472
566
473
567
474 def load_config(self, argv=None, aliases=None, flags=None):
568 def load_config(self, argv=None, aliases=None, flags=None):
475 """Parse the configuration and generate the Config object.
569 """Parse the configuration and generate the Config object.
476
570
477 After loading, any arguments that are not key-value or
571 After loading, any arguments that are not key-value or
478 flags will be stored in self.extra_args - a list of
572 flags will be stored in self.extra_args - a list of
479 unparsed command-line arguments. This is used for
573 unparsed command-line arguments. This is used for
480 arguments such as input files or subcommands.
574 arguments such as input files or subcommands.
481
575
482 Parameters
576 Parameters
483 ----------
577 ----------
484 argv : list, optional
578 argv : list, optional
485 A list that has the form of sys.argv[1:] which has unicode
579 A list that has the form of sys.argv[1:] which has unicode
486 elements of the form u"key=value". If this is None (default),
580 elements of the form u"key=value". If this is None (default),
487 then self.argv will be used.
581 then self.argv will be used.
488 aliases : dict
582 aliases : dict
489 A dict of aliases for configurable traits.
583 A dict of aliases for configurable traits.
490 Keys are the short aliases, Values are the resolved trait.
584 Keys are the short aliases, Values are the resolved trait.
491 Of the form: `{'alias' : 'Configurable.trait'}`
585 Of the form: `{'alias' : 'Configurable.trait'}`
492 flags : dict
586 flags : dict
493 A dict of flags, keyed by str name. Values can be Config objects
587 A dict of flags, keyed by str name. Values can be Config objects
494 or dicts. When the flag is triggered, The config is loaded as
588 or dicts. When the flag is triggered, The config is loaded as
495 `self.config.update(cfg)`.
589 `self.config.update(cfg)`.
496 """
590 """
497 self.clear()
591 self.clear()
498 if argv is None:
592 if argv is None:
499 argv = self.argv
593 argv = self.argv
500 if aliases is None:
594 if aliases is None:
501 aliases = self.aliases
595 aliases = self.aliases
502 if flags is None:
596 if flags is None:
503 flags = self.flags
597 flags = self.flags
504
598
505 # ensure argv is a list of unicode strings:
599 # ensure argv is a list of unicode strings:
506 uargv = self._decode_argv(argv)
600 uargv = self._decode_argv(argv)
507 for idx,raw in enumerate(uargv):
601 for idx,raw in enumerate(uargv):
508 # strip leading '-'
602 # strip leading '-'
509 item = raw.lstrip('-')
603 item = raw.lstrip('-')
510
604
511 if raw == '--':
605 if raw == '--':
512 # don't parse arguments after '--'
606 # don't parse arguments after '--'
513 # this is useful for relaying arguments to scripts, e.g.
607 # this is useful for relaying arguments to scripts, e.g.
514 # ipython -i foo.py --matplotlib=qt -- args after '--' go-to-foo.py
608 # ipython -i foo.py --matplotlib=qt -- args after '--' go-to-foo.py
515 self.extra_args.extend(uargv[idx+1:])
609 self.extra_args.extend(uargv[idx+1:])
516 break
610 break
517
611
518 if kv_pattern.match(raw):
612 if kv_pattern.match(raw):
519 lhs,rhs = item.split('=',1)
613 lhs,rhs = item.split('=',1)
520 # Substitute longnames for aliases.
614 # Substitute longnames for aliases.
521 if lhs in aliases:
615 if lhs in aliases:
522 lhs = aliases[lhs]
616 lhs = aliases[lhs]
523 if '.' not in lhs:
617 if '.' not in lhs:
524 # probably a mistyped alias, but not technically illegal
618 # probably a mistyped alias, but not technically illegal
525 warn.warn("Unrecognized alias: '%s', it will probably have no effect."%lhs)
619 warn.warn("Unrecognized alias: '%s', it will probably have no effect."%lhs)
526 try:
620 try:
527 self._exec_config_str(lhs, rhs)
621 self._exec_config_str(lhs, rhs)
528 except Exception:
622 except Exception:
529 raise ArgumentError("Invalid argument: '%s'" % raw)
623 raise ArgumentError("Invalid argument: '%s'" % raw)
530
624
531 elif flag_pattern.match(raw):
625 elif flag_pattern.match(raw):
532 if item in flags:
626 if item in flags:
533 cfg,help = flags[item]
627 cfg,help = flags[item]
534 self._load_flag(cfg)
628 self._load_flag(cfg)
535 else:
629 else:
536 raise ArgumentError("Unrecognized flag: '%s'"%raw)
630 raise ArgumentError("Unrecognized flag: '%s'"%raw)
537 elif raw.startswith('-'):
631 elif raw.startswith('-'):
538 kv = '--'+item
632 kv = '--'+item
539 if kv_pattern.match(kv):
633 if kv_pattern.match(kv):
540 raise ArgumentError("Invalid argument: '%s', did you mean '%s'?"%(raw, kv))
634 raise ArgumentError("Invalid argument: '%s', did you mean '%s'?"%(raw, kv))
541 else:
635 else:
542 raise ArgumentError("Invalid argument: '%s'"%raw)
636 raise ArgumentError("Invalid argument: '%s'"%raw)
543 else:
637 else:
544 # keep all args that aren't valid in a list,
638 # keep all args that aren't valid in a list,
545 # in case our parent knows what to do with them.
639 # in case our parent knows what to do with them.
546 self.extra_args.append(item)
640 self.extra_args.append(item)
547 return self.config
641 return self.config
548
642
549 class ArgParseConfigLoader(CommandLineConfigLoader):
643 class ArgParseConfigLoader(CommandLineConfigLoader):
550 """A loader that uses the argparse module to load from the command line."""
644 """A loader that uses the argparse module to load from the command line."""
551
645
552 def __init__(self, argv=None, aliases=None, flags=None, *parser_args, **parser_kw):
646 def __init__(self, argv=None, aliases=None, flags=None, *parser_args, **parser_kw):
553 """Create a config loader for use with argparse.
647 """Create a config loader for use with argparse.
554
648
555 Parameters
649 Parameters
556 ----------
650 ----------
557
651
558 argv : optional, list
652 argv : optional, list
559 If given, used to read command-line arguments from, otherwise
653 If given, used to read command-line arguments from, otherwise
560 sys.argv[1:] is used.
654 sys.argv[1:] is used.
561
655
562 parser_args : tuple
656 parser_args : tuple
563 A tuple of positional arguments that will be passed to the
657 A tuple of positional arguments that will be passed to the
564 constructor of :class:`argparse.ArgumentParser`.
658 constructor of :class:`argparse.ArgumentParser`.
565
659
566 parser_kw : dict
660 parser_kw : dict
567 A tuple of keyword arguments that will be passed to the
661 A tuple of keyword arguments that will be passed to the
568 constructor of :class:`argparse.ArgumentParser`.
662 constructor of :class:`argparse.ArgumentParser`.
569
663
570 Returns
664 Returns
571 -------
665 -------
572 config : Config
666 config : Config
573 The resulting Config object.
667 The resulting Config object.
574 """
668 """
575 super(CommandLineConfigLoader, self).__init__()
669 super(CommandLineConfigLoader, self).__init__()
576 self.clear()
670 self.clear()
577 if argv is None:
671 if argv is None:
578 argv = sys.argv[1:]
672 argv = sys.argv[1:]
579 self.argv = argv
673 self.argv = argv
580 self.aliases = aliases or {}
674 self.aliases = aliases or {}
581 self.flags = flags or {}
675 self.flags = flags or {}
582
676
583 self.parser_args = parser_args
677 self.parser_args = parser_args
584 self.version = parser_kw.pop("version", None)
678 self.version = parser_kw.pop("version", None)
585 kwargs = dict(argument_default=argparse.SUPPRESS)
679 kwargs = dict(argument_default=argparse.SUPPRESS)
586 kwargs.update(parser_kw)
680 kwargs.update(parser_kw)
587 self.parser_kw = kwargs
681 self.parser_kw = kwargs
588
682
589 def load_config(self, argv=None, aliases=None, flags=None):
683 def load_config(self, argv=None, aliases=None, flags=None):
590 """Parse command line arguments and return as a Config object.
684 """Parse command line arguments and return as a Config object.
591
685
592 Parameters
686 Parameters
593 ----------
687 ----------
594
688
595 args : optional, list
689 args : optional, list
596 If given, a list with the structure of sys.argv[1:] to parse
690 If given, a list with the structure of sys.argv[1:] to parse
597 arguments from. If not given, the instance's self.argv attribute
691 arguments from. If not given, the instance's self.argv attribute
598 (given at construction time) is used."""
692 (given at construction time) is used."""
599 self.clear()
693 self.clear()
600 if argv is None:
694 if argv is None:
601 argv = self.argv
695 argv = self.argv
602 if aliases is None:
696 if aliases is None:
603 aliases = self.aliases
697 aliases = self.aliases
604 if flags is None:
698 if flags is None:
605 flags = self.flags
699 flags = self.flags
606 self._create_parser(aliases, flags)
700 self._create_parser(aliases, flags)
607 self._parse_args(argv)
701 self._parse_args(argv)
608 self._convert_to_config()
702 self._convert_to_config()
609 return self.config
703 return self.config
610
704
611 def get_extra_args(self):
705 def get_extra_args(self):
612 if hasattr(self, 'extra_args'):
706 if hasattr(self, 'extra_args'):
613 return self.extra_args
707 return self.extra_args
614 else:
708 else:
615 return []
709 return []
616
710
617 def _create_parser(self, aliases=None, flags=None):
711 def _create_parser(self, aliases=None, flags=None):
618 self.parser = ArgumentParser(*self.parser_args, **self.parser_kw)
712 self.parser = ArgumentParser(*self.parser_args, **self.parser_kw)
619 self._add_arguments(aliases, flags)
713 self._add_arguments(aliases, flags)
620
714
621 def _add_arguments(self, aliases=None, flags=None):
715 def _add_arguments(self, aliases=None, flags=None):
622 raise NotImplementedError("subclasses must implement _add_arguments")
716 raise NotImplementedError("subclasses must implement _add_arguments")
623
717
624 def _parse_args(self, args):
718 def _parse_args(self, args):
625 """self.parser->self.parsed_data"""
719 """self.parser->self.parsed_data"""
626 # decode sys.argv to support unicode command-line options
720 # decode sys.argv to support unicode command-line options
627 enc = DEFAULT_ENCODING
721 enc = DEFAULT_ENCODING
628 uargs = [py3compat.cast_unicode(a, enc) for a in args]
722 uargs = [py3compat.cast_unicode(a, enc) for a in args]
629 self.parsed_data, self.extra_args = self.parser.parse_known_args(uargs)
723 self.parsed_data, self.extra_args = self.parser.parse_known_args(uargs)
630
724
631 def _convert_to_config(self):
725 def _convert_to_config(self):
632 """self.parsed_data->self.config"""
726 """self.parsed_data->self.config"""
633 for k, v in vars(self.parsed_data).iteritems():
727 for k, v in vars(self.parsed_data).iteritems():
634 exec "self.config.%s = v"%k in locals(), globals()
728 exec "self.config.%s = v"%k in locals(), globals()
635
729
636 class KVArgParseConfigLoader(ArgParseConfigLoader):
730 class KVArgParseConfigLoader(ArgParseConfigLoader):
637 """A config loader that loads aliases and flags with argparse,
731 """A config loader that loads aliases and flags with argparse,
638 but will use KVLoader for the rest. This allows better parsing
732 but will use KVLoader for the rest. This allows better parsing
639 of common args, such as `ipython -c 'print 5'`, but still gets
733 of common args, such as `ipython -c 'print 5'`, but still gets
640 arbitrary config with `ipython --InteractiveShell.use_readline=False`"""
734 arbitrary config with `ipython --InteractiveShell.use_readline=False`"""
641
735
642 def _add_arguments(self, aliases=None, flags=None):
736 def _add_arguments(self, aliases=None, flags=None):
643 self.alias_flags = {}
737 self.alias_flags = {}
644 # print aliases, flags
738 # print aliases, flags
645 if aliases is None:
739 if aliases is None:
646 aliases = self.aliases
740 aliases = self.aliases
647 if flags is None:
741 if flags is None:
648 flags = self.flags
742 flags = self.flags
649 paa = self.parser.add_argument
743 paa = self.parser.add_argument
650 for key,value in aliases.iteritems():
744 for key,value in aliases.iteritems():
651 if key in flags:
745 if key in flags:
652 # flags
746 # flags
653 nargs = '?'
747 nargs = '?'
654 else:
748 else:
655 nargs = None
749 nargs = None
656 if len(key) is 1:
750 if len(key) is 1:
657 paa('-'+key, '--'+key, type=unicode, dest=value, nargs=nargs)
751 paa('-'+key, '--'+key, type=unicode, dest=value, nargs=nargs)
658 else:
752 else:
659 paa('--'+key, type=unicode, dest=value, nargs=nargs)
753 paa('--'+key, type=unicode, dest=value, nargs=nargs)
660 for key, (value, help) in flags.iteritems():
754 for key, (value, help) in flags.iteritems():
661 if key in self.aliases:
755 if key in self.aliases:
662 #
756 #
663 self.alias_flags[self.aliases[key]] = value
757 self.alias_flags[self.aliases[key]] = value
664 continue
758 continue
665 if len(key) is 1:
759 if len(key) is 1:
666 paa('-'+key, '--'+key, action='append_const', dest='_flags', const=value)
760 paa('-'+key, '--'+key, action='append_const', dest='_flags', const=value)
667 else:
761 else:
668 paa('--'+key, action='append_const', dest='_flags', const=value)
762 paa('--'+key, action='append_const', dest='_flags', const=value)
669
763
670 def _convert_to_config(self):
764 def _convert_to_config(self):
671 """self.parsed_data->self.config, parse unrecognized extra args via KVLoader."""
765 """self.parsed_data->self.config, parse unrecognized extra args via KVLoader."""
672 # remove subconfigs list from namespace before transforming the Namespace
766 # remove subconfigs list from namespace before transforming the Namespace
673 if '_flags' in self.parsed_data:
767 if '_flags' in self.parsed_data:
674 subcs = self.parsed_data._flags
768 subcs = self.parsed_data._flags
675 del self.parsed_data._flags
769 del self.parsed_data._flags
676 else:
770 else:
677 subcs = []
771 subcs = []
678
772
679 for k, v in vars(self.parsed_data).iteritems():
773 for k, v in vars(self.parsed_data).iteritems():
680 if v is None:
774 if v is None:
681 # it was a flag that shares the name of an alias
775 # it was a flag that shares the name of an alias
682 subcs.append(self.alias_flags[k])
776 subcs.append(self.alias_flags[k])
683 else:
777 else:
684 # eval the KV assignment
778 # eval the KV assignment
685 self._exec_config_str(k, v)
779 self._exec_config_str(k, v)
686
780
687 for subc in subcs:
781 for subc in subcs:
688 self._load_flag(subc)
782 self._load_flag(subc)
689
783
690 if self.extra_args:
784 if self.extra_args:
691 sub_parser = KeyValueConfigLoader()
785 sub_parser = KeyValueConfigLoader()
692 sub_parser.load_config(self.extra_args)
786 sub_parser.load_config(self.extra_args)
693 self.config.merge(sub_parser.config)
787 self.config.merge(sub_parser.config)
694 self.extra_args = sub_parser.extra_args
788 self.extra_args = sub_parser.extra_args
695
789
696
790
697 def load_pyconfig_files(config_files, path):
791 def load_pyconfig_files(config_files, path):
698 """Load multiple Python config files, merging each of them in turn.
792 """Load multiple Python config files, merging each of them in turn.
699
793
700 Parameters
794 Parameters
701 ==========
795 ==========
702 config_files : list of str
796 config_files : list of str
703 List of config files names to load and merge into the config.
797 List of config files names to load and merge into the config.
704 path : unicode
798 path : unicode
705 The full path to the location of the config files.
799 The full path to the location of the config files.
706 """
800 """
707 config = Config()
801 config = Config()
708 for cf in config_files:
802 for cf in config_files:
709 loader = PyFileConfigLoader(cf, path=path)
803 loader = PyFileConfigLoader(cf, path=path)
710 try:
804 try:
711 next_config = loader.load_config()
805 next_config = loader.load_config()
712 except ConfigFileNotFound:
806 except ConfigFileNotFound:
713 pass
807 pass
714 except:
808 except:
715 raise
809 raise
716 else:
810 else:
717 config.merge(next_config)
811 config.merge(next_config)
718 return config
812 return config
General Comments 0
You need to be logged in to leave comments. Login now