##// END OF EJS Templates
Merge pull request #4276 from minrk/config-extend...
Thomas Kluyver -
r12934:61716e73 merge
parent child Browse files
Show More
@@ -0,0 +1,15 b''
1 Extending Configurable Containers
2 ---------------------------------
3
4 Some configurable traits are containers (list, dict, set)
5 Config objects now support calling ``extend``, ``update``, ``insert``, etc.
6 on traits in config files, which will ultimately result in calling
7 those methods on the original object.
8
9 The effect being that you can now add to containers without having to copy/paste
10 the initial value::
11
12 c = get_config()
13 c.InlineBackend.rc.update({ 'figure.figsize' : (6, 4) })
14
15
@@ -28,7 +28,7 b' Authors:'
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
@@ -149,6 +149,11 b' class Configurable(HasTraits):'
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.
@@ -25,6 +25,7 b' Authors'
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
@@ -32,6 +33,7 b' import sys'
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
@@ -74,6 +76,92 b' class ArgumentParser(argparse.ArgumentParser):'
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 _prepend = List()
94
95 def append(self, obj):
96 self._extend.append(obj)
97
98 def extend(self, other):
99 self._extend.extend(other)
100
101 def prepend(self, other):
102 """like list.extend, but for the front"""
103 self._prepend[:0] = other
104
105 _inserts = List()
106 def insert(self, index, other):
107 if not isinstance(index, int):
108 raise TypeError("An integer is required")
109 self._inserts.append((index, other))
110
111 # dict methods
112 # update is used for both dict and set
113 _update = Any()
114 def update(self, other):
115 if self._update is None:
116 if isinstance(other, dict):
117 self._update = {}
118 else:
119 self._update = set()
120 self._update.update(other)
121
122 # set methods
123 def add(self, obj):
124 self.update({obj})
125
126 def get_value(self, initial):
127 """construct the value from the initial one
128
129 after applying any insert / extend / update changes
130 """
131 if self._value is not None:
132 return self._value
133 value = copy.deepcopy(initial)
134 if isinstance(value, list):
135 for idx, obj in self._inserts:
136 value.insert(idx, obj)
137 value[:0] = self._prepend
138 value.extend(self._extend)
139
140 elif isinstance(value, dict):
141 if self._update:
142 value.update(self._update)
143 elif isinstance(value, set):
144 if self._update:
145 value.update(self._update)
146 self._value = value
147 return value
148
149 def to_dict(self):
150 """return JSONable dict form of my data
151
152 Currently update as dict or set, extend, prepend as lists, and inserts as list of tuples.
153 """
154 d = {}
155 if self._update:
156 d['update'] = self._update
157 if self._extend:
158 d['extend'] = self._extend
159 if self._prepend:
160 d['prepend'] = self._prepend
161 elif self._inserts:
162 d['inserts'] = self._inserts
163 return d
164
77 165
78 166 class Config(dict):
79 167 """An attribute based dict that can do smart merges."""
@@ -125,6 +213,14 b' class Config(dict):'
125 213 return False
126 214
127 215 def __contains__(self, key):
216 # allow nested contains of the form `"Section.key" in config`
217 if '.' in key:
218 first, remainder = key.split('.', 1)
219 if first not in self:
220 return False
221 return remainder in self[first]
222
223 # we always have Sections
128 224 if self._is_section_key(key):
129 225 return True
130 226 else:
@@ -170,7 +266,14 b' class Config(dict):'
170 266 dict.__setitem__(self, key, c)
171 267 return c
172 268 else:
269 try:
173 270 return dict.__getitem__(self, key)
271 except KeyError:
272 # undefined
273 v = LazyConfigValue()
274 dict.__setitem__(self, key, v)
275 return v
276
174 277
175 278 def __setitem__(self, key, value):
176 279 if self._is_section_key(key):
@@ -9,11 +9,5 b' lines = """'
9 9 from IPython.parallel import *
10 10 """
11 11
12 # You have to make sure that attributes that are containers already
13 # exist before using them. Simple assigning a new list will override
14 # all previous values.
15 if hasattr(app, 'exec_lines'):
16 12 app.exec_lines.append(lines)
17 else:
18 app.exec_lines = [lines]
19 13
@@ -10,12 +10,4 b' import cmath'
10 10 from math import *
11 11 """
12 12
13 # You have to make sure that attributes that are containers already
14 # exist before using them. Simple assigning a new list will override
15 # all previous values.
16
17 if hasattr(app, 'exec_lines'):
18 13 app.exec_lines.append(lines)
19 else:
20 app.exec_lines = [lines]
21
@@ -21,10 +21,4 b' lines = """'
21 21 %rehashx
22 22 """
23 23
24 # You have to make sure that attributes that are containers already
25 # exist before using them. Simple assigning a new list will override
26 # all previous values.
27 if hasattr(app, 'exec_lines'):
28 24 app.exec_lines.append(lines)
29 else:
30 app.exec_lines = [lines]
@@ -13,18 +13,8 b" k, m, n = symbols('k m n', integer=True)"
13 13 f, g, h = symbols('f g h', cls=Function)
14 14 """
15 15
16 # You have to make sure that attributes that are containers already
17 # exist before using them. Simple assigning a new list will override
18 # all previous values.
19
20 if hasattr(app, 'exec_lines'):
21 16 app.exec_lines.append(lines)
22 else:
23 app.exec_lines = [lines]
24 17
25 18 # Load the sympy_printing extension to enable nice printing of sympy expr's.
26 if hasattr(app, 'extensions'):
27 app.extensions.append('sympyprinting')
28 else:
29 app.extensions = ['sympyprinting']
19 app.extensions.append('sympy.interactive.ipythonprinting')
30 20
@@ -27,7 +27,7 b' from IPython.config.configurable import ('
27 27 )
28 28
29 29 from IPython.utils.traitlets import (
30 Integer, Float, Unicode
30 Integer, Float, Unicode, List, Dict, Set,
31 31 )
32 32
33 33 from IPython.config.loader import Config
@@ -281,3 +281,80 b' class TestParentConfigurable(TestCase):'
281 281 myc = MyConfigurable(parent=parent)
282 282 self.assertEqual(myc.b, parent.config.MyParent2.MyParent.MyConfigurable.b)
283 283
284 class Containers(Configurable):
285 lis = List(config=True)
286 def _lis_default(self):
287 return [-1]
288
289 s = Set(config=True)
290 def _s_default(self):
291 return {'a'}
292
293 d = Dict(config=True)
294 def _d_default(self):
295 return {'a' : 'b'}
296
297 class TestConfigContainers(TestCase):
298 def test_extend(self):
299 c = Config()
300 c.Containers.lis.extend(range(5))
301 obj = Containers(config=c)
302 self.assertEqual(obj.lis, range(-1,5))
303
304 def test_insert(self):
305 c = Config()
306 c.Containers.lis.insert(0, 'a')
307 c.Containers.lis.insert(1, 'b')
308 obj = Containers(config=c)
309 self.assertEqual(obj.lis, ['a', 'b', -1])
310
311 def test_prepend(self):
312 c = Config()
313 c.Containers.lis.prepend([1,2])
314 c.Containers.lis.prepend([2,3])
315 obj = Containers(config=c)
316 self.assertEqual(obj.lis, [2,3,1,2,-1])
317
318 def test_prepend_extend(self):
319 c = Config()
320 c.Containers.lis.prepend([1,2])
321 c.Containers.lis.extend([2,3])
322 obj = Containers(config=c)
323 self.assertEqual(obj.lis, [1,2,-1,2,3])
324
325 def test_append_extend(self):
326 c = Config()
327 c.Containers.lis.append([1,2])
328 c.Containers.lis.extend([2,3])
329 obj = Containers(config=c)
330 self.assertEqual(obj.lis, [-1,[1,2],2,3])
331
332 def test_extend_append(self):
333 c = Config()
334 c.Containers.lis.extend([2,3])
335 c.Containers.lis.append([1,2])
336 obj = Containers(config=c)
337 self.assertEqual(obj.lis, [-1,2,3,[1,2]])
338
339 def test_insert_extend(self):
340 c = Config()
341 c.Containers.lis.insert(0, 1)
342 c.Containers.lis.extend([2,3])
343 obj = Containers(config=c)
344 self.assertEqual(obj.lis, [1,-1,2,3])
345
346 def test_set_update(self):
347 c = Config()
348 c.Containers.s.update({0,1,2})
349 c.Containers.s.update({3})
350 obj = Containers(config=c)
351 self.assertEqual(obj.s, {'a', 0, 1, 2, 3})
352
353 def test_dict_update(self):
354 c = Config()
355 c.Containers.d.update({'c' : 'd'})
356 c.Containers.d.update({'e' : 'f'})
357 obj = Containers(config=c)
358 self.assertEqual(obj.d, {'a':'b', 'c':'d', 'e':'f'})
359
360
@@ -9,7 +9,7 b' Authors:'
9 9 """
10 10
11 11 #-----------------------------------------------------------------------------
12 # Copyright (C) 2008-2011 The IPython Development Team
12 # Copyright (C) 2008 The IPython Development Team
13 13 #
14 14 # Distributed under the terms of the BSD License. The full license is in
15 15 # the file COPYING, distributed as part of this software.
@@ -279,4 +279,14 b' class TestConfig(TestCase):'
279 279 self.assertEqual(c1.Foo.__class__, Config)
280 280 self.assertEqual(c1.Foo.bar, 1)
281 281 self.assertEqual(c1.Foo.baz, 2)
282 self.assertRaises(AttributeError, getattr, c2.Foo, 'baz')
282 self.assertNotIn('baz', c2.Foo)
283
284 def test_contains(self):
285 c1 = Config({'Foo' : {'baz' : 2}})
286 c2 = Config({'Foo' : {'bar' : 1}})
287 self.assertIn('Foo', c1)
288 self.assertIn('Foo.baz', c1)
289 self.assertIn('Foo.bar', c2)
290 self.assertNotIn('Foo.bar', c1)
291
292
@@ -282,10 +282,7 b' class BaseIPythonApplication(Application):'
282 282 if self.profile_dir is not None:
283 283 # already ran
284 284 return
285 try:
286 # location explicitly specified:
287 location = self.config.ProfileDir.location
288 except AttributeError:
285 if 'ProfileDir.location' not in self.config:
289 286 # location not specified, find by profile name
290 287 try:
291 288 p = ProfileDir.find_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
@@ -305,6 +302,7 b' class BaseIPythonApplication(Application):'
305 302 else:
306 303 self.log.info("Using existing profile dir: %r"%p.location)
307 304 else:
305 location = self.config.ProfileDir.location
308 306 # location is fully specified
309 307 try:
310 308 p = ProfileDir.find_profile_dir(location, self.config)
@@ -399,9 +399,9 b' class IPControllerApp(BaseParallelApplication):'
399 399 q.connect_mon(monitor_url)
400 400 q.daemon=True
401 401 children.append(q)
402 try:
402 if 'TaskScheduler.scheme_name' in self.config:
403 403 scheme = self.config.TaskScheduler.scheme_name
404 except AttributeError:
404 else:
405 405 scheme = TaskScheduler.scheme_name.get_default_value()
406 406 # Task Queue (in a Process)
407 407 if scheme == 'pure':
@@ -211,14 +211,9 b' class IPEngineApp(BaseParallelApplication):'
211 211
212 212 # allow hand-override of location for disambiguation
213 213 # and ssh-server
214 try:
215 config.EngineFactory.location
216 except AttributeError:
214 if 'EngineFactory.location' not in config:
217 215 config.EngineFactory.location = d['location']
218
219 try:
220 config.EngineFactory.sshserver
221 except AttributeError:
216 if 'EngineFactory.sshserver' not in config:
222 217 config.EngineFactory.sshserver = d.get('ssh')
223 218
224 219 location = config.EngineFactory.location
@@ -313,21 +308,17 b' class IPEngineApp(BaseParallelApplication):'
313 308 self.log.fatal("Fatal: url file never arrived: %s", self.url_file)
314 309 self.exit(1)
315 310
316
317 try:
318 exec_lines = config.IPKernelApp.exec_lines
319 except AttributeError:
320 try:
321 exec_lines = config.InteractiveShellApp.exec_lines
322 except AttributeError:
323 exec_lines = config.IPKernelApp.exec_lines = []
324 try:
325 exec_files = config.IPKernelApp.exec_files
326 except AttributeError:
327 try:
328 exec_files = config.InteractiveShellApp.exec_files
329 except AttributeError:
330 exec_files = config.IPKernelApp.exec_files = []
311 exec_lines = []
312 for app in ('IPKernelApp', 'InteractiveShellApp'):
313 if '%s.exec_lines' in config:
314 exec_lines = config.IPKernelApp.exec_lines = config[app].exec_lines
315 break
316
317 exec_files = []
318 for app in ('IPKernelApp', 'InteractiveShellApp'):
319 if '%s.exec_files' in config:
320 exec_files = config.IPKernelApp.exec_files = config[app].exec_files
321 break
331 322
332 323 if self.startup_script:
333 324 exec_files.append(self.startup_script)
@@ -255,10 +255,9 b' class HubFactory(RegistrationFactory):'
255 255
256 256 ctx = self.context
257 257 loop = self.loop
258
259 try:
258 if 'TaskScheduler.scheme_name' in self.config:
260 259 scheme = self.config.TaskScheduler.scheme_name
261 except AttributeError:
260 else:
262 261 from .scheduler import TaskScheduler
263 262 scheme = TaskScheduler.scheme_name.get_default_value()
264 263
@@ -281,18 +281,10 b' class IPythonQtConsoleApp(BaseIPythonApplication, IPythonConsoleApp):'
281 281 # are removed from the backend.
282 282
283 283 # parse the colors arg down to current known labels
284 try:
285 colors = self.config.ZMQInteractiveShell.colors
286 except AttributeError:
287 colors = None
288 try:
289 style = self.config.IPythonWidget.syntax_style
290 except AttributeError:
291 style = None
292 try:
293 sheet = self.config.IPythonWidget.style_sheet
294 except AttributeError:
295 sheet = None
284 cfg = self.config
285 colors = cfg.ZMQInteractiveShell.colors if 'ZMQInteractiveShell.colors' in cfg else None
286 style = cfg.IPythonWidget.syntax_style if 'IPythonWidget.syntax_style' in cfg else None
287 sheet = cfg.IPythonWidget.style_sheet if 'IPythonWidget.style_sheet' in cfg else None
296 288
297 289 # find the value for colors:
298 290 if colors:
General Comments 0
You need to be logged in to leave comments. Login now