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