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