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