##// END OF EJS Templates
add LazyConfigValue...
MinRK -
Show More
@@ -28,7 +28,7 b' Authors:'
28 import datetime
28 import datetime
29 from copy import deepcopy
29 from copy import deepcopy
30
30
31 from loader import Config
31 from .loader import Config, LazyConfigValue
32 from IPython.utils.traitlets import HasTraits, Instance
32 from IPython.utils.traitlets import HasTraits, Instance
33 from IPython.utils.text import indent, wrap_paragraphs
33 from IPython.utils.text import indent, wrap_paragraphs
34
34
@@ -137,7 +137,7 b' class Configurable(HasTraits):'
137 if c._has_section(sname):
137 if c._has_section(sname):
138 my_config.merge(c[sname])
138 my_config.merge(c[sname])
139 return my_config
139 return my_config
140
140
141 def _load_config(self, cfg, section_names=None, traits=None):
141 def _load_config(self, cfg, section_names=None, traits=None):
142 """load traits from a Config object"""
142 """load traits from a Config object"""
143
143
@@ -149,6 +149,11 b' class Configurable(HasTraits):'
149 my_config = self._find_my_config(cfg)
149 my_config = self._find_my_config(cfg)
150 for name, config_value in my_config.iteritems():
150 for name, config_value in my_config.iteritems():
151 if name in traits:
151 if name in traits:
152 if isinstance(config_value, LazyConfigValue):
153 # ConfigValue is a wrapper for using append / update on containers
154 # without having to copy the
155 initial = getattr(self, name)
156 config_value = config_value.get_value(initial)
152 # We have to do a deepcopy here if we don't deepcopy the entire
157 # We have to do a deepcopy here if we don't deepcopy the entire
153 # config object. If we don't, a mutable config_value will be
158 # config object. If we don't, a mutable config_value will be
154 # shared by all instances, effectively making it a class attribute.
159 # shared by all instances, effectively making it a class attribute.
@@ -25,6 +25,7 b' Authors'
25
25
26 import __builtin__ as builtin_mod
26 import __builtin__ as builtin_mod
27 import argparse
27 import argparse
28 import copy
28 import os
29 import os
29 import re
30 import re
30 import sys
31 import sys
@@ -32,6 +33,7 b' import sys'
32 from IPython.utils.path import filefind, get_ipython_dir
33 from IPython.utils.path import filefind, get_ipython_dir
33 from IPython.utils import py3compat, warn
34 from IPython.utils import py3compat, warn
34 from IPython.utils.encoding import DEFAULT_ENCODING
35 from IPython.utils.encoding import DEFAULT_ENCODING
36 from IPython.utils.traitlets import HasTraits, List, Any, TraitError
35
37
36 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
37 # Exceptions
39 # Exceptions
@@ -74,6 +76,83 b' class ArgumentParser(argparse.ArgumentParser):'
74 # Config class for holding config information
76 # Config class for holding config information
75 #-----------------------------------------------------------------------------
77 #-----------------------------------------------------------------------------
76
78
79 class LazyConfigValue(HasTraits):
80 """Proxy object for exposing methods on configurable containers
81
82 Exposes:
83
84 - append, extend, insert on lists
85 - update on dicts
86 - update, add on sets
87 """
88
89 _value = None
90
91 # list methods
92 _extend = List()
93
94 def append(self, obj):
95 self._extend.append(obj)
96
97 def extend(self, other):
98 self._extend.extend(other)
99
100 _inserts = List()
101 def insert(self, index, other):
102 if not isinstance(index, int):
103 raise TypeError("An integer is required")
104 self._inserts.append((index, other))
105
106 # dict methods
107 # update is used for both dict and set
108 _update = Any()
109 def update(self, other):
110 if self._update is None:
111 if isinstance(other, dict):
112 self._update = {}
113 else:
114 self._update = set()
115 self._update.update(other)
116
117 # set methods
118 def add(self, obj):
119 self.update({obj})
120
121 def get_value(self, initial):
122 """construct the value from the initial one
123
124 after applying any insert / extend / update changes
125 """
126 if self._value is not None:
127 return self._value
128 value = copy.deepcopy(initial)
129 if isinstance(value, list):
130 for idx, obj in self._inserts:
131 value.insert(idx, obj)
132 value.extend(self._extend)
133 elif isinstance(value, dict):
134 if self._update:
135 value.update(self._update)
136 elif isinstance(value, set):
137 if self._update:
138 value.update(self._update)
139 self._value = value
140 return value
141
142 def to_dict(self):
143 """return JSONable dict form of my data
144
145 Currently update as dict or set, extend as list, and inserts as list of tuples.
146 """
147 d = {}
148 if self._update:
149 d['update'] = self._update
150 if self._extend:
151 d['extend'] = self._extend
152 elif self._inserts:
153 d['inserts'] = self._inserts
154 return d
155
77
156
78 class Config(dict):
157 class Config(dict):
79 """An attribute based dict that can do smart merges."""
158 """An attribute based dict that can do smart merges."""
@@ -125,6 +204,14 b' class Config(dict):'
125 return False
204 return False
126
205
127 def __contains__(self, key):
206 def __contains__(self, key):
207 # allow nested contains of the form `Section.key in config`
208 if '.' in key:
209 first, remainder = key.split('.', 1)
210 if first not in self:
211 return False
212 return remainder in self[first]
213
214 # we always have Sections
128 if self._is_section_key(key):
215 if self._is_section_key(key):
129 return True
216 return True
130 else:
217 else:
@@ -147,7 +234,7 b' class Config(dict):'
147 def __deepcopy__(self, memo):
234 def __deepcopy__(self, memo):
148 import copy
235 import copy
149 return type(self)(copy.deepcopy(self.items()))
236 return type(self)(copy.deepcopy(self.items()))
150
237
151 def __getitem__(self, key):
238 def __getitem__(self, key):
152 # We cannot use directly self._is_section_key, because it triggers
239 # We cannot use directly self._is_section_key, because it triggers
153 # infinite recursion on top of PyPy. Instead, we manually fish the
240 # infinite recursion on top of PyPy. Instead, we manually fish the
@@ -170,7 +257,14 b' class Config(dict):'
170 dict.__setitem__(self, key, c)
257 dict.__setitem__(self, key, c)
171 return c
258 return c
172 else:
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 def __setitem__(self, key, value):
269 def __setitem__(self, key, value):
176 if self._is_section_key(key):
270 if self._is_section_key(key):
General Comments 0
You need to be logged in to leave comments. Login now