##// END OF EJS Templates
Move color trait type to the widget package
Sylvain Corlay -
Show More
@@ -0,0 +1,20 b''
1 """Test trait types of the widget packages."""
2
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
5
6 from unittest import TestCase
7 from IPython.utils.traitlets import HasTraits
8 from IPython.utils.tests.test_traitlets import TraitTestBase
9 from IPython.html.widgets import Color
10
11
12 class ColorTrait(HasTraits):
13 value = Color("black")
14
15
16 class TestColor(TraitTestBase):
17 obj = ColorTrait()
18
19 _good_values = ["blue", "#AA0", "#FFFFFF"]
20 _bad_values = ["vanilla", "blues"]
@@ -0,0 +1,29 b''
1 # encoding: utf-8
2 """
3 Trait types for html widgets.
4 """
5
6 # Copyright (c) IPython Development Team.
7 # Distributed under the terms of the Modified BSD License.
8
9 import re
10 from IPython.utils import traitlets
11
12 #-----------------------------------------------------------------------------
13 # Utilities
14 #-----------------------------------------------------------------------------
15
16 _color_names = ['aliceblue', 'antiquewhite', 'aqua', 'aquamarine', 'azure', 'beige', 'bisque', 'black', 'blanchedalmond', 'blue', 'blueviolet', 'brown', 'burlywood', 'cadetblue', 'chartreuse', 'chocolate', 'coral', 'cornflowerblue', 'cornsilk', 'crimson', 'cyan', 'darkblue', 'darkcyan', 'darkgoldenrod', 'darkgray', 'darkgreen', 'darkkhaki', 'darkmagenta', 'darkolivegreen', 'darkorange', 'darkorchid', 'darkred', 'darksalmon', 'darkseagreen', 'darkslateblue', 'darkslategray', 'darkturquoise', 'darkviolet', 'deeppink', 'deepskyblue', 'dimgray', 'dodgerblue', 'firebrick', 'floralwhite', 'forestgreen', 'fuchsia', 'gainsboro', 'ghostwhite', 'gold', 'goldenrod', 'gray', 'green', 'greenyellow', 'honeydew', 'hotpink', 'indianred ', 'indigo ', 'ivory', 'khaki', 'lavender', 'lavenderblush', 'lawngreen', 'lemonchiffon', 'lightblue', 'lightcoral', 'lightcyan', 'lightgoldenrodyellow', 'lightgray', 'lightgreen', 'lightpink', 'lightsalmon', 'lightseagreen', 'lightskyblue', 'lightslategray', 'lightsteelblue', 'lightyellow', 'lime', 'limegreen', 'linen', 'magenta', 'maroon', 'mediumaquamarine', 'mediumblue', 'mediumorchid', 'mediumpurple', 'mediumseagreen', 'mediumslateblue', 'mediumspringgreen', 'mediumturquoise', 'mediumvioletred', 'midnightblue', 'mintcream', 'mistyrose', 'moccasin', 'navajowhite', 'navy', 'oldlace', 'olive', 'olivedrab', 'orange', 'orangered', 'orchid', 'palegoldenrod', 'palegreen', 'paleturquoise', 'palevioletred', 'papayawhip', 'peachpuff', 'peru', 'pink', 'plum', 'powderblue', 'purple', 'rebeccapurple', 'red', 'rosybrown', 'royalblue', 'saddlebrown', 'salmon', 'sandybrown', 'seagreen', 'seashell', 'sienna', 'silver', 'skyblue', 'slateblue', 'slategray', 'snow', 'springgreen', 'steelblue', 'tan', 'teal', 'thistle', 'tomato', 'turquoise', 'violet', 'wheat', 'white', 'whitesmoke', 'yellow', 'yellowgreen']
17 _color_re = re.compile(r'#[a-fA-F0-9]{3}(?:[a-fA-F0-9]{3})?$')
18
19
20 class Color(traitlets._CoercedString):
21 """A string holding a valid HTML color such as 'blue', '#060482', '#A80'"""
22
23 info_text = 'a valid HTML color'
24
25 def validate(self, obj, value):
26 value = self._coerce_str(obj, value)
27 if value.lower() in _color_names or _color_re.match(value):
28 return value
29 self.error(obj, value) No newline at end of file
@@ -1,38 +1,40 b''
1 from .widget import Widget, DOMWidget, CallbackDispatcher, register
1 from .widget import Widget, DOMWidget, CallbackDispatcher, register
2
2
3 from .trait_types import Color
4
3 from .widget_bool import Checkbox, ToggleButton
5 from .widget_bool import Checkbox, ToggleButton
4 from .widget_button import Button
6 from .widget_button import Button
5 from .widget_box import Box, FlexBox, HBox, VBox
7 from .widget_box import Box, FlexBox, HBox, VBox
6 from .widget_float import FloatText, BoundedFloatText, FloatSlider, FloatProgress, FloatRangeSlider
8 from .widget_float import FloatText, BoundedFloatText, FloatSlider, FloatProgress, FloatRangeSlider
7 from .widget_image import Image
9 from .widget_image import Image
8 from .widget_int import IntText, BoundedIntText, IntSlider, IntProgress, IntRangeSlider
10 from .widget_int import IntText, BoundedIntText, IntSlider, IntProgress, IntRangeSlider
9 from .widget_output import Output
11 from .widget_output import Output
10 from .widget_selection import RadioButtons, ToggleButtons, Dropdown, Select, SelectMultiple
12 from .widget_selection import RadioButtons, ToggleButtons, Dropdown, Select, SelectMultiple
11 from .widget_selectioncontainer import Tab, Accordion
13 from .widget_selectioncontainer import Tab, Accordion
12 from .widget_string import HTML, Latex, Text, Textarea
14 from .widget_string import HTML, Latex, Text, Textarea
13 from .interaction import interact, interactive, fixed, interact_manual
15 from .interaction import interact, interactive, fixed, interact_manual
14 from .widget_link import jslink, jsdlink
16 from .widget_link import jslink, jsdlink
15
17
16 # Deprecated classes
18 # Deprecated classes
17 from .widget_bool import CheckboxWidget, ToggleButtonWidget
19 from .widget_bool import CheckboxWidget, ToggleButtonWidget
18 from .widget_button import ButtonWidget
20 from .widget_button import ButtonWidget
19 from .widget_box import ContainerWidget
21 from .widget_box import ContainerWidget
20 from .widget_float import FloatTextWidget, BoundedFloatTextWidget, FloatSliderWidget, FloatProgressWidget
22 from .widget_float import FloatTextWidget, BoundedFloatTextWidget, FloatSliderWidget, FloatProgressWidget
21 from .widget_image import ImageWidget
23 from .widget_image import ImageWidget
22 from .widget_int import IntTextWidget, BoundedIntTextWidget, IntSliderWidget, IntProgressWidget
24 from .widget_int import IntTextWidget, BoundedIntTextWidget, IntSliderWidget, IntProgressWidget
23 from .widget_selection import RadioButtonsWidget, ToggleButtonsWidget, DropdownWidget, SelectWidget
25 from .widget_selection import RadioButtonsWidget, ToggleButtonsWidget, DropdownWidget, SelectWidget
24 from .widget_selectioncontainer import TabWidget, AccordionWidget
26 from .widget_selectioncontainer import TabWidget, AccordionWidget
25 from .widget_string import HTMLWidget, LatexWidget, TextWidget, TextareaWidget
27 from .widget_string import HTMLWidget, LatexWidget, TextWidget, TextareaWidget
26
28
27 # We use warn_explicit so we have very brief messages without file or line numbers.
29 # We use warn_explicit so we have very brief messages without file or line numbers.
28 # The concern is that file or line numbers will confuse the interactive user.
30 # The concern is that file or line numbers will confuse the interactive user.
29 # To ignore this warning, do:
31 # To ignore this warning, do:
30 #
32 #
31 # from warnings import filterwarnings
33 # from warnings import filterwarnings
32 # filterwarnings('ignore', module='IPython.html.widgets')
34 # filterwarnings('ignore', module='IPython.html.widgets')
33
35
34 from warnings import warn_explicit
36 from warnings import warn_explicit
35 __warningregistry__ = {}
37 __warningregistry__ = {}
36 warn_explicit("IPython widgets are experimental and may change in the future.",
38 warn_explicit("IPython widgets are experimental and may change in the future.",
37 FutureWarning, '', 0, module = 'IPython.html.widgets',
39 FutureWarning, '', 0, module = 'IPython.html.widgets',
38 registry = __warningregistry__, module_globals = globals)
40 registry = __warningregistry__, module_globals = globals)
@@ -1,489 +1,490 b''
1 """Base Widget class. Allows user to create widgets in the back-end that render
1 """Base Widget class. Allows user to create widgets in the back-end that render
2 in the IPython notebook front-end.
2 in the IPython notebook front-end.
3 """
3 """
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 # Copyright (c) 2013, the IPython Development Team.
5 # Copyright (c) 2013, the IPython Development Team.
6 #
6 #
7 # Distributed under the terms of the Modified BSD License.
7 # Distributed under the terms of the Modified BSD License.
8 #
8 #
9 # The full license is in the file COPYING.txt, distributed with this software.
9 # The full license is in the file COPYING.txt, distributed with this software.
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11
11
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 # Imports
13 # Imports
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 from contextlib import contextmanager
15 from contextlib import contextmanager
16 import collections
16 import collections
17
17
18 from IPython.core.getipython import get_ipython
18 from IPython.core.getipython import get_ipython
19 from IPython.kernel.comm import Comm
19 from IPython.kernel.comm import Comm
20 from IPython.config import LoggingConfigurable
20 from IPython.config import LoggingConfigurable
21 from IPython.utils.importstring import import_item
21 from IPython.utils.importstring import import_item
22 from IPython.utils.traitlets import Unicode, Dict, Instance, Bool, List, \
22 from IPython.utils.traitlets import Unicode, Dict, Instance, Bool, List, \
23 CaselessStrEnum, Tuple, CUnicode, Int, Set, Color
23 CaselessStrEnum, Tuple, CUnicode, Int, Set
24 from IPython.utils.py3compat import string_types
24 from IPython.utils.py3compat import string_types
25 from .trait_types import Color
25
26
26 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
27 # Classes
28 # Classes
28 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
29 class CallbackDispatcher(LoggingConfigurable):
30 class CallbackDispatcher(LoggingConfigurable):
30 """A structure for registering and running callbacks"""
31 """A structure for registering and running callbacks"""
31 callbacks = List()
32 callbacks = List()
32
33
33 def __call__(self, *args, **kwargs):
34 def __call__(self, *args, **kwargs):
34 """Call all of the registered callbacks."""
35 """Call all of the registered callbacks."""
35 value = None
36 value = None
36 for callback in self.callbacks:
37 for callback in self.callbacks:
37 try:
38 try:
38 local_value = callback(*args, **kwargs)
39 local_value = callback(*args, **kwargs)
39 except Exception as e:
40 except Exception as e:
40 ip = get_ipython()
41 ip = get_ipython()
41 if ip is None:
42 if ip is None:
42 self.log.warn("Exception in callback %s: %s", callback, e, exc_info=True)
43 self.log.warn("Exception in callback %s: %s", callback, e, exc_info=True)
43 else:
44 else:
44 ip.showtraceback()
45 ip.showtraceback()
45 else:
46 else:
46 value = local_value if local_value is not None else value
47 value = local_value if local_value is not None else value
47 return value
48 return value
48
49
49 def register_callback(self, callback, remove=False):
50 def register_callback(self, callback, remove=False):
50 """(Un)Register a callback
51 """(Un)Register a callback
51
52
52 Parameters
53 Parameters
53 ----------
54 ----------
54 callback: method handle
55 callback: method handle
55 Method to be registered or unregistered.
56 Method to be registered or unregistered.
56 remove=False: bool
57 remove=False: bool
57 Whether to unregister the callback."""
58 Whether to unregister the callback."""
58
59
59 # (Un)Register the callback.
60 # (Un)Register the callback.
60 if remove and callback in self.callbacks:
61 if remove and callback in self.callbacks:
61 self.callbacks.remove(callback)
62 self.callbacks.remove(callback)
62 elif not remove and callback not in self.callbacks:
63 elif not remove and callback not in self.callbacks:
63 self.callbacks.append(callback)
64 self.callbacks.append(callback)
64
65
65 def _show_traceback(method):
66 def _show_traceback(method):
66 """decorator for showing tracebacks in IPython"""
67 """decorator for showing tracebacks in IPython"""
67 def m(self, *args, **kwargs):
68 def m(self, *args, **kwargs):
68 try:
69 try:
69 return(method(self, *args, **kwargs))
70 return(method(self, *args, **kwargs))
70 except Exception as e:
71 except Exception as e:
71 ip = get_ipython()
72 ip = get_ipython()
72 if ip is None:
73 if ip is None:
73 self.log.warn("Exception in widget method %s: %s", method, e, exc_info=True)
74 self.log.warn("Exception in widget method %s: %s", method, e, exc_info=True)
74 else:
75 else:
75 ip.showtraceback()
76 ip.showtraceback()
76 return m
77 return m
77
78
78
79
79 def register(key=None):
80 def register(key=None):
80 """Returns a decorator registering a widget class in the widget registry.
81 """Returns a decorator registering a widget class in the widget registry.
81 If no key is provided, the class name is used as a key. A key is
82 If no key is provided, the class name is used as a key. A key is
82 provided for each core IPython widget so that the frontend can use
83 provided for each core IPython widget so that the frontend can use
83 this key regardless of the language of the kernel"""
84 this key regardless of the language of the kernel"""
84 def wrap(widget):
85 def wrap(widget):
85 l = key if key is not None else widget.__module__ + widget.__name__
86 l = key if key is not None else widget.__module__ + widget.__name__
86 Widget.widget_types[l] = widget
87 Widget.widget_types[l] = widget
87 return widget
88 return widget
88 return wrap
89 return wrap
89
90
90
91
91 class Widget(LoggingConfigurable):
92 class Widget(LoggingConfigurable):
92 #-------------------------------------------------------------------------
93 #-------------------------------------------------------------------------
93 # Class attributes
94 # Class attributes
94 #-------------------------------------------------------------------------
95 #-------------------------------------------------------------------------
95 _widget_construction_callback = None
96 _widget_construction_callback = None
96 widgets = {}
97 widgets = {}
97 widget_types = {}
98 widget_types = {}
98
99
99 @staticmethod
100 @staticmethod
100 def on_widget_constructed(callback):
101 def on_widget_constructed(callback):
101 """Registers a callback to be called when a widget is constructed.
102 """Registers a callback to be called when a widget is constructed.
102
103
103 The callback must have the following signature:
104 The callback must have the following signature:
104 callback(widget)"""
105 callback(widget)"""
105 Widget._widget_construction_callback = callback
106 Widget._widget_construction_callback = callback
106
107
107 @staticmethod
108 @staticmethod
108 def _call_widget_constructed(widget):
109 def _call_widget_constructed(widget):
109 """Static method, called when a widget is constructed."""
110 """Static method, called when a widget is constructed."""
110 if Widget._widget_construction_callback is not None and callable(Widget._widget_construction_callback):
111 if Widget._widget_construction_callback is not None and callable(Widget._widget_construction_callback):
111 Widget._widget_construction_callback(widget)
112 Widget._widget_construction_callback(widget)
112
113
113 @staticmethod
114 @staticmethod
114 def handle_comm_opened(comm, msg):
115 def handle_comm_opened(comm, msg):
115 """Static method, called when a widget is constructed."""
116 """Static method, called when a widget is constructed."""
116 widget_class = import_item(msg['content']['data']['widget_class'])
117 widget_class = import_item(msg['content']['data']['widget_class'])
117 widget = widget_class(comm=comm)
118 widget = widget_class(comm=comm)
118
119
119
120
120 #-------------------------------------------------------------------------
121 #-------------------------------------------------------------------------
121 # Traits
122 # Traits
122 #-------------------------------------------------------------------------
123 #-------------------------------------------------------------------------
123 _model_module = Unicode(None, allow_none=True, help="""A requirejs module name
124 _model_module = Unicode(None, allow_none=True, help="""A requirejs module name
124 in which to find _model_name. If empty, look in the global registry.""")
125 in which to find _model_name. If empty, look in the global registry.""")
125 _model_name = Unicode('WidgetModel', help="""Name of the backbone model
126 _model_name = Unicode('WidgetModel', help="""Name of the backbone model
126 registered in the front-end to create and sync this widget with.""")
127 registered in the front-end to create and sync this widget with.""")
127 _view_module = Unicode(help="""A requirejs module in which to find _view_name.
128 _view_module = Unicode(help="""A requirejs module in which to find _view_name.
128 If empty, look in the global registry.""", sync=True)
129 If empty, look in the global registry.""", sync=True)
129 _view_name = Unicode(None, allow_none=True, help="""Default view registered in the front-end
130 _view_name = Unicode(None, allow_none=True, help="""Default view registered in the front-end
130 to use to represent the widget.""", sync=True)
131 to use to represent the widget.""", sync=True)
131 comm = Instance('IPython.kernel.comm.Comm')
132 comm = Instance('IPython.kernel.comm.Comm')
132
133
133 msg_throttle = Int(3, sync=True, help="""Maximum number of msgs the
134 msg_throttle = Int(3, sync=True, help="""Maximum number of msgs the
134 front-end can send before receiving an idle msg from the back-end.""")
135 front-end can send before receiving an idle msg from the back-end.""")
135
136
136 version = Int(0, sync=True, help="""Widget's version""")
137 version = Int(0, sync=True, help="""Widget's version""")
137 keys = List()
138 keys = List()
138 def _keys_default(self):
139 def _keys_default(self):
139 return [name for name in self.traits(sync=True)]
140 return [name for name in self.traits(sync=True)]
140
141
141 _property_lock = Tuple((None, None))
142 _property_lock = Tuple((None, None))
142 _send_state_lock = Int(0)
143 _send_state_lock = Int(0)
143 _states_to_send = Set()
144 _states_to_send = Set()
144 _display_callbacks = Instance(CallbackDispatcher, ())
145 _display_callbacks = Instance(CallbackDispatcher, ())
145 _msg_callbacks = Instance(CallbackDispatcher, ())
146 _msg_callbacks = Instance(CallbackDispatcher, ())
146
147
147 #-------------------------------------------------------------------------
148 #-------------------------------------------------------------------------
148 # (Con/de)structor
149 # (Con/de)structor
149 #-------------------------------------------------------------------------
150 #-------------------------------------------------------------------------
150 def __init__(self, **kwargs):
151 def __init__(self, **kwargs):
151 """Public constructor"""
152 """Public constructor"""
152 self._model_id = kwargs.pop('model_id', None)
153 self._model_id = kwargs.pop('model_id', None)
153 super(Widget, self).__init__(**kwargs)
154 super(Widget, self).__init__(**kwargs)
154
155
155 Widget._call_widget_constructed(self)
156 Widget._call_widget_constructed(self)
156 self.open()
157 self.open()
157
158
158 def __del__(self):
159 def __del__(self):
159 """Object disposal"""
160 """Object disposal"""
160 self.close()
161 self.close()
161
162
162 #-------------------------------------------------------------------------
163 #-------------------------------------------------------------------------
163 # Properties
164 # Properties
164 #-------------------------------------------------------------------------
165 #-------------------------------------------------------------------------
165
166
166 def open(self):
167 def open(self):
167 """Open a comm to the frontend if one isn't already open."""
168 """Open a comm to the frontend if one isn't already open."""
168 if self.comm is None:
169 if self.comm is None:
169 args = dict(target_name='ipython.widget',
170 args = dict(target_name='ipython.widget',
170 data={'model_name': self._model_name,
171 data={'model_name': self._model_name,
171 'model_module': self._model_module})
172 'model_module': self._model_module})
172 if self._model_id is not None:
173 if self._model_id is not None:
173 args['comm_id'] = self._model_id
174 args['comm_id'] = self._model_id
174 self.comm = Comm(**args)
175 self.comm = Comm(**args)
175
176
176 def _comm_changed(self, name, new):
177 def _comm_changed(self, name, new):
177 """Called when the comm is changed."""
178 """Called when the comm is changed."""
178 if new is None:
179 if new is None:
179 return
180 return
180 self._model_id = self.model_id
181 self._model_id = self.model_id
181
182
182 self.comm.on_msg(self._handle_msg)
183 self.comm.on_msg(self._handle_msg)
183 Widget.widgets[self.model_id] = self
184 Widget.widgets[self.model_id] = self
184
185
185 # first update
186 # first update
186 self.send_state()
187 self.send_state()
187
188
188 @property
189 @property
189 def model_id(self):
190 def model_id(self):
190 """Gets the model id of this widget.
191 """Gets the model id of this widget.
191
192
192 If a Comm doesn't exist yet, a Comm will be created automagically."""
193 If a Comm doesn't exist yet, a Comm will be created automagically."""
193 return self.comm.comm_id
194 return self.comm.comm_id
194
195
195 #-------------------------------------------------------------------------
196 #-------------------------------------------------------------------------
196 # Methods
197 # Methods
197 #-------------------------------------------------------------------------
198 #-------------------------------------------------------------------------
198
199
199 def close(self):
200 def close(self):
200 """Close method.
201 """Close method.
201
202
202 Closes the underlying comm.
203 Closes the underlying comm.
203 When the comm is closed, all of the widget views are automatically
204 When the comm is closed, all of the widget views are automatically
204 removed from the front-end."""
205 removed from the front-end."""
205 if self.comm is not None:
206 if self.comm is not None:
206 Widget.widgets.pop(self.model_id, None)
207 Widget.widgets.pop(self.model_id, None)
207 self.comm.close()
208 self.comm.close()
208 self.comm = None
209 self.comm = None
209
210
210 def send_state(self, key=None):
211 def send_state(self, key=None):
211 """Sends the widget state, or a piece of it, to the front-end.
212 """Sends the widget state, or a piece of it, to the front-end.
212
213
213 Parameters
214 Parameters
214 ----------
215 ----------
215 key : unicode, or iterable (optional)
216 key : unicode, or iterable (optional)
216 A single property's name or iterable of property names to sync with the front-end.
217 A single property's name or iterable of property names to sync with the front-end.
217 """
218 """
218 self._send({
219 self._send({
219 "method" : "update",
220 "method" : "update",
220 "state" : self.get_state(key=key)
221 "state" : self.get_state(key=key)
221 })
222 })
222
223
223 def get_state(self, key=None):
224 def get_state(self, key=None):
224 """Gets the widget state, or a piece of it.
225 """Gets the widget state, or a piece of it.
225
226
226 Parameters
227 Parameters
227 ----------
228 ----------
228 key : unicode or iterable (optional)
229 key : unicode or iterable (optional)
229 A single property's name or iterable of property names to get.
230 A single property's name or iterable of property names to get.
230 """
231 """
231 if key is None:
232 if key is None:
232 keys = self.keys
233 keys = self.keys
233 elif isinstance(key, string_types):
234 elif isinstance(key, string_types):
234 keys = [key]
235 keys = [key]
235 elif isinstance(key, collections.Iterable):
236 elif isinstance(key, collections.Iterable):
236 keys = key
237 keys = key
237 else:
238 else:
238 raise ValueError("key must be a string, an iterable of keys, or None")
239 raise ValueError("key must be a string, an iterable of keys, or None")
239 state = {}
240 state = {}
240 for k in keys:
241 for k in keys:
241 f = self.trait_metadata(k, 'to_json', self._trait_to_json)
242 f = self.trait_metadata(k, 'to_json', self._trait_to_json)
242 value = getattr(self, k)
243 value = getattr(self, k)
243 state[k] = f(value)
244 state[k] = f(value)
244 return state
245 return state
245
246
246 def set_state(self, sync_data):
247 def set_state(self, sync_data):
247 """Called when a state is received from the front-end."""
248 """Called when a state is received from the front-end."""
248 for name in self.keys:
249 for name in self.keys:
249 if name in sync_data:
250 if name in sync_data:
250 json_value = sync_data[name]
251 json_value = sync_data[name]
251 from_json = self.trait_metadata(name, 'from_json', self._trait_from_json)
252 from_json = self.trait_metadata(name, 'from_json', self._trait_from_json)
252 with self._lock_property(name, json_value):
253 with self._lock_property(name, json_value):
253 setattr(self, name, from_json(json_value))
254 setattr(self, name, from_json(json_value))
254
255
255 def send(self, content):
256 def send(self, content):
256 """Sends a custom msg to the widget model in the front-end.
257 """Sends a custom msg to the widget model in the front-end.
257
258
258 Parameters
259 Parameters
259 ----------
260 ----------
260 content : dict
261 content : dict
261 Content of the message to send.
262 Content of the message to send.
262 """
263 """
263 self._send({"method": "custom", "content": content})
264 self._send({"method": "custom", "content": content})
264
265
265 def on_msg(self, callback, remove=False):
266 def on_msg(self, callback, remove=False):
266 """(Un)Register a custom msg receive callback.
267 """(Un)Register a custom msg receive callback.
267
268
268 Parameters
269 Parameters
269 ----------
270 ----------
270 callback: callable
271 callback: callable
271 callback will be passed two arguments when a message arrives::
272 callback will be passed two arguments when a message arrives::
272
273
273 callback(widget, content)
274 callback(widget, content)
274
275
275 remove: bool
276 remove: bool
276 True if the callback should be unregistered."""
277 True if the callback should be unregistered."""
277 self._msg_callbacks.register_callback(callback, remove=remove)
278 self._msg_callbacks.register_callback(callback, remove=remove)
278
279
279 def on_displayed(self, callback, remove=False):
280 def on_displayed(self, callback, remove=False):
280 """(Un)Register a widget displayed callback.
281 """(Un)Register a widget displayed callback.
281
282
282 Parameters
283 Parameters
283 ----------
284 ----------
284 callback: method handler
285 callback: method handler
285 Must have a signature of::
286 Must have a signature of::
286
287
287 callback(widget, **kwargs)
288 callback(widget, **kwargs)
288
289
289 kwargs from display are passed through without modification.
290 kwargs from display are passed through without modification.
290 remove: bool
291 remove: bool
291 True if the callback should be unregistered."""
292 True if the callback should be unregistered."""
292 self._display_callbacks.register_callback(callback, remove=remove)
293 self._display_callbacks.register_callback(callback, remove=remove)
293
294
294 #-------------------------------------------------------------------------
295 #-------------------------------------------------------------------------
295 # Support methods
296 # Support methods
296 #-------------------------------------------------------------------------
297 #-------------------------------------------------------------------------
297 @contextmanager
298 @contextmanager
298 def _lock_property(self, key, value):
299 def _lock_property(self, key, value):
299 """Lock a property-value pair.
300 """Lock a property-value pair.
300
301
301 The value should be the JSON state of the property.
302 The value should be the JSON state of the property.
302
303
303 NOTE: This, in addition to the single lock for all state changes, is
304 NOTE: This, in addition to the single lock for all state changes, is
304 flawed. In the future we may want to look into buffering state changes
305 flawed. In the future we may want to look into buffering state changes
305 back to the front-end."""
306 back to the front-end."""
306 self._property_lock = (key, value)
307 self._property_lock = (key, value)
307 try:
308 try:
308 yield
309 yield
309 finally:
310 finally:
310 self._property_lock = (None, None)
311 self._property_lock = (None, None)
311
312
312 @contextmanager
313 @contextmanager
313 def hold_sync(self):
314 def hold_sync(self):
314 """Hold syncing any state until the context manager is released"""
315 """Hold syncing any state until the context manager is released"""
315 # We increment a value so that this can be nested. Syncing will happen when
316 # We increment a value so that this can be nested. Syncing will happen when
316 # all levels have been released.
317 # all levels have been released.
317 self._send_state_lock += 1
318 self._send_state_lock += 1
318 try:
319 try:
319 yield
320 yield
320 finally:
321 finally:
321 self._send_state_lock -=1
322 self._send_state_lock -=1
322 if self._send_state_lock == 0:
323 if self._send_state_lock == 0:
323 self.send_state(self._states_to_send)
324 self.send_state(self._states_to_send)
324 self._states_to_send.clear()
325 self._states_to_send.clear()
325
326
326 def _should_send_property(self, key, value):
327 def _should_send_property(self, key, value):
327 """Check the property lock (property_lock)"""
328 """Check the property lock (property_lock)"""
328 to_json = self.trait_metadata(key, 'to_json', self._trait_to_json)
329 to_json = self.trait_metadata(key, 'to_json', self._trait_to_json)
329 if (key == self._property_lock[0]
330 if (key == self._property_lock[0]
330 and to_json(value) == self._property_lock[1]):
331 and to_json(value) == self._property_lock[1]):
331 return False
332 return False
332 elif self._send_state_lock > 0:
333 elif self._send_state_lock > 0:
333 self._states_to_send.add(key)
334 self._states_to_send.add(key)
334 return False
335 return False
335 else:
336 else:
336 return True
337 return True
337
338
338 # Event handlers
339 # Event handlers
339 @_show_traceback
340 @_show_traceback
340 def _handle_msg(self, msg):
341 def _handle_msg(self, msg):
341 """Called when a msg is received from the front-end"""
342 """Called when a msg is received from the front-end"""
342 data = msg['content']['data']
343 data = msg['content']['data']
343 method = data['method']
344 method = data['method']
344
345
345 # Handle backbone sync methods CREATE, PATCH, and UPDATE all in one.
346 # Handle backbone sync methods CREATE, PATCH, and UPDATE all in one.
346 if method == 'backbone':
347 if method == 'backbone':
347 if 'sync_data' in data:
348 if 'sync_data' in data:
348 sync_data = data['sync_data']
349 sync_data = data['sync_data']
349 self.set_state(sync_data) # handles all methods
350 self.set_state(sync_data) # handles all methods
350
351
351 # Handle a state request.
352 # Handle a state request.
352 elif method == 'request_state':
353 elif method == 'request_state':
353 self.send_state()
354 self.send_state()
354
355
355 # Handle a custom msg from the front-end.
356 # Handle a custom msg from the front-end.
356 elif method == 'custom':
357 elif method == 'custom':
357 if 'content' in data:
358 if 'content' in data:
358 self._handle_custom_msg(data['content'])
359 self._handle_custom_msg(data['content'])
359
360
360 # Catch remainder.
361 # Catch remainder.
361 else:
362 else:
362 self.log.error('Unknown front-end to back-end widget msg with method "%s"' % method)
363 self.log.error('Unknown front-end to back-end widget msg with method "%s"' % method)
363
364
364 def _handle_custom_msg(self, content):
365 def _handle_custom_msg(self, content):
365 """Called when a custom msg is received."""
366 """Called when a custom msg is received."""
366 self._msg_callbacks(self, content)
367 self._msg_callbacks(self, content)
367
368
368 def _notify_trait(self, name, old_value, new_value):
369 def _notify_trait(self, name, old_value, new_value):
369 """Called when a property has been changed."""
370 """Called when a property has been changed."""
370 # Trigger default traitlet callback machinery. This allows any user
371 # Trigger default traitlet callback machinery. This allows any user
371 # registered validation to be processed prior to allowing the widget
372 # registered validation to be processed prior to allowing the widget
372 # machinery to handle the state.
373 # machinery to handle the state.
373 LoggingConfigurable._notify_trait(self, name, old_value, new_value)
374 LoggingConfigurable._notify_trait(self, name, old_value, new_value)
374
375
375 # Send the state after the user registered callbacks for trait changes
376 # Send the state after the user registered callbacks for trait changes
376 # have all fired (allows for user to validate values).
377 # have all fired (allows for user to validate values).
377 if self.comm is not None and name in self.keys:
378 if self.comm is not None and name in self.keys:
378 # Make sure this isn't information that the front-end just sent us.
379 # Make sure this isn't information that the front-end just sent us.
379 if self._should_send_property(name, new_value):
380 if self._should_send_property(name, new_value):
380 # Send new state to front-end
381 # Send new state to front-end
381 self.send_state(key=name)
382 self.send_state(key=name)
382
383
383 def _handle_displayed(self, **kwargs):
384 def _handle_displayed(self, **kwargs):
384 """Called when a view has been displayed for this widget instance"""
385 """Called when a view has been displayed for this widget instance"""
385 self._display_callbacks(self, **kwargs)
386 self._display_callbacks(self, **kwargs)
386
387
387 def _trait_to_json(self, x):
388 def _trait_to_json(self, x):
388 """Convert a trait value to json
389 """Convert a trait value to json
389
390
390 Traverse lists/tuples and dicts and serialize their values as well.
391 Traverse lists/tuples and dicts and serialize their values as well.
391 Replace any widgets with their model_id
392 Replace any widgets with their model_id
392 """
393 """
393 if isinstance(x, dict):
394 if isinstance(x, dict):
394 return {k: self._trait_to_json(v) for k, v in x.items()}
395 return {k: self._trait_to_json(v) for k, v in x.items()}
395 elif isinstance(x, (list, tuple)):
396 elif isinstance(x, (list, tuple)):
396 return [self._trait_to_json(v) for v in x]
397 return [self._trait_to_json(v) for v in x]
397 elif isinstance(x, Widget):
398 elif isinstance(x, Widget):
398 return "IPY_MODEL_" + x.model_id
399 return "IPY_MODEL_" + x.model_id
399 else:
400 else:
400 return x # Value must be JSON-able
401 return x # Value must be JSON-able
401
402
402 def _trait_from_json(self, x):
403 def _trait_from_json(self, x):
403 """Convert json values to objects
404 """Convert json values to objects
404
405
405 Replace any strings representing valid model id values to Widget references.
406 Replace any strings representing valid model id values to Widget references.
406 """
407 """
407 if isinstance(x, dict):
408 if isinstance(x, dict):
408 return {k: self._trait_from_json(v) for k, v in x.items()}
409 return {k: self._trait_from_json(v) for k, v in x.items()}
409 elif isinstance(x, (list, tuple)):
410 elif isinstance(x, (list, tuple)):
410 return [self._trait_from_json(v) for v in x]
411 return [self._trait_from_json(v) for v in x]
411 elif isinstance(x, string_types) and x.startswith('IPY_MODEL_') and x[10:] in Widget.widgets:
412 elif isinstance(x, string_types) and x.startswith('IPY_MODEL_') and x[10:] in Widget.widgets:
412 # we want to support having child widgets at any level in a hierarchy
413 # we want to support having child widgets at any level in a hierarchy
413 # trusting that a widget UUID will not appear out in the wild
414 # trusting that a widget UUID will not appear out in the wild
414 return Widget.widgets[x[10:]]
415 return Widget.widgets[x[10:]]
415 else:
416 else:
416 return x
417 return x
417
418
418 def _ipython_display_(self, **kwargs):
419 def _ipython_display_(self, **kwargs):
419 """Called when `IPython.display.display` is called on the widget."""
420 """Called when `IPython.display.display` is called on the widget."""
420 # Show view.
421 # Show view.
421 if self._view_name is not None:
422 if self._view_name is not None:
422 self._send({"method": "display"})
423 self._send({"method": "display"})
423 self._handle_displayed(**kwargs)
424 self._handle_displayed(**kwargs)
424
425
425 def _send(self, msg):
426 def _send(self, msg):
426 """Sends a message to the model in the front-end."""
427 """Sends a message to the model in the front-end."""
427 self.comm.send(msg)
428 self.comm.send(msg)
428
429
429
430
430 class DOMWidget(Widget):
431 class DOMWidget(Widget):
431 visible = Bool(True, allow_none=True, help="Whether the widget is visible. False collapses the empty space, while None preserves the empty space.", sync=True)
432 visible = Bool(True, allow_none=True, help="Whether the widget is visible. False collapses the empty space, while None preserves the empty space.", sync=True)
432 _css = Tuple(sync=True, help="CSS property list: (selector, key, value)")
433 _css = Tuple(sync=True, help="CSS property list: (selector, key, value)")
433 _dom_classes = Tuple(sync=True, help="DOM classes applied to widget.$el.")
434 _dom_classes = Tuple(sync=True, help="DOM classes applied to widget.$el.")
434
435
435 width = CUnicode(sync=True)
436 width = CUnicode(sync=True)
436 height = CUnicode(sync=True)
437 height = CUnicode(sync=True)
437 # A default padding of 2.5 px makes the widgets look nice when displayed inline.
438 # A default padding of 2.5 px makes the widgets look nice when displayed inline.
438 padding = CUnicode(sync=True)
439 padding = CUnicode(sync=True)
439 margin = CUnicode(sync=True)
440 margin = CUnicode(sync=True)
440
441
441 color = Color(None, allow_none=True, sync=True)
442 color = Color(None, allow_none=True, sync=True)
442 background_color = Color(None, allow_none=True, sync=True)
443 background_color = Color(None, allow_none=True, sync=True)
443 border_color = Color(None, allow_none=True, sync=True)
444 border_color = Color(None, allow_none=True, sync=True)
444
445
445 border_width = CUnicode(sync=True)
446 border_width = CUnicode(sync=True)
446 border_radius = CUnicode(sync=True)
447 border_radius = CUnicode(sync=True)
447 border_style = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_border-style.asp
448 border_style = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_border-style.asp
448 'none',
449 'none',
449 'hidden',
450 'hidden',
450 'dotted',
451 'dotted',
451 'dashed',
452 'dashed',
452 'solid',
453 'solid',
453 'double',
454 'double',
454 'groove',
455 'groove',
455 'ridge',
456 'ridge',
456 'inset',
457 'inset',
457 'outset',
458 'outset',
458 'initial',
459 'initial',
459 'inherit', ''],
460 'inherit', ''],
460 default_value='', sync=True)
461 default_value='', sync=True)
461
462
462 font_style = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_font_font-style.asp
463 font_style = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_font_font-style.asp
463 'normal',
464 'normal',
464 'italic',
465 'italic',
465 'oblique',
466 'oblique',
466 'initial',
467 'initial',
467 'inherit', ''],
468 'inherit', ''],
468 default_value='', sync=True)
469 default_value='', sync=True)
469 font_weight = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_font_weight.asp
470 font_weight = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_font_weight.asp
470 'normal',
471 'normal',
471 'bold',
472 'bold',
472 'bolder',
473 'bolder',
473 'lighter',
474 'lighter',
474 'initial',
475 'initial',
475 'inherit', ''] + list(map(str, range(100,1000,100))),
476 'inherit', ''] + list(map(str, range(100,1000,100))),
476 default_value='', sync=True)
477 default_value='', sync=True)
477 font_size = CUnicode(sync=True)
478 font_size = CUnicode(sync=True)
478 font_family = Unicode(sync=True)
479 font_family = Unicode(sync=True)
479
480
480 def __init__(self, *pargs, **kwargs):
481 def __init__(self, *pargs, **kwargs):
481 super(DOMWidget, self).__init__(*pargs, **kwargs)
482 super(DOMWidget, self).__init__(*pargs, **kwargs)
482
483
483 def _validate_border(name, old, new):
484 def _validate_border(name, old, new):
484 if new is not None and new != '':
485 if new is not None and new != '':
485 if name != 'border_width' and not self.border_width:
486 if name != 'border_width' and not self.border_width:
486 self.border_width = 1
487 self.border_width = 1
487 if name != 'border_style' and self.border_style == '':
488 if name != 'border_style' and self.border_style == '':
488 self.border_style = 'solid'
489 self.border_style = 'solid'
489 self.on_trait_change(_validate_border, ['border_width', 'border_style', 'border_color'])
490 self.on_trait_change(_validate_border, ['border_width', 'border_style', 'border_color'])
@@ -1,296 +1,297 b''
1 """Float class.
1 """Float class.
2
2
3 Represents an unbounded float using a widget.
3 Represents an unbounded float using a widget.
4 """
4 """
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6 # Copyright (c) 2013, the IPython Development Team.
6 # Copyright (c) 2013, the IPython Development Team.
7 #
7 #
8 # Distributed under the terms of the Modified BSD License.
8 # Distributed under the terms of the Modified BSD License.
9 #
9 #
10 # The full license is in the file COPYING.txt, distributed with this software.
10 # The full license is in the file COPYING.txt, distributed with this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 from .widget import DOMWidget, register
16 from .widget import DOMWidget, register
17 from IPython.utils.traitlets import Unicode, CFloat, Bool, CaselessStrEnum, Tuple, Color
17 from .trait_types import Color
18 from IPython.utils.traitlets import Unicode, CFloat, Bool, CaselessStrEnum, Tuple
18 from IPython.utils.warn import DeprecatedClass
19 from IPython.utils.warn import DeprecatedClass
19
20
20 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
21 # Classes
22 # Classes
22 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
23 class _Float(DOMWidget):
24 class _Float(DOMWidget):
24 value = CFloat(0.0, help="Float value", sync=True)
25 value = CFloat(0.0, help="Float value", sync=True)
25 disabled = Bool(False, help="Enable or disable user changes", sync=True)
26 disabled = Bool(False, help="Enable or disable user changes", sync=True)
26 description = Unicode(help="Description of the value this widget represents", sync=True)
27 description = Unicode(help="Description of the value this widget represents", sync=True)
27
28
28 def __init__(self, value=None, **kwargs):
29 def __init__(self, value=None, **kwargs):
29 if value is not None:
30 if value is not None:
30 kwargs['value'] = value
31 kwargs['value'] = value
31 super(_Float, self).__init__(**kwargs)
32 super(_Float, self).__init__(**kwargs)
32
33
33 class _BoundedFloat(_Float):
34 class _BoundedFloat(_Float):
34 max = CFloat(100.0, help="Max value", sync=True)
35 max = CFloat(100.0, help="Max value", sync=True)
35 min = CFloat(0.0, help="Min value", sync=True)
36 min = CFloat(0.0, help="Min value", sync=True)
36 step = CFloat(0.1, help="Minimum step that the value can take (ignored by some views)", sync=True)
37 step = CFloat(0.1, help="Minimum step that the value can take (ignored by some views)", sync=True)
37
38
38 def __init__(self, *pargs, **kwargs):
39 def __init__(self, *pargs, **kwargs):
39 """Constructor"""
40 """Constructor"""
40 super(_BoundedFloat, self).__init__(*pargs, **kwargs)
41 super(_BoundedFloat, self).__init__(*pargs, **kwargs)
41 self._handle_value_changed('value', None, self.value)
42 self._handle_value_changed('value', None, self.value)
42 self._handle_max_changed('max', None, self.max)
43 self._handle_max_changed('max', None, self.max)
43 self._handle_min_changed('min', None, self.min)
44 self._handle_min_changed('min', None, self.min)
44 self.on_trait_change(self._handle_value_changed, 'value')
45 self.on_trait_change(self._handle_value_changed, 'value')
45 self.on_trait_change(self._handle_max_changed, 'max')
46 self.on_trait_change(self._handle_max_changed, 'max')
46 self.on_trait_change(self._handle_min_changed, 'min')
47 self.on_trait_change(self._handle_min_changed, 'min')
47
48
48 def _handle_value_changed(self, name, old, new):
49 def _handle_value_changed(self, name, old, new):
49 """Validate value."""
50 """Validate value."""
50 if self.min > new or new > self.max:
51 if self.min > new or new > self.max:
51 self.value = min(max(new, self.min), self.max)
52 self.value = min(max(new, self.min), self.max)
52
53
53 def _handle_max_changed(self, name, old, new):
54 def _handle_max_changed(self, name, old, new):
54 """Make sure the min is always <= the max."""
55 """Make sure the min is always <= the max."""
55 if new < self.min:
56 if new < self.min:
56 raise ValueError("setting max < min")
57 raise ValueError("setting max < min")
57 if new < self.value:
58 if new < self.value:
58 self.value = new
59 self.value = new
59
60
60 def _handle_min_changed(self, name, old, new):
61 def _handle_min_changed(self, name, old, new):
61 """Make sure the max is always >= the min."""
62 """Make sure the max is always >= the min."""
62 if new > self.max:
63 if new > self.max:
63 raise ValueError("setting min > max")
64 raise ValueError("setting min > max")
64 if new > self.value:
65 if new > self.value:
65 self.value = new
66 self.value = new
66
67
67
68
68 @register('IPython.FloatText')
69 @register('IPython.FloatText')
69 class FloatText(_Float):
70 class FloatText(_Float):
70 """ Displays a float value within a textbox. For a textbox in
71 """ Displays a float value within a textbox. For a textbox in
71 which the value must be within a specific range, use BoundedFloatText.
72 which the value must be within a specific range, use BoundedFloatText.
72
73
73 Parameters
74 Parameters
74 ----------
75 ----------
75 value : float
76 value : float
76 value displayed
77 value displayed
77 description : str
78 description : str
78 description displayed next to the textbox
79 description displayed next to the textbox
79 color : str Unicode color code (eg. '#C13535'), optional
80 color : str Unicode color code (eg. '#C13535'), optional
80 color of the value displayed
81 color of the value displayed
81 """
82 """
82 _view_name = Unicode('FloatTextView', sync=True)
83 _view_name = Unicode('FloatTextView', sync=True)
83
84
84
85
85 @register('IPython.BoundedFloatText')
86 @register('IPython.BoundedFloatText')
86 class BoundedFloatText(_BoundedFloat):
87 class BoundedFloatText(_BoundedFloat):
87 """ Displays a float value within a textbox. Value must be within the range specified.
88 """ Displays a float value within a textbox. Value must be within the range specified.
88 For a textbox in which the value doesn't need to be within a specific range, use FloatText.
89 For a textbox in which the value doesn't need to be within a specific range, use FloatText.
89
90
90 Parameters
91 Parameters
91 ----------
92 ----------
92 value : float
93 value : float
93 value displayed
94 value displayed
94 min : float
95 min : float
95 minimal value of the range of possible values displayed
96 minimal value of the range of possible values displayed
96 max : float
97 max : float
97 maximal value of the range of possible values displayed
98 maximal value of the range of possible values displayed
98 description : str
99 description : str
99 description displayed next to the textbox
100 description displayed next to the textbox
100 color : str Unicode color code (eg. '#C13535'), optional
101 color : str Unicode color code (eg. '#C13535'), optional
101 color of the value displayed
102 color of the value displayed
102 """
103 """
103 _view_name = Unicode('FloatTextView', sync=True)
104 _view_name = Unicode('FloatTextView', sync=True)
104
105
105
106
106 @register('IPython.FloatSlider')
107 @register('IPython.FloatSlider')
107 class FloatSlider(_BoundedFloat):
108 class FloatSlider(_BoundedFloat):
108 """ Slider/trackbar of floating values with the specified range.
109 """ Slider/trackbar of floating values with the specified range.
109
110
110 Parameters
111 Parameters
111 ----------
112 ----------
112 value : float
113 value : float
113 position of the slider
114 position of the slider
114 min : float
115 min : float
115 minimal position of the slider
116 minimal position of the slider
116 max : float
117 max : float
117 maximal position of the slider
118 maximal position of the slider
118 step : float
119 step : float
119 step of the trackbar
120 step of the trackbar
120 description : str
121 description : str
121 name of the slider
122 name of the slider
122 orientation : {'vertical', 'horizontal}, optional
123 orientation : {'vertical', 'horizontal}, optional
123 default is horizontal
124 default is horizontal
124 readout : {True, False}, optional
125 readout : {True, False}, optional
125 default is True, display the current value of the slider next to it
126 default is True, display the current value of the slider next to it
126 slider_color : str Unicode color code (eg. '#C13535'), optional
127 slider_color : str Unicode color code (eg. '#C13535'), optional
127 color of the slider
128 color of the slider
128 color : str Unicode color code (eg. '#C13535'), optional
129 color : str Unicode color code (eg. '#C13535'), optional
129 color of the value displayed (if readout == True)
130 color of the value displayed (if readout == True)
130 """
131 """
131 _view_name = Unicode('FloatSliderView', sync=True)
132 _view_name = Unicode('FloatSliderView', sync=True)
132 orientation = CaselessStrEnum(values=['horizontal', 'vertical'],
133 orientation = CaselessStrEnum(values=['horizontal', 'vertical'],
133 default_value='horizontal', help="Vertical or horizontal.", sync=True)
134 default_value='horizontal', help="Vertical or horizontal.", sync=True)
134 _range = Bool(False, help="Display a range selector", sync=True)
135 _range = Bool(False, help="Display a range selector", sync=True)
135 readout = Bool(True, help="Display the current value of the slider next to it.", sync=True)
136 readout = Bool(True, help="Display the current value of the slider next to it.", sync=True)
136 slider_color = Color(None, allow_none=True, sync=True)
137 slider_color = Color(None, allow_none=True, sync=True)
137
138
138
139
139 @register('IPython.FloatProgress')
140 @register('IPython.FloatProgress')
140 class FloatProgress(_BoundedFloat):
141 class FloatProgress(_BoundedFloat):
141 """ Displays a progress bar.
142 """ Displays a progress bar.
142
143
143 Parameters
144 Parameters
144 -----------
145 -----------
145 value : float
146 value : float
146 position within the range of the progress bar
147 position within the range of the progress bar
147 min : float
148 min : float
148 minimal position of the slider
149 minimal position of the slider
149 max : float
150 max : float
150 maximal position of the slider
151 maximal position of the slider
151 step : float
152 step : float
152 step of the progress bar
153 step of the progress bar
153 description : str
154 description : str
154 name of the progress bar
155 name of the progress bar
155 bar_style: {'success', 'info', 'warning', 'danger', ''}, optional
156 bar_style: {'success', 'info', 'warning', 'danger', ''}, optional
156 color of the progress bar, default is '' (blue)
157 color of the progress bar, default is '' (blue)
157 colors are: 'success'-green, 'info'-light blue, 'warning'-orange, 'danger'-red
158 colors are: 'success'-green, 'info'-light blue, 'warning'-orange, 'danger'-red
158 """
159 """
159 _view_name = Unicode('ProgressView', sync=True)
160 _view_name = Unicode('ProgressView', sync=True)
160
161
161 bar_style = CaselessStrEnum(
162 bar_style = CaselessStrEnum(
162 values=['success', 'info', 'warning', 'danger', ''],
163 values=['success', 'info', 'warning', 'danger', ''],
163 default_value='', allow_none=True, sync=True, help="""Use a
164 default_value='', allow_none=True, sync=True, help="""Use a
164 predefined styling for the progess bar.""")
165 predefined styling for the progess bar.""")
165
166
166 class _FloatRange(_Float):
167 class _FloatRange(_Float):
167 value = Tuple(CFloat, CFloat, default_value=(0.0, 1.0), help="Tuple of (lower, upper) bounds", sync=True)
168 value = Tuple(CFloat, CFloat, default_value=(0.0, 1.0), help="Tuple of (lower, upper) bounds", sync=True)
168 lower = CFloat(0.0, help="Lower bound", sync=False)
169 lower = CFloat(0.0, help="Lower bound", sync=False)
169 upper = CFloat(1.0, help="Upper bound", sync=False)
170 upper = CFloat(1.0, help="Upper bound", sync=False)
170
171
171 def __init__(self, *pargs, **kwargs):
172 def __init__(self, *pargs, **kwargs):
172 value_given = 'value' in kwargs
173 value_given = 'value' in kwargs
173 lower_given = 'lower' in kwargs
174 lower_given = 'lower' in kwargs
174 upper_given = 'upper' in kwargs
175 upper_given = 'upper' in kwargs
175 if value_given and (lower_given or upper_given):
176 if value_given and (lower_given or upper_given):
176 raise ValueError("Cannot specify both 'value' and 'lower'/'upper' for range widget")
177 raise ValueError("Cannot specify both 'value' and 'lower'/'upper' for range widget")
177 if lower_given != upper_given:
178 if lower_given != upper_given:
178 raise ValueError("Must specify both 'lower' and 'upper' for range widget")
179 raise ValueError("Must specify both 'lower' and 'upper' for range widget")
179
180
180 DOMWidget.__init__(self, *pargs, **kwargs)
181 DOMWidget.__init__(self, *pargs, **kwargs)
181
182
182 # ensure the traits match, preferring whichever (if any) was given in kwargs
183 # ensure the traits match, preferring whichever (if any) was given in kwargs
183 if value_given:
184 if value_given:
184 self.lower, self.upper = self.value
185 self.lower, self.upper = self.value
185 else:
186 else:
186 self.value = (self.lower, self.upper)
187 self.value = (self.lower, self.upper)
187
188
188 self.on_trait_change(self._validate, ['value', 'upper', 'lower'])
189 self.on_trait_change(self._validate, ['value', 'upper', 'lower'])
189
190
190 def _validate(self, name, old, new):
191 def _validate(self, name, old, new):
191 if name == 'value':
192 if name == 'value':
192 self.lower, self.upper = min(new), max(new)
193 self.lower, self.upper = min(new), max(new)
193 elif name == 'lower':
194 elif name == 'lower':
194 self.value = (new, self.value[1])
195 self.value = (new, self.value[1])
195 elif name == 'upper':
196 elif name == 'upper':
196 self.value = (self.value[0], new)
197 self.value = (self.value[0], new)
197
198
198 class _BoundedFloatRange(_FloatRange):
199 class _BoundedFloatRange(_FloatRange):
199 step = CFloat(1.0, help="Minimum step that the value can take (ignored by some views)", sync=True)
200 step = CFloat(1.0, help="Minimum step that the value can take (ignored by some views)", sync=True)
200 max = CFloat(100.0, help="Max value", sync=True)
201 max = CFloat(100.0, help="Max value", sync=True)
201 min = CFloat(0.0, help="Min value", sync=True)
202 min = CFloat(0.0, help="Min value", sync=True)
202
203
203 def __init__(self, *pargs, **kwargs):
204 def __init__(self, *pargs, **kwargs):
204 any_value_given = 'value' in kwargs or 'upper' in kwargs or 'lower' in kwargs
205 any_value_given = 'value' in kwargs or 'upper' in kwargs or 'lower' in kwargs
205 _FloatRange.__init__(self, *pargs, **kwargs)
206 _FloatRange.__init__(self, *pargs, **kwargs)
206
207
207 # ensure a minimal amount of sanity
208 # ensure a minimal amount of sanity
208 if self.min > self.max:
209 if self.min > self.max:
209 raise ValueError("min must be <= max")
210 raise ValueError("min must be <= max")
210
211
211 if any_value_given:
212 if any_value_given:
212 # if a value was given, clamp it within (min, max)
213 # if a value was given, clamp it within (min, max)
213 self._validate("value", None, self.value)
214 self._validate("value", None, self.value)
214 else:
215 else:
215 # otherwise, set it to 25-75% to avoid the handles overlapping
216 # otherwise, set it to 25-75% to avoid the handles overlapping
216 self.value = (0.75*self.min + 0.25*self.max,
217 self.value = (0.75*self.min + 0.25*self.max,
217 0.25*self.min + 0.75*self.max)
218 0.25*self.min + 0.75*self.max)
218 # callback already set for 'value', 'lower', 'upper'
219 # callback already set for 'value', 'lower', 'upper'
219 self.on_trait_change(self._validate, ['min', 'max'])
220 self.on_trait_change(self._validate, ['min', 'max'])
220
221
221
222
222 def _validate(self, name, old, new):
223 def _validate(self, name, old, new):
223 if name == "min":
224 if name == "min":
224 if new > self.max:
225 if new > self.max:
225 raise ValueError("setting min > max")
226 raise ValueError("setting min > max")
226 self.min = new
227 self.min = new
227 elif name == "max":
228 elif name == "max":
228 if new < self.min:
229 if new < self.min:
229 raise ValueError("setting max < min")
230 raise ValueError("setting max < min")
230 self.max = new
231 self.max = new
231
232
232 low, high = self.value
233 low, high = self.value
233 if name == "value":
234 if name == "value":
234 low, high = min(new), max(new)
235 low, high = min(new), max(new)
235 elif name == "upper":
236 elif name == "upper":
236 if new < self.lower:
237 if new < self.lower:
237 raise ValueError("setting upper < lower")
238 raise ValueError("setting upper < lower")
238 high = new
239 high = new
239 elif name == "lower":
240 elif name == "lower":
240 if new > self.upper:
241 if new > self.upper:
241 raise ValueError("setting lower > upper")
242 raise ValueError("setting lower > upper")
242 low = new
243 low = new
243
244
244 low = max(self.min, min(low, self.max))
245 low = max(self.min, min(low, self.max))
245 high = min(self.max, max(high, self.min))
246 high = min(self.max, max(high, self.min))
246
247
247 # determine the order in which we should update the
248 # determine the order in which we should update the
248 # lower, upper traits to avoid a temporary inverted overlap
249 # lower, upper traits to avoid a temporary inverted overlap
249 lower_first = high < self.lower
250 lower_first = high < self.lower
250
251
251 self.value = (low, high)
252 self.value = (low, high)
252 if lower_first:
253 if lower_first:
253 self.lower = low
254 self.lower = low
254 self.upper = high
255 self.upper = high
255 else:
256 else:
256 self.upper = high
257 self.upper = high
257 self.lower = low
258 self.lower = low
258
259
259
260
260 @register('IPython.FloatRangeSlider')
261 @register('IPython.FloatRangeSlider')
261 class FloatRangeSlider(_BoundedFloatRange):
262 class FloatRangeSlider(_BoundedFloatRange):
262 """ Slider/trackbar for displaying a floating value range (within the specified range of values).
263 """ Slider/trackbar for displaying a floating value range (within the specified range of values).
263
264
264 Parameters
265 Parameters
265 ----------
266 ----------
266 value : float tuple
267 value : float tuple
267 range of the slider displayed
268 range of the slider displayed
268 min : float
269 min : float
269 minimal position of the slider
270 minimal position of the slider
270 max : float
271 max : float
271 maximal position of the slider
272 maximal position of the slider
272 step : float
273 step : float
273 step of the trackbar
274 step of the trackbar
274 description : str
275 description : str
275 name of the slider
276 name of the slider
276 orientation : {'vertical', 'horizontal}, optional
277 orientation : {'vertical', 'horizontal}, optional
277 default is horizontal
278 default is horizontal
278 readout : {True, False}, optional
279 readout : {True, False}, optional
279 default is True, display the current value of the slider next to it
280 default is True, display the current value of the slider next to it
280 slider_color : str Unicode color code (eg. '#C13535'), optional
281 slider_color : str Unicode color code (eg. '#C13535'), optional
281 color of the slider
282 color of the slider
282 color : str Unicode color code (eg. '#C13535'), optional
283 color : str Unicode color code (eg. '#C13535'), optional
283 color of the value displayed (if readout == True)
284 color of the value displayed (if readout == True)
284 """
285 """
285 _view_name = Unicode('FloatSliderView', sync=True)
286 _view_name = Unicode('FloatSliderView', sync=True)
286 orientation = CaselessStrEnum(values=['horizontal', 'vertical'],
287 orientation = CaselessStrEnum(values=['horizontal', 'vertical'],
287 default_value='horizontal', help="Vertical or horizontal.", sync=True)
288 default_value='horizontal', help="Vertical or horizontal.", sync=True)
288 _range = Bool(True, help="Display a range selector", sync=True)
289 _range = Bool(True, help="Display a range selector", sync=True)
289 readout = Bool(True, help="Display the current value of the slider next to it.", sync=True)
290 readout = Bool(True, help="Display the current value of the slider next to it.", sync=True)
290 slider_color = Color(None, allow_none=True, sync=True)
291 slider_color = Color(None, allow_none=True, sync=True)
291
292
292 # Remove in IPython 4.0
293 # Remove in IPython 4.0
293 FloatTextWidget = DeprecatedClass(FloatText, 'FloatTextWidget')
294 FloatTextWidget = DeprecatedClass(FloatText, 'FloatTextWidget')
294 BoundedFloatTextWidget = DeprecatedClass(BoundedFloatText, 'BoundedFloatTextWidget')
295 BoundedFloatTextWidget = DeprecatedClass(BoundedFloatText, 'BoundedFloatTextWidget')
295 FloatSliderWidget = DeprecatedClass(FloatSlider, 'FloatSliderWidget')
296 FloatSliderWidget = DeprecatedClass(FloatSlider, 'FloatSliderWidget')
296 FloatProgressWidget = DeprecatedClass(FloatProgress, 'FloatProgressWidget')
297 FloatProgressWidget = DeprecatedClass(FloatProgress, 'FloatProgressWidget')
@@ -1,207 +1,208 b''
1 """Int class.
1 """Int class.
2
2
3 Represents an unbounded int using a widget.
3 Represents an unbounded int using a widget.
4 """
4 """
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6 # Copyright (c) 2013, the IPython Development Team.
6 # Copyright (c) 2013, the IPython Development Team.
7 #
7 #
8 # Distributed under the terms of the Modified BSD License.
8 # Distributed under the terms of the Modified BSD License.
9 #
9 #
10 # The full license is in the file COPYING.txt, distributed with this software.
10 # The full license is in the file COPYING.txt, distributed with this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 from .widget import DOMWidget, register
16 from .widget import DOMWidget, register
17 from IPython.utils.traitlets import Unicode, CInt, Bool, CaselessStrEnum, Tuple, Color
17 from .trait_types import Color
18 from IPython.utils.traitlets import Unicode, CInt, Bool, CaselessStrEnum, Tuple
18 from IPython.utils.warn import DeprecatedClass
19 from IPython.utils.warn import DeprecatedClass
19
20
20 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
21 # Classes
22 # Classes
22 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
23 class _Int(DOMWidget):
24 class _Int(DOMWidget):
24 """Base class used to create widgets that represent an int."""
25 """Base class used to create widgets that represent an int."""
25 value = CInt(0, help="Int value", sync=True)
26 value = CInt(0, help="Int value", sync=True)
26 disabled = Bool(False, help="Enable or disable user changes", sync=True)
27 disabled = Bool(False, help="Enable or disable user changes", sync=True)
27 description = Unicode(help="Description of the value this widget represents", sync=True)
28 description = Unicode(help="Description of the value this widget represents", sync=True)
28
29
29 def __init__(self, value=None, **kwargs):
30 def __init__(self, value=None, **kwargs):
30 if value is not None:
31 if value is not None:
31 kwargs['value'] = value
32 kwargs['value'] = value
32 super(_Int, self).__init__(**kwargs)
33 super(_Int, self).__init__(**kwargs)
33
34
34 class _BoundedInt(_Int):
35 class _BoundedInt(_Int):
35 """Base class used to create widgets that represent a int that is bounded
36 """Base class used to create widgets that represent a int that is bounded
36 by a minium and maximum."""
37 by a minium and maximum."""
37 step = CInt(1, help="Minimum step that the value can take (ignored by some views)", sync=True)
38 step = CInt(1, help="Minimum step that the value can take (ignored by some views)", sync=True)
38 max = CInt(100, help="Max value", sync=True)
39 max = CInt(100, help="Max value", sync=True)
39 min = CInt(0, help="Min value", sync=True)
40 min = CInt(0, help="Min value", sync=True)
40
41
41 def __init__(self, *pargs, **kwargs):
42 def __init__(self, *pargs, **kwargs):
42 """Constructor"""
43 """Constructor"""
43 super(_BoundedInt, self).__init__(*pargs, **kwargs)
44 super(_BoundedInt, self).__init__(*pargs, **kwargs)
44 self._handle_value_changed('value', None, self.value)
45 self._handle_value_changed('value', None, self.value)
45 self._handle_max_changed('max', None, self.max)
46 self._handle_max_changed('max', None, self.max)
46 self._handle_min_changed('min', None, self.min)
47 self._handle_min_changed('min', None, self.min)
47 self.on_trait_change(self._handle_value_changed, 'value')
48 self.on_trait_change(self._handle_value_changed, 'value')
48 self.on_trait_change(self._handle_max_changed, 'max')
49 self.on_trait_change(self._handle_max_changed, 'max')
49 self.on_trait_change(self._handle_min_changed, 'min')
50 self.on_trait_change(self._handle_min_changed, 'min')
50
51
51 def _handle_value_changed(self, name, old, new):
52 def _handle_value_changed(self, name, old, new):
52 """Validate value."""
53 """Validate value."""
53 if self.min > new or new > self.max:
54 if self.min > new or new > self.max:
54 self.value = min(max(new, self.min), self.max)
55 self.value = min(max(new, self.min), self.max)
55
56
56 def _handle_max_changed(self, name, old, new):
57 def _handle_max_changed(self, name, old, new):
57 """Make sure the min is always <= the max."""
58 """Make sure the min is always <= the max."""
58 if new < self.min:
59 if new < self.min:
59 raise ValueError("setting max < min")
60 raise ValueError("setting max < min")
60 if new < self.value:
61 if new < self.value:
61 self.value = new
62 self.value = new
62
63
63 def _handle_min_changed(self, name, old, new):
64 def _handle_min_changed(self, name, old, new):
64 """Make sure the max is always >= the min."""
65 """Make sure the max is always >= the min."""
65 if new > self.max:
66 if new > self.max:
66 raise ValueError("setting min > max")
67 raise ValueError("setting min > max")
67 if new > self.value:
68 if new > self.value:
68 self.value = new
69 self.value = new
69
70
70 @register('IPython.IntText')
71 @register('IPython.IntText')
71 class IntText(_Int):
72 class IntText(_Int):
72 """Textbox widget that represents a int."""
73 """Textbox widget that represents a int."""
73 _view_name = Unicode('IntTextView', sync=True)
74 _view_name = Unicode('IntTextView', sync=True)
74
75
75
76
76 @register('IPython.BoundedIntText')
77 @register('IPython.BoundedIntText')
77 class BoundedIntText(_BoundedInt):
78 class BoundedIntText(_BoundedInt):
78 """Textbox widget that represents a int bounded by a minimum and maximum value."""
79 """Textbox widget that represents a int bounded by a minimum and maximum value."""
79 _view_name = Unicode('IntTextView', sync=True)
80 _view_name = Unicode('IntTextView', sync=True)
80
81
81
82
82 @register('IPython.IntSlider')
83 @register('IPython.IntSlider')
83 class IntSlider(_BoundedInt):
84 class IntSlider(_BoundedInt):
84 """Slider widget that represents a int bounded by a minimum and maximum value."""
85 """Slider widget that represents a int bounded by a minimum and maximum value."""
85 _view_name = Unicode('IntSliderView', sync=True)
86 _view_name = Unicode('IntSliderView', sync=True)
86 orientation = CaselessStrEnum(values=['horizontal', 'vertical'],
87 orientation = CaselessStrEnum(values=['horizontal', 'vertical'],
87 default_value='horizontal', help="Vertical or horizontal.", sync=True)
88 default_value='horizontal', help="Vertical or horizontal.", sync=True)
88 _range = Bool(False, help="Display a range selector", sync=True)
89 _range = Bool(False, help="Display a range selector", sync=True)
89 readout = Bool(True, help="Display the current value of the slider next to it.", sync=True)
90 readout = Bool(True, help="Display the current value of the slider next to it.", sync=True)
90 slider_color = Color(None, allow_none=True, sync=True)
91 slider_color = Color(None, allow_none=True, sync=True)
91
92
92
93
93 @register('IPython.IntProgress')
94 @register('IPython.IntProgress')
94 class IntProgress(_BoundedInt):
95 class IntProgress(_BoundedInt):
95 """Progress bar that represents a int bounded by a minimum and maximum value."""
96 """Progress bar that represents a int bounded by a minimum and maximum value."""
96 _view_name = Unicode('ProgressView', sync=True)
97 _view_name = Unicode('ProgressView', sync=True)
97
98
98 bar_style = CaselessStrEnum(
99 bar_style = CaselessStrEnum(
99 values=['success', 'info', 'warning', 'danger', ''],
100 values=['success', 'info', 'warning', 'danger', ''],
100 default_value='', allow_none=True, sync=True, help="""Use a
101 default_value='', allow_none=True, sync=True, help="""Use a
101 predefined styling for the progess bar.""")
102 predefined styling for the progess bar.""")
102
103
103 class _IntRange(_Int):
104 class _IntRange(_Int):
104 value = Tuple(CInt, CInt, default_value=(0, 1), help="Tuple of (lower, upper) bounds", sync=True)
105 value = Tuple(CInt, CInt, default_value=(0, 1), help="Tuple of (lower, upper) bounds", sync=True)
105 lower = CInt(0, help="Lower bound", sync=False)
106 lower = CInt(0, help="Lower bound", sync=False)
106 upper = CInt(1, help="Upper bound", sync=False)
107 upper = CInt(1, help="Upper bound", sync=False)
107
108
108 def __init__(self, *pargs, **kwargs):
109 def __init__(self, *pargs, **kwargs):
109 value_given = 'value' in kwargs
110 value_given = 'value' in kwargs
110 lower_given = 'lower' in kwargs
111 lower_given = 'lower' in kwargs
111 upper_given = 'upper' in kwargs
112 upper_given = 'upper' in kwargs
112 if value_given and (lower_given or upper_given):
113 if value_given and (lower_given or upper_given):
113 raise ValueError("Cannot specify both 'value' and 'lower'/'upper' for range widget")
114 raise ValueError("Cannot specify both 'value' and 'lower'/'upper' for range widget")
114 if lower_given != upper_given:
115 if lower_given != upper_given:
115 raise ValueError("Must specify both 'lower' and 'upper' for range widget")
116 raise ValueError("Must specify both 'lower' and 'upper' for range widget")
116
117
117 super(_IntRange, self).__init__(*pargs, **kwargs)
118 super(_IntRange, self).__init__(*pargs, **kwargs)
118
119
119 # ensure the traits match, preferring whichever (if any) was given in kwargs
120 # ensure the traits match, preferring whichever (if any) was given in kwargs
120 if value_given:
121 if value_given:
121 self.lower, self.upper = self.value
122 self.lower, self.upper = self.value
122 else:
123 else:
123 self.value = (self.lower, self.upper)
124 self.value = (self.lower, self.upper)
124
125
125 self.on_trait_change(self._validate, ['value', 'upper', 'lower'])
126 self.on_trait_change(self._validate, ['value', 'upper', 'lower'])
126
127
127 def _validate(self, name, old, new):
128 def _validate(self, name, old, new):
128 if name == 'value':
129 if name == 'value':
129 self.lower, self.upper = min(new), max(new)
130 self.lower, self.upper = min(new), max(new)
130 elif name == 'lower':
131 elif name == 'lower':
131 self.value = (new, self.value[1])
132 self.value = (new, self.value[1])
132 elif name == 'upper':
133 elif name == 'upper':
133 self.value = (self.value[0], new)
134 self.value = (self.value[0], new)
134
135
135 class _BoundedIntRange(_IntRange):
136 class _BoundedIntRange(_IntRange):
136 step = CInt(1, help="Minimum step that the value can take (ignored by some views)", sync=True)
137 step = CInt(1, help="Minimum step that the value can take (ignored by some views)", sync=True)
137 max = CInt(100, help="Max value", sync=True)
138 max = CInt(100, help="Max value", sync=True)
138 min = CInt(0, help="Min value", sync=True)
139 min = CInt(0, help="Min value", sync=True)
139
140
140 def __init__(self, *pargs, **kwargs):
141 def __init__(self, *pargs, **kwargs):
141 any_value_given = 'value' in kwargs or 'upper' in kwargs or 'lower' in kwargs
142 any_value_given = 'value' in kwargs or 'upper' in kwargs or 'lower' in kwargs
142 _IntRange.__init__(self, *pargs, **kwargs)
143 _IntRange.__init__(self, *pargs, **kwargs)
143
144
144 # ensure a minimal amount of sanity
145 # ensure a minimal amount of sanity
145 if self.min > self.max:
146 if self.min > self.max:
146 raise ValueError("min must be <= max")
147 raise ValueError("min must be <= max")
147
148
148 if any_value_given:
149 if any_value_given:
149 # if a value was given, clamp it within (min, max)
150 # if a value was given, clamp it within (min, max)
150 self._validate("value", None, self.value)
151 self._validate("value", None, self.value)
151 else:
152 else:
152 # otherwise, set it to 25-75% to avoid the handles overlapping
153 # otherwise, set it to 25-75% to avoid the handles overlapping
153 self.value = (0.75*self.min + 0.25*self.max,
154 self.value = (0.75*self.min + 0.25*self.max,
154 0.25*self.min + 0.75*self.max)
155 0.25*self.min + 0.75*self.max)
155 # callback already set for 'value', 'lower', 'upper'
156 # callback already set for 'value', 'lower', 'upper'
156 self.on_trait_change(self._validate, ['min', 'max'])
157 self.on_trait_change(self._validate, ['min', 'max'])
157
158
158 def _validate(self, name, old, new):
159 def _validate(self, name, old, new):
159 if name == "min":
160 if name == "min":
160 if new > self.max:
161 if new > self.max:
161 raise ValueError("setting min > max")
162 raise ValueError("setting min > max")
162 elif name == "max":
163 elif name == "max":
163 if new < self.min:
164 if new < self.min:
164 raise ValueError("setting max < min")
165 raise ValueError("setting max < min")
165
166
166 low, high = self.value
167 low, high = self.value
167 if name == "value":
168 if name == "value":
168 low, high = min(new), max(new)
169 low, high = min(new), max(new)
169 elif name == "upper":
170 elif name == "upper":
170 if new < self.lower:
171 if new < self.lower:
171 raise ValueError("setting upper < lower")
172 raise ValueError("setting upper < lower")
172 high = new
173 high = new
173 elif name == "lower":
174 elif name == "lower":
174 if new > self.upper:
175 if new > self.upper:
175 raise ValueError("setting lower > upper")
176 raise ValueError("setting lower > upper")
176 low = new
177 low = new
177
178
178 low = max(self.min, min(low, self.max))
179 low = max(self.min, min(low, self.max))
179 high = min(self.max, max(high, self.min))
180 high = min(self.max, max(high, self.min))
180
181
181 # determine the order in which we should update the
182 # determine the order in which we should update the
182 # lower, upper traits to avoid a temporary inverted overlap
183 # lower, upper traits to avoid a temporary inverted overlap
183 lower_first = high < self.lower
184 lower_first = high < self.lower
184
185
185 self.value = (low, high)
186 self.value = (low, high)
186 if lower_first:
187 if lower_first:
187 self.lower = low
188 self.lower = low
188 self.upper = high
189 self.upper = high
189 else:
190 else:
190 self.upper = high
191 self.upper = high
191 self.lower = low
192 self.lower = low
192
193
193 @register('IPython.IntRangeSlider')
194 @register('IPython.IntRangeSlider')
194 class IntRangeSlider(_BoundedIntRange):
195 class IntRangeSlider(_BoundedIntRange):
195 """Slider widget that represents a pair of ints between a minimum and maximum value."""
196 """Slider widget that represents a pair of ints between a minimum and maximum value."""
196 _view_name = Unicode('IntSliderView', sync=True)
197 _view_name = Unicode('IntSliderView', sync=True)
197 orientation = CaselessStrEnum(values=['horizontal', 'vertical'],
198 orientation = CaselessStrEnum(values=['horizontal', 'vertical'],
198 default_value='horizontal', help="Vertical or horizontal.", sync=True)
199 default_value='horizontal', help="Vertical or horizontal.", sync=True)
199 _range = Bool(True, help="Display a range selector", sync=True)
200 _range = Bool(True, help="Display a range selector", sync=True)
200 readout = Bool(True, help="Display the current value of the slider next to it.", sync=True)
201 readout = Bool(True, help="Display the current value of the slider next to it.", sync=True)
201 slider_color = Color(None, allow_none=True, sync=True)
202 slider_color = Color(None, allow_none=True, sync=True)
202
203
203 # Remove in IPython 4.0
204 # Remove in IPython 4.0
204 IntTextWidget = DeprecatedClass(IntText, 'IntTextWidget')
205 IntTextWidget = DeprecatedClass(IntText, 'IntTextWidget')
205 BoundedIntTextWidget = DeprecatedClass(BoundedIntText, 'BoundedIntTextWidget')
206 BoundedIntTextWidget = DeprecatedClass(BoundedIntText, 'BoundedIntTextWidget')
206 IntSliderWidget = DeprecatedClass(IntSlider, 'IntSliderWidget')
207 IntSliderWidget = DeprecatedClass(IntSlider, 'IntSliderWidget')
207 IntProgressWidget = DeprecatedClass(IntProgress, 'IntProgressWidget')
208 IntProgressWidget = DeprecatedClass(IntProgress, 'IntProgressWidget')
@@ -1,1503 +1,1493 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """Tests for IPython.utils.traitlets."""
2 """Tests for IPython.utils.traitlets."""
3
3
4 # Copyright (c) IPython Development Team.
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
5 # Distributed under the terms of the Modified BSD License.
6 #
6 #
7 # Adapted from enthought.traits, Copyright (c) Enthought, Inc.,
7 # Adapted from enthought.traits, Copyright (c) Enthought, Inc.,
8 # also under the terms of the Modified BSD License.
8 # also under the terms of the Modified BSD License.
9
9
10 import pickle
10 import pickle
11 import re
11 import re
12 import sys
12 import sys
13 from unittest import TestCase
13 from unittest import TestCase
14
14
15 import nose.tools as nt
15 import nose.tools as nt
16 from nose import SkipTest
16 from nose import SkipTest
17
17
18 from IPython.utils.traitlets import (
18 from IPython.utils.traitlets import (
19 HasTraits, MetaHasTraits, TraitType, Any, Bool, CBytes, Dict, Enum,
19 HasTraits, MetaHasTraits, TraitType, Any, Bool, CBytes, Dict, Enum,
20 Int, Long, Integer, Float, Complex, Bytes, Unicode, Color, TraitError,
20 Int, Long, Integer, Float, Complex, Bytes, Unicode, TraitError,
21 Union, Undefined, Type, This, Instance, TCPAddress, List, Tuple,
21 Union, Undefined, Type, This, Instance, TCPAddress, List, Tuple,
22 ObjectName, DottedObjectName, CRegExp, link, directional_link,
22 ObjectName, DottedObjectName, CRegExp, link, directional_link,
23 EventfulList, EventfulDict, ForwardDeclaredType, ForwardDeclaredInstance,
23 EventfulList, EventfulDict, ForwardDeclaredType, ForwardDeclaredInstance,
24 )
24 )
25 from IPython.utils import py3compat
25 from IPython.utils import py3compat
26 from IPython.testing.decorators import skipif
26 from IPython.testing.decorators import skipif
27
27
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29 # Helper classes for testing
29 # Helper classes for testing
30 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
31
31
32
32
33 class HasTraitsStub(HasTraits):
33 class HasTraitsStub(HasTraits):
34
34
35 def _notify_trait(self, name, old, new):
35 def _notify_trait(self, name, old, new):
36 self._notify_name = name
36 self._notify_name = name
37 self._notify_old = old
37 self._notify_old = old
38 self._notify_new = new
38 self._notify_new = new
39
39
40
40
41 #-----------------------------------------------------------------------------
41 #-----------------------------------------------------------------------------
42 # Test classes
42 # Test classes
43 #-----------------------------------------------------------------------------
43 #-----------------------------------------------------------------------------
44
44
45
45
46 class TestTraitType(TestCase):
46 class TestTraitType(TestCase):
47
47
48 def test_get_undefined(self):
48 def test_get_undefined(self):
49 class A(HasTraits):
49 class A(HasTraits):
50 a = TraitType
50 a = TraitType
51 a = A()
51 a = A()
52 self.assertEqual(a.a, Undefined)
52 self.assertEqual(a.a, Undefined)
53
53
54 def test_set(self):
54 def test_set(self):
55 class A(HasTraitsStub):
55 class A(HasTraitsStub):
56 a = TraitType
56 a = TraitType
57
57
58 a = A()
58 a = A()
59 a.a = 10
59 a.a = 10
60 self.assertEqual(a.a, 10)
60 self.assertEqual(a.a, 10)
61 self.assertEqual(a._notify_name, 'a')
61 self.assertEqual(a._notify_name, 'a')
62 self.assertEqual(a._notify_old, Undefined)
62 self.assertEqual(a._notify_old, Undefined)
63 self.assertEqual(a._notify_new, 10)
63 self.assertEqual(a._notify_new, 10)
64
64
65 def test_validate(self):
65 def test_validate(self):
66 class MyTT(TraitType):
66 class MyTT(TraitType):
67 def validate(self, inst, value):
67 def validate(self, inst, value):
68 return -1
68 return -1
69 class A(HasTraitsStub):
69 class A(HasTraitsStub):
70 tt = MyTT
70 tt = MyTT
71
71
72 a = A()
72 a = A()
73 a.tt = 10
73 a.tt = 10
74 self.assertEqual(a.tt, -1)
74 self.assertEqual(a.tt, -1)
75
75
76 def test_default_validate(self):
76 def test_default_validate(self):
77 class MyIntTT(TraitType):
77 class MyIntTT(TraitType):
78 def validate(self, obj, value):
78 def validate(self, obj, value):
79 if isinstance(value, int):
79 if isinstance(value, int):
80 return value
80 return value
81 self.error(obj, value)
81 self.error(obj, value)
82 class A(HasTraits):
82 class A(HasTraits):
83 tt = MyIntTT(10)
83 tt = MyIntTT(10)
84 a = A()
84 a = A()
85 self.assertEqual(a.tt, 10)
85 self.assertEqual(a.tt, 10)
86
86
87 # Defaults are validated when the HasTraits is instantiated
87 # Defaults are validated when the HasTraits is instantiated
88 class B(HasTraits):
88 class B(HasTraits):
89 tt = MyIntTT('bad default')
89 tt = MyIntTT('bad default')
90 self.assertRaises(TraitError, B)
90 self.assertRaises(TraitError, B)
91
91
92 def test_info(self):
92 def test_info(self):
93 class A(HasTraits):
93 class A(HasTraits):
94 tt = TraitType
94 tt = TraitType
95 a = A()
95 a = A()
96 self.assertEqual(A.tt.info(), 'any value')
96 self.assertEqual(A.tt.info(), 'any value')
97
97
98 def test_error(self):
98 def test_error(self):
99 class A(HasTraits):
99 class A(HasTraits):
100 tt = TraitType
100 tt = TraitType
101 a = A()
101 a = A()
102 self.assertRaises(TraitError, A.tt.error, a, 10)
102 self.assertRaises(TraitError, A.tt.error, a, 10)
103
103
104 def test_dynamic_initializer(self):
104 def test_dynamic_initializer(self):
105 class A(HasTraits):
105 class A(HasTraits):
106 x = Int(10)
106 x = Int(10)
107 def _x_default(self):
107 def _x_default(self):
108 return 11
108 return 11
109 class B(A):
109 class B(A):
110 x = Int(20)
110 x = Int(20)
111 class C(A):
111 class C(A):
112 def _x_default(self):
112 def _x_default(self):
113 return 21
113 return 21
114
114
115 a = A()
115 a = A()
116 self.assertEqual(a._trait_values, {})
116 self.assertEqual(a._trait_values, {})
117 self.assertEqual(list(a._trait_dyn_inits.keys()), ['x'])
117 self.assertEqual(list(a._trait_dyn_inits.keys()), ['x'])
118 self.assertEqual(a.x, 11)
118 self.assertEqual(a.x, 11)
119 self.assertEqual(a._trait_values, {'x': 11})
119 self.assertEqual(a._trait_values, {'x': 11})
120 b = B()
120 b = B()
121 self.assertEqual(b._trait_values, {'x': 20})
121 self.assertEqual(b._trait_values, {'x': 20})
122 self.assertEqual(list(a._trait_dyn_inits.keys()), ['x'])
122 self.assertEqual(list(a._trait_dyn_inits.keys()), ['x'])
123 self.assertEqual(b.x, 20)
123 self.assertEqual(b.x, 20)
124 c = C()
124 c = C()
125 self.assertEqual(c._trait_values, {})
125 self.assertEqual(c._trait_values, {})
126 self.assertEqual(list(a._trait_dyn_inits.keys()), ['x'])
126 self.assertEqual(list(a._trait_dyn_inits.keys()), ['x'])
127 self.assertEqual(c.x, 21)
127 self.assertEqual(c.x, 21)
128 self.assertEqual(c._trait_values, {'x': 21})
128 self.assertEqual(c._trait_values, {'x': 21})
129 # Ensure that the base class remains unmolested when the _default
129 # Ensure that the base class remains unmolested when the _default
130 # initializer gets overridden in a subclass.
130 # initializer gets overridden in a subclass.
131 a = A()
131 a = A()
132 c = C()
132 c = C()
133 self.assertEqual(a._trait_values, {})
133 self.assertEqual(a._trait_values, {})
134 self.assertEqual(list(a._trait_dyn_inits.keys()), ['x'])
134 self.assertEqual(list(a._trait_dyn_inits.keys()), ['x'])
135 self.assertEqual(a.x, 11)
135 self.assertEqual(a.x, 11)
136 self.assertEqual(a._trait_values, {'x': 11})
136 self.assertEqual(a._trait_values, {'x': 11})
137
137
138
138
139
139
140 class TestHasTraitsMeta(TestCase):
140 class TestHasTraitsMeta(TestCase):
141
141
142 def test_metaclass(self):
142 def test_metaclass(self):
143 self.assertEqual(type(HasTraits), MetaHasTraits)
143 self.assertEqual(type(HasTraits), MetaHasTraits)
144
144
145 class A(HasTraits):
145 class A(HasTraits):
146 a = Int
146 a = Int
147
147
148 a = A()
148 a = A()
149 self.assertEqual(type(a.__class__), MetaHasTraits)
149 self.assertEqual(type(a.__class__), MetaHasTraits)
150 self.assertEqual(a.a,0)
150 self.assertEqual(a.a,0)
151 a.a = 10
151 a.a = 10
152 self.assertEqual(a.a,10)
152 self.assertEqual(a.a,10)
153
153
154 class B(HasTraits):
154 class B(HasTraits):
155 b = Int()
155 b = Int()
156
156
157 b = B()
157 b = B()
158 self.assertEqual(b.b,0)
158 self.assertEqual(b.b,0)
159 b.b = 10
159 b.b = 10
160 self.assertEqual(b.b,10)
160 self.assertEqual(b.b,10)
161
161
162 class C(HasTraits):
162 class C(HasTraits):
163 c = Int(30)
163 c = Int(30)
164
164
165 c = C()
165 c = C()
166 self.assertEqual(c.c,30)
166 self.assertEqual(c.c,30)
167 c.c = 10
167 c.c = 10
168 self.assertEqual(c.c,10)
168 self.assertEqual(c.c,10)
169
169
170 def test_this_class(self):
170 def test_this_class(self):
171 class A(HasTraits):
171 class A(HasTraits):
172 t = This()
172 t = This()
173 tt = This()
173 tt = This()
174 class B(A):
174 class B(A):
175 tt = This()
175 tt = This()
176 ttt = This()
176 ttt = This()
177 self.assertEqual(A.t.this_class, A)
177 self.assertEqual(A.t.this_class, A)
178 self.assertEqual(B.t.this_class, A)
178 self.assertEqual(B.t.this_class, A)
179 self.assertEqual(B.tt.this_class, B)
179 self.assertEqual(B.tt.this_class, B)
180 self.assertEqual(B.ttt.this_class, B)
180 self.assertEqual(B.ttt.this_class, B)
181
181
182 class TestHasTraitsNotify(TestCase):
182 class TestHasTraitsNotify(TestCase):
183
183
184 def setUp(self):
184 def setUp(self):
185 self._notify1 = []
185 self._notify1 = []
186 self._notify2 = []
186 self._notify2 = []
187
187
188 def notify1(self, name, old, new):
188 def notify1(self, name, old, new):
189 self._notify1.append((name, old, new))
189 self._notify1.append((name, old, new))
190
190
191 def notify2(self, name, old, new):
191 def notify2(self, name, old, new):
192 self._notify2.append((name, old, new))
192 self._notify2.append((name, old, new))
193
193
194 def test_notify_all(self):
194 def test_notify_all(self):
195
195
196 class A(HasTraits):
196 class A(HasTraits):
197 a = Int
197 a = Int
198 b = Float
198 b = Float
199
199
200 a = A()
200 a = A()
201 a.on_trait_change(self.notify1)
201 a.on_trait_change(self.notify1)
202 a.a = 0
202 a.a = 0
203 self.assertEqual(len(self._notify1),0)
203 self.assertEqual(len(self._notify1),0)
204 a.b = 0.0
204 a.b = 0.0
205 self.assertEqual(len(self._notify1),0)
205 self.assertEqual(len(self._notify1),0)
206 a.a = 10
206 a.a = 10
207 self.assertTrue(('a',0,10) in self._notify1)
207 self.assertTrue(('a',0,10) in self._notify1)
208 a.b = 10.0
208 a.b = 10.0
209 self.assertTrue(('b',0.0,10.0) in self._notify1)
209 self.assertTrue(('b',0.0,10.0) in self._notify1)
210 self.assertRaises(TraitError,setattr,a,'a','bad string')
210 self.assertRaises(TraitError,setattr,a,'a','bad string')
211 self.assertRaises(TraitError,setattr,a,'b','bad string')
211 self.assertRaises(TraitError,setattr,a,'b','bad string')
212 self._notify1 = []
212 self._notify1 = []
213 a.on_trait_change(self.notify1,remove=True)
213 a.on_trait_change(self.notify1,remove=True)
214 a.a = 20
214 a.a = 20
215 a.b = 20.0
215 a.b = 20.0
216 self.assertEqual(len(self._notify1),0)
216 self.assertEqual(len(self._notify1),0)
217
217
218 def test_notify_one(self):
218 def test_notify_one(self):
219
219
220 class A(HasTraits):
220 class A(HasTraits):
221 a = Int
221 a = Int
222 b = Float
222 b = Float
223
223
224 a = A()
224 a = A()
225 a.on_trait_change(self.notify1, 'a')
225 a.on_trait_change(self.notify1, 'a')
226 a.a = 0
226 a.a = 0
227 self.assertEqual(len(self._notify1),0)
227 self.assertEqual(len(self._notify1),0)
228 a.a = 10
228 a.a = 10
229 self.assertTrue(('a',0,10) in self._notify1)
229 self.assertTrue(('a',0,10) in self._notify1)
230 self.assertRaises(TraitError,setattr,a,'a','bad string')
230 self.assertRaises(TraitError,setattr,a,'a','bad string')
231
231
232 def test_subclass(self):
232 def test_subclass(self):
233
233
234 class A(HasTraits):
234 class A(HasTraits):
235 a = Int
235 a = Int
236
236
237 class B(A):
237 class B(A):
238 b = Float
238 b = Float
239
239
240 b = B()
240 b = B()
241 self.assertEqual(b.a,0)
241 self.assertEqual(b.a,0)
242 self.assertEqual(b.b,0.0)
242 self.assertEqual(b.b,0.0)
243 b.a = 100
243 b.a = 100
244 b.b = 100.0
244 b.b = 100.0
245 self.assertEqual(b.a,100)
245 self.assertEqual(b.a,100)
246 self.assertEqual(b.b,100.0)
246 self.assertEqual(b.b,100.0)
247
247
248 def test_notify_subclass(self):
248 def test_notify_subclass(self):
249
249
250 class A(HasTraits):
250 class A(HasTraits):
251 a = Int
251 a = Int
252
252
253 class B(A):
253 class B(A):
254 b = Float
254 b = Float
255
255
256 b = B()
256 b = B()
257 b.on_trait_change(self.notify1, 'a')
257 b.on_trait_change(self.notify1, 'a')
258 b.on_trait_change(self.notify2, 'b')
258 b.on_trait_change(self.notify2, 'b')
259 b.a = 0
259 b.a = 0
260 b.b = 0.0
260 b.b = 0.0
261 self.assertEqual(len(self._notify1),0)
261 self.assertEqual(len(self._notify1),0)
262 self.assertEqual(len(self._notify2),0)
262 self.assertEqual(len(self._notify2),0)
263 b.a = 10
263 b.a = 10
264 b.b = 10.0
264 b.b = 10.0
265 self.assertTrue(('a',0,10) in self._notify1)
265 self.assertTrue(('a',0,10) in self._notify1)
266 self.assertTrue(('b',0.0,10.0) in self._notify2)
266 self.assertTrue(('b',0.0,10.0) in self._notify2)
267
267
268 def test_static_notify(self):
268 def test_static_notify(self):
269
269
270 class A(HasTraits):
270 class A(HasTraits):
271 a = Int
271 a = Int
272 _notify1 = []
272 _notify1 = []
273 def _a_changed(self, name, old, new):
273 def _a_changed(self, name, old, new):
274 self._notify1.append((name, old, new))
274 self._notify1.append((name, old, new))
275
275
276 a = A()
276 a = A()
277 a.a = 0
277 a.a = 0
278 # This is broken!!!
278 # This is broken!!!
279 self.assertEqual(len(a._notify1),0)
279 self.assertEqual(len(a._notify1),0)
280 a.a = 10
280 a.a = 10
281 self.assertTrue(('a',0,10) in a._notify1)
281 self.assertTrue(('a',0,10) in a._notify1)
282
282
283 class B(A):
283 class B(A):
284 b = Float
284 b = Float
285 _notify2 = []
285 _notify2 = []
286 def _b_changed(self, name, old, new):
286 def _b_changed(self, name, old, new):
287 self._notify2.append((name, old, new))
287 self._notify2.append((name, old, new))
288
288
289 b = B()
289 b = B()
290 b.a = 10
290 b.a = 10
291 b.b = 10.0
291 b.b = 10.0
292 self.assertTrue(('a',0,10) in b._notify1)
292 self.assertTrue(('a',0,10) in b._notify1)
293 self.assertTrue(('b',0.0,10.0) in b._notify2)
293 self.assertTrue(('b',0.0,10.0) in b._notify2)
294
294
295 def test_notify_args(self):
295 def test_notify_args(self):
296
296
297 def callback0():
297 def callback0():
298 self.cb = ()
298 self.cb = ()
299 def callback1(name):
299 def callback1(name):
300 self.cb = (name,)
300 self.cb = (name,)
301 def callback2(name, new):
301 def callback2(name, new):
302 self.cb = (name, new)
302 self.cb = (name, new)
303 def callback3(name, old, new):
303 def callback3(name, old, new):
304 self.cb = (name, old, new)
304 self.cb = (name, old, new)
305
305
306 class A(HasTraits):
306 class A(HasTraits):
307 a = Int
307 a = Int
308
308
309 a = A()
309 a = A()
310 a.on_trait_change(callback0, 'a')
310 a.on_trait_change(callback0, 'a')
311 a.a = 10
311 a.a = 10
312 self.assertEqual(self.cb,())
312 self.assertEqual(self.cb,())
313 a.on_trait_change(callback0, 'a', remove=True)
313 a.on_trait_change(callback0, 'a', remove=True)
314
314
315 a.on_trait_change(callback1, 'a')
315 a.on_trait_change(callback1, 'a')
316 a.a = 100
316 a.a = 100
317 self.assertEqual(self.cb,('a',))
317 self.assertEqual(self.cb,('a',))
318 a.on_trait_change(callback1, 'a', remove=True)
318 a.on_trait_change(callback1, 'a', remove=True)
319
319
320 a.on_trait_change(callback2, 'a')
320 a.on_trait_change(callback2, 'a')
321 a.a = 1000
321 a.a = 1000
322 self.assertEqual(self.cb,('a',1000))
322 self.assertEqual(self.cb,('a',1000))
323 a.on_trait_change(callback2, 'a', remove=True)
323 a.on_trait_change(callback2, 'a', remove=True)
324
324
325 a.on_trait_change(callback3, 'a')
325 a.on_trait_change(callback3, 'a')
326 a.a = 10000
326 a.a = 10000
327 self.assertEqual(self.cb,('a',1000,10000))
327 self.assertEqual(self.cb,('a',1000,10000))
328 a.on_trait_change(callback3, 'a', remove=True)
328 a.on_trait_change(callback3, 'a', remove=True)
329
329
330 self.assertEqual(len(a._trait_notifiers['a']),0)
330 self.assertEqual(len(a._trait_notifiers['a']),0)
331
331
332 def test_notify_only_once(self):
332 def test_notify_only_once(self):
333
333
334 class A(HasTraits):
334 class A(HasTraits):
335 listen_to = ['a']
335 listen_to = ['a']
336
336
337 a = Int(0)
337 a = Int(0)
338 b = 0
338 b = 0
339
339
340 def __init__(self, **kwargs):
340 def __init__(self, **kwargs):
341 super(A, self).__init__(**kwargs)
341 super(A, self).__init__(**kwargs)
342 self.on_trait_change(self.listener1, ['a'])
342 self.on_trait_change(self.listener1, ['a'])
343
343
344 def listener1(self, name, old, new):
344 def listener1(self, name, old, new):
345 self.b += 1
345 self.b += 1
346
346
347 class B(A):
347 class B(A):
348
348
349 c = 0
349 c = 0
350 d = 0
350 d = 0
351
351
352 def __init__(self, **kwargs):
352 def __init__(self, **kwargs):
353 super(B, self).__init__(**kwargs)
353 super(B, self).__init__(**kwargs)
354 self.on_trait_change(self.listener2)
354 self.on_trait_change(self.listener2)
355
355
356 def listener2(self, name, old, new):
356 def listener2(self, name, old, new):
357 self.c += 1
357 self.c += 1
358
358
359 def _a_changed(self, name, old, new):
359 def _a_changed(self, name, old, new):
360 self.d += 1
360 self.d += 1
361
361
362 b = B()
362 b = B()
363 b.a += 1
363 b.a += 1
364 self.assertEqual(b.b, b.c)
364 self.assertEqual(b.b, b.c)
365 self.assertEqual(b.b, b.d)
365 self.assertEqual(b.b, b.d)
366 b.a += 1
366 b.a += 1
367 self.assertEqual(b.b, b.c)
367 self.assertEqual(b.b, b.c)
368 self.assertEqual(b.b, b.d)
368 self.assertEqual(b.b, b.d)
369
369
370
370
371 class TestHasTraits(TestCase):
371 class TestHasTraits(TestCase):
372
372
373 def test_trait_names(self):
373 def test_trait_names(self):
374 class A(HasTraits):
374 class A(HasTraits):
375 i = Int
375 i = Int
376 f = Float
376 f = Float
377 a = A()
377 a = A()
378 self.assertEqual(sorted(a.trait_names()),['f','i'])
378 self.assertEqual(sorted(a.trait_names()),['f','i'])
379 self.assertEqual(sorted(A.class_trait_names()),['f','i'])
379 self.assertEqual(sorted(A.class_trait_names()),['f','i'])
380
380
381 def test_trait_metadata(self):
381 def test_trait_metadata(self):
382 class A(HasTraits):
382 class A(HasTraits):
383 i = Int(config_key='MY_VALUE')
383 i = Int(config_key='MY_VALUE')
384 a = A()
384 a = A()
385 self.assertEqual(a.trait_metadata('i','config_key'), 'MY_VALUE')
385 self.assertEqual(a.trait_metadata('i','config_key'), 'MY_VALUE')
386
386
387 def test_trait_metadata_default(self):
387 def test_trait_metadata_default(self):
388 class A(HasTraits):
388 class A(HasTraits):
389 i = Int()
389 i = Int()
390 a = A()
390 a = A()
391 self.assertEqual(a.trait_metadata('i', 'config_key'), None)
391 self.assertEqual(a.trait_metadata('i', 'config_key'), None)
392 self.assertEqual(a.trait_metadata('i', 'config_key', 'default'), 'default')
392 self.assertEqual(a.trait_metadata('i', 'config_key', 'default'), 'default')
393
393
394 def test_traits(self):
394 def test_traits(self):
395 class A(HasTraits):
395 class A(HasTraits):
396 i = Int
396 i = Int
397 f = Float
397 f = Float
398 a = A()
398 a = A()
399 self.assertEqual(a.traits(), dict(i=A.i, f=A.f))
399 self.assertEqual(a.traits(), dict(i=A.i, f=A.f))
400 self.assertEqual(A.class_traits(), dict(i=A.i, f=A.f))
400 self.assertEqual(A.class_traits(), dict(i=A.i, f=A.f))
401
401
402 def test_traits_metadata(self):
402 def test_traits_metadata(self):
403 class A(HasTraits):
403 class A(HasTraits):
404 i = Int(config_key='VALUE1', other_thing='VALUE2')
404 i = Int(config_key='VALUE1', other_thing='VALUE2')
405 f = Float(config_key='VALUE3', other_thing='VALUE2')
405 f = Float(config_key='VALUE3', other_thing='VALUE2')
406 j = Int(0)
406 j = Int(0)
407 a = A()
407 a = A()
408 self.assertEqual(a.traits(), dict(i=A.i, f=A.f, j=A.j))
408 self.assertEqual(a.traits(), dict(i=A.i, f=A.f, j=A.j))
409 traits = a.traits(config_key='VALUE1', other_thing='VALUE2')
409 traits = a.traits(config_key='VALUE1', other_thing='VALUE2')
410 self.assertEqual(traits, dict(i=A.i))
410 self.assertEqual(traits, dict(i=A.i))
411
411
412 # This passes, but it shouldn't because I am replicating a bug in
412 # This passes, but it shouldn't because I am replicating a bug in
413 # traits.
413 # traits.
414 traits = a.traits(config_key=lambda v: True)
414 traits = a.traits(config_key=lambda v: True)
415 self.assertEqual(traits, dict(i=A.i, f=A.f, j=A.j))
415 self.assertEqual(traits, dict(i=A.i, f=A.f, j=A.j))
416
416
417 def test_init(self):
417 def test_init(self):
418 class A(HasTraits):
418 class A(HasTraits):
419 i = Int()
419 i = Int()
420 x = Float()
420 x = Float()
421 a = A(i=1, x=10.0)
421 a = A(i=1, x=10.0)
422 self.assertEqual(a.i, 1)
422 self.assertEqual(a.i, 1)
423 self.assertEqual(a.x, 10.0)
423 self.assertEqual(a.x, 10.0)
424
424
425 def test_positional_args(self):
425 def test_positional_args(self):
426 class A(HasTraits):
426 class A(HasTraits):
427 i = Int(0)
427 i = Int(0)
428 def __init__(self, i):
428 def __init__(self, i):
429 super(A, self).__init__()
429 super(A, self).__init__()
430 self.i = i
430 self.i = i
431
431
432 a = A(5)
432 a = A(5)
433 self.assertEqual(a.i, 5)
433 self.assertEqual(a.i, 5)
434 # should raise TypeError if no positional arg given
434 # should raise TypeError if no positional arg given
435 self.assertRaises(TypeError, A)
435 self.assertRaises(TypeError, A)
436
436
437 #-----------------------------------------------------------------------------
437 #-----------------------------------------------------------------------------
438 # Tests for specific trait types
438 # Tests for specific trait types
439 #-----------------------------------------------------------------------------
439 #-----------------------------------------------------------------------------
440
440
441
441
442 class TestType(TestCase):
442 class TestType(TestCase):
443
443
444 def test_default(self):
444 def test_default(self):
445
445
446 class B(object): pass
446 class B(object): pass
447 class A(HasTraits):
447 class A(HasTraits):
448 klass = Type
448 klass = Type
449
449
450 a = A()
450 a = A()
451 self.assertEqual(a.klass, None)
451 self.assertEqual(a.klass, None)
452
452
453 a.klass = B
453 a.klass = B
454 self.assertEqual(a.klass, B)
454 self.assertEqual(a.klass, B)
455 self.assertRaises(TraitError, setattr, a, 'klass', 10)
455 self.assertRaises(TraitError, setattr, a, 'klass', 10)
456
456
457 def test_value(self):
457 def test_value(self):
458
458
459 class B(object): pass
459 class B(object): pass
460 class C(object): pass
460 class C(object): pass
461 class A(HasTraits):
461 class A(HasTraits):
462 klass = Type(B)
462 klass = Type(B)
463
463
464 a = A()
464 a = A()
465 self.assertEqual(a.klass, B)
465 self.assertEqual(a.klass, B)
466 self.assertRaises(TraitError, setattr, a, 'klass', C)
466 self.assertRaises(TraitError, setattr, a, 'klass', C)
467 self.assertRaises(TraitError, setattr, a, 'klass', object)
467 self.assertRaises(TraitError, setattr, a, 'klass', object)
468 a.klass = B
468 a.klass = B
469
469
470 def test_allow_none(self):
470 def test_allow_none(self):
471
471
472 class B(object): pass
472 class B(object): pass
473 class C(B): pass
473 class C(B): pass
474 class A(HasTraits):
474 class A(HasTraits):
475 klass = Type(B, allow_none=False)
475 klass = Type(B, allow_none=False)
476
476
477 a = A()
477 a = A()
478 self.assertEqual(a.klass, B)
478 self.assertEqual(a.klass, B)
479 self.assertRaises(TraitError, setattr, a, 'klass', None)
479 self.assertRaises(TraitError, setattr, a, 'klass', None)
480 a.klass = C
480 a.klass = C
481 self.assertEqual(a.klass, C)
481 self.assertEqual(a.klass, C)
482
482
483 def test_validate_klass(self):
483 def test_validate_klass(self):
484
484
485 class A(HasTraits):
485 class A(HasTraits):
486 klass = Type('no strings allowed')
486 klass = Type('no strings allowed')
487
487
488 self.assertRaises(ImportError, A)
488 self.assertRaises(ImportError, A)
489
489
490 class A(HasTraits):
490 class A(HasTraits):
491 klass = Type('rub.adub.Duck')
491 klass = Type('rub.adub.Duck')
492
492
493 self.assertRaises(ImportError, A)
493 self.assertRaises(ImportError, A)
494
494
495 def test_validate_default(self):
495 def test_validate_default(self):
496
496
497 class B(object): pass
497 class B(object): pass
498 class A(HasTraits):
498 class A(HasTraits):
499 klass = Type('bad default', B)
499 klass = Type('bad default', B)
500
500
501 self.assertRaises(ImportError, A)
501 self.assertRaises(ImportError, A)
502
502
503 class C(HasTraits):
503 class C(HasTraits):
504 klass = Type(None, B, allow_none=False)
504 klass = Type(None, B, allow_none=False)
505
505
506 self.assertRaises(TraitError, C)
506 self.assertRaises(TraitError, C)
507
507
508 def test_str_klass(self):
508 def test_str_klass(self):
509
509
510 class A(HasTraits):
510 class A(HasTraits):
511 klass = Type('IPython.utils.ipstruct.Struct')
511 klass = Type('IPython.utils.ipstruct.Struct')
512
512
513 from IPython.utils.ipstruct import Struct
513 from IPython.utils.ipstruct import Struct
514 a = A()
514 a = A()
515 a.klass = Struct
515 a.klass = Struct
516 self.assertEqual(a.klass, Struct)
516 self.assertEqual(a.klass, Struct)
517
517
518 self.assertRaises(TraitError, setattr, a, 'klass', 10)
518 self.assertRaises(TraitError, setattr, a, 'klass', 10)
519
519
520 def test_set_str_klass(self):
520 def test_set_str_klass(self):
521
521
522 class A(HasTraits):
522 class A(HasTraits):
523 klass = Type()
523 klass = Type()
524
524
525 a = A(klass='IPython.utils.ipstruct.Struct')
525 a = A(klass='IPython.utils.ipstruct.Struct')
526 from IPython.utils.ipstruct import Struct
526 from IPython.utils.ipstruct import Struct
527 self.assertEqual(a.klass, Struct)
527 self.assertEqual(a.klass, Struct)
528
528
529 class TestInstance(TestCase):
529 class TestInstance(TestCase):
530
530
531 def test_basic(self):
531 def test_basic(self):
532 class Foo(object): pass
532 class Foo(object): pass
533 class Bar(Foo): pass
533 class Bar(Foo): pass
534 class Bah(object): pass
534 class Bah(object): pass
535
535
536 class A(HasTraits):
536 class A(HasTraits):
537 inst = Instance(Foo)
537 inst = Instance(Foo)
538
538
539 a = A()
539 a = A()
540 self.assertTrue(a.inst is None)
540 self.assertTrue(a.inst is None)
541 a.inst = Foo()
541 a.inst = Foo()
542 self.assertTrue(isinstance(a.inst, Foo))
542 self.assertTrue(isinstance(a.inst, Foo))
543 a.inst = Bar()
543 a.inst = Bar()
544 self.assertTrue(isinstance(a.inst, Foo))
544 self.assertTrue(isinstance(a.inst, Foo))
545 self.assertRaises(TraitError, setattr, a, 'inst', Foo)
545 self.assertRaises(TraitError, setattr, a, 'inst', Foo)
546 self.assertRaises(TraitError, setattr, a, 'inst', Bar)
546 self.assertRaises(TraitError, setattr, a, 'inst', Bar)
547 self.assertRaises(TraitError, setattr, a, 'inst', Bah())
547 self.assertRaises(TraitError, setattr, a, 'inst', Bah())
548
548
549 def test_default_klass(self):
549 def test_default_klass(self):
550 class Foo(object): pass
550 class Foo(object): pass
551 class Bar(Foo): pass
551 class Bar(Foo): pass
552 class Bah(object): pass
552 class Bah(object): pass
553
553
554 class FooInstance(Instance):
554 class FooInstance(Instance):
555 klass = Foo
555 klass = Foo
556
556
557 class A(HasTraits):
557 class A(HasTraits):
558 inst = FooInstance()
558 inst = FooInstance()
559
559
560 a = A()
560 a = A()
561 self.assertTrue(a.inst is None)
561 self.assertTrue(a.inst is None)
562 a.inst = Foo()
562 a.inst = Foo()
563 self.assertTrue(isinstance(a.inst, Foo))
563 self.assertTrue(isinstance(a.inst, Foo))
564 a.inst = Bar()
564 a.inst = Bar()
565 self.assertTrue(isinstance(a.inst, Foo))
565 self.assertTrue(isinstance(a.inst, Foo))
566 self.assertRaises(TraitError, setattr, a, 'inst', Foo)
566 self.assertRaises(TraitError, setattr, a, 'inst', Foo)
567 self.assertRaises(TraitError, setattr, a, 'inst', Bar)
567 self.assertRaises(TraitError, setattr, a, 'inst', Bar)
568 self.assertRaises(TraitError, setattr, a, 'inst', Bah())
568 self.assertRaises(TraitError, setattr, a, 'inst', Bah())
569
569
570 def test_unique_default_value(self):
570 def test_unique_default_value(self):
571 class Foo(object): pass
571 class Foo(object): pass
572 class A(HasTraits):
572 class A(HasTraits):
573 inst = Instance(Foo,(),{})
573 inst = Instance(Foo,(),{})
574
574
575 a = A()
575 a = A()
576 b = A()
576 b = A()
577 self.assertTrue(a.inst is not b.inst)
577 self.assertTrue(a.inst is not b.inst)
578
578
579 def test_args_kw(self):
579 def test_args_kw(self):
580 class Foo(object):
580 class Foo(object):
581 def __init__(self, c): self.c = c
581 def __init__(self, c): self.c = c
582 class Bar(object): pass
582 class Bar(object): pass
583 class Bah(object):
583 class Bah(object):
584 def __init__(self, c, d):
584 def __init__(self, c, d):
585 self.c = c; self.d = d
585 self.c = c; self.d = d
586
586
587 class A(HasTraits):
587 class A(HasTraits):
588 inst = Instance(Foo, (10,))
588 inst = Instance(Foo, (10,))
589 a = A()
589 a = A()
590 self.assertEqual(a.inst.c, 10)
590 self.assertEqual(a.inst.c, 10)
591
591
592 class B(HasTraits):
592 class B(HasTraits):
593 inst = Instance(Bah, args=(10,), kw=dict(d=20))
593 inst = Instance(Bah, args=(10,), kw=dict(d=20))
594 b = B()
594 b = B()
595 self.assertEqual(b.inst.c, 10)
595 self.assertEqual(b.inst.c, 10)
596 self.assertEqual(b.inst.d, 20)
596 self.assertEqual(b.inst.d, 20)
597
597
598 class C(HasTraits):
598 class C(HasTraits):
599 inst = Instance(Foo)
599 inst = Instance(Foo)
600 c = C()
600 c = C()
601 self.assertTrue(c.inst is None)
601 self.assertTrue(c.inst is None)
602
602
603 def test_bad_default(self):
603 def test_bad_default(self):
604 class Foo(object): pass
604 class Foo(object): pass
605
605
606 class A(HasTraits):
606 class A(HasTraits):
607 inst = Instance(Foo, allow_none=False)
607 inst = Instance(Foo, allow_none=False)
608
608
609 self.assertRaises(TraitError, A)
609 self.assertRaises(TraitError, A)
610
610
611 def test_instance(self):
611 def test_instance(self):
612 class Foo(object): pass
612 class Foo(object): pass
613
613
614 def inner():
614 def inner():
615 class A(HasTraits):
615 class A(HasTraits):
616 inst = Instance(Foo())
616 inst = Instance(Foo())
617
617
618 self.assertRaises(TraitError, inner)
618 self.assertRaises(TraitError, inner)
619
619
620
620
621 class TestThis(TestCase):
621 class TestThis(TestCase):
622
622
623 def test_this_class(self):
623 def test_this_class(self):
624 class Foo(HasTraits):
624 class Foo(HasTraits):
625 this = This
625 this = This
626
626
627 f = Foo()
627 f = Foo()
628 self.assertEqual(f.this, None)
628 self.assertEqual(f.this, None)
629 g = Foo()
629 g = Foo()
630 f.this = g
630 f.this = g
631 self.assertEqual(f.this, g)
631 self.assertEqual(f.this, g)
632 self.assertRaises(TraitError, setattr, f, 'this', 10)
632 self.assertRaises(TraitError, setattr, f, 'this', 10)
633
633
634 def test_this_inst(self):
634 def test_this_inst(self):
635 class Foo(HasTraits):
635 class Foo(HasTraits):
636 this = This()
636 this = This()
637
637
638 f = Foo()
638 f = Foo()
639 f.this = Foo()
639 f.this = Foo()
640 self.assertTrue(isinstance(f.this, Foo))
640 self.assertTrue(isinstance(f.this, Foo))
641
641
642 def test_subclass(self):
642 def test_subclass(self):
643 class Foo(HasTraits):
643 class Foo(HasTraits):
644 t = This()
644 t = This()
645 class Bar(Foo):
645 class Bar(Foo):
646 pass
646 pass
647 f = Foo()
647 f = Foo()
648 b = Bar()
648 b = Bar()
649 f.t = b
649 f.t = b
650 b.t = f
650 b.t = f
651 self.assertEqual(f.t, b)
651 self.assertEqual(f.t, b)
652 self.assertEqual(b.t, f)
652 self.assertEqual(b.t, f)
653
653
654 def test_subclass_override(self):
654 def test_subclass_override(self):
655 class Foo(HasTraits):
655 class Foo(HasTraits):
656 t = This()
656 t = This()
657 class Bar(Foo):
657 class Bar(Foo):
658 t = This()
658 t = This()
659 f = Foo()
659 f = Foo()
660 b = Bar()
660 b = Bar()
661 f.t = b
661 f.t = b
662 self.assertEqual(f.t, b)
662 self.assertEqual(f.t, b)
663 self.assertRaises(TraitError, setattr, b, 't', f)
663 self.assertRaises(TraitError, setattr, b, 't', f)
664
664
665 def test_this_in_container(self):
665 def test_this_in_container(self):
666
666
667 class Tree(HasTraits):
667 class Tree(HasTraits):
668 value = Unicode()
668 value = Unicode()
669 leaves = List(This())
669 leaves = List(This())
670
670
671 tree = Tree(
671 tree = Tree(
672 value='foo',
672 value='foo',
673 leaves=[Tree('bar'), Tree('buzz')]
673 leaves=[Tree('bar'), Tree('buzz')]
674 )
674 )
675
675
676 with self.assertRaises(TraitError):
676 with self.assertRaises(TraitError):
677 tree.leaves = [1, 2]
677 tree.leaves = [1, 2]
678
678
679 class TraitTestBase(TestCase):
679 class TraitTestBase(TestCase):
680 """A best testing class for basic trait types."""
680 """A best testing class for basic trait types."""
681
681
682 def assign(self, value):
682 def assign(self, value):
683 self.obj.value = value
683 self.obj.value = value
684
684
685 def coerce(self, value):
685 def coerce(self, value):
686 return value
686 return value
687
687
688 def test_good_values(self):
688 def test_good_values(self):
689 if hasattr(self, '_good_values'):
689 if hasattr(self, '_good_values'):
690 for value in self._good_values:
690 for value in self._good_values:
691 self.assign(value)
691 self.assign(value)
692 self.assertEqual(self.obj.value, self.coerce(value))
692 self.assertEqual(self.obj.value, self.coerce(value))
693
693
694 def test_bad_values(self):
694 def test_bad_values(self):
695 if hasattr(self, '_bad_values'):
695 if hasattr(self, '_bad_values'):
696 for value in self._bad_values:
696 for value in self._bad_values:
697 try:
697 try:
698 self.assertRaises(TraitError, self.assign, value)
698 self.assertRaises(TraitError, self.assign, value)
699 except AssertionError:
699 except AssertionError:
700 assert False, value
700 assert False, value
701
701
702 def test_default_value(self):
702 def test_default_value(self):
703 if hasattr(self, '_default_value'):
703 if hasattr(self, '_default_value'):
704 self.assertEqual(self._default_value, self.obj.value)
704 self.assertEqual(self._default_value, self.obj.value)
705
705
706 def test_allow_none(self):
706 def test_allow_none(self):
707 if (hasattr(self, '_bad_values') and hasattr(self, '_good_values') and
707 if (hasattr(self, '_bad_values') and hasattr(self, '_good_values') and
708 None in self._bad_values):
708 None in self._bad_values):
709 trait=self.obj.traits()['value']
709 trait=self.obj.traits()['value']
710 try:
710 try:
711 trait.allow_none = True
711 trait.allow_none = True
712 self._bad_values.remove(None)
712 self._bad_values.remove(None)
713 #skip coerce. Allow None casts None to None.
713 #skip coerce. Allow None casts None to None.
714 self.assign(None)
714 self.assign(None)
715 self.assertEqual(self.obj.value,None)
715 self.assertEqual(self.obj.value,None)
716 self.test_good_values()
716 self.test_good_values()
717 self.test_bad_values()
717 self.test_bad_values()
718 finally:
718 finally:
719 #tear down
719 #tear down
720 trait.allow_none = False
720 trait.allow_none = False
721 self._bad_values.append(None)
721 self._bad_values.append(None)
722
722
723 def tearDown(self):
723 def tearDown(self):
724 # restore default value after tests, if set
724 # restore default value after tests, if set
725 if hasattr(self, '_default_value'):
725 if hasattr(self, '_default_value'):
726 self.obj.value = self._default_value
726 self.obj.value = self._default_value
727
727
728
728
729 class AnyTrait(HasTraits):
729 class AnyTrait(HasTraits):
730
730
731 value = Any
731 value = Any
732
732
733 class AnyTraitTest(TraitTestBase):
733 class AnyTraitTest(TraitTestBase):
734
734
735 obj = AnyTrait()
735 obj = AnyTrait()
736
736
737 _default_value = None
737 _default_value = None
738 _good_values = [10.0, 'ten', u'ten', [10], {'ten': 10},(10,), None, 1j]
738 _good_values = [10.0, 'ten', u'ten', [10], {'ten': 10},(10,), None, 1j]
739 _bad_values = []
739 _bad_values = []
740
740
741 class UnionTrait(HasTraits):
741 class UnionTrait(HasTraits):
742
742
743 value = Union([Type(), Bool()])
743 value = Union([Type(), Bool()])
744
744
745 class UnionTraitTest(TraitTestBase):
745 class UnionTraitTest(TraitTestBase):
746
746
747 obj = UnionTrait(value='IPython.utils.ipstruct.Struct')
747 obj = UnionTrait(value='IPython.utils.ipstruct.Struct')
748 _good_values = [int, float, True]
748 _good_values = [int, float, True]
749 _bad_values = [[], (0,), 1j]
749 _bad_values = [[], (0,), 1j]
750
750
751 class OrTrait(HasTraits):
751 class OrTrait(HasTraits):
752
752
753 value = Bool() | Unicode()
753 value = Bool() | Unicode()
754
754
755 class OrTraitTest(TraitTestBase):
755 class OrTraitTest(TraitTestBase):
756
756
757 obj = OrTrait()
757 obj = OrTrait()
758 _good_values = [True, False, 'ten']
758 _good_values = [True, False, 'ten']
759 _bad_values = [[], (0,), 1j]
759 _bad_values = [[], (0,), 1j]
760
760
761 class IntTrait(HasTraits):
761 class IntTrait(HasTraits):
762
762
763 value = Int(99)
763 value = Int(99)
764
764
765 class TestInt(TraitTestBase):
765 class TestInt(TraitTestBase):
766
766
767 obj = IntTrait()
767 obj = IntTrait()
768 _default_value = 99
768 _default_value = 99
769 _good_values = [10, -10]
769 _good_values = [10, -10]
770 _bad_values = ['ten', u'ten', [10], {'ten': 10},(10,), None, 1j,
770 _bad_values = ['ten', u'ten', [10], {'ten': 10},(10,), None, 1j,
771 10.1, -10.1, '10L', '-10L', '10.1', '-10.1', u'10L',
771 10.1, -10.1, '10L', '-10L', '10.1', '-10.1', u'10L',
772 u'-10L', u'10.1', u'-10.1', '10', '-10', u'10', u'-10']
772 u'-10L', u'10.1', u'-10.1', '10', '-10', u'10', u'-10']
773 if not py3compat.PY3:
773 if not py3compat.PY3:
774 _bad_values.extend([long(10), long(-10), 10*sys.maxint, -10*sys.maxint])
774 _bad_values.extend([long(10), long(-10), 10*sys.maxint, -10*sys.maxint])
775
775
776
776
777 class LongTrait(HasTraits):
777 class LongTrait(HasTraits):
778
778
779 value = Long(99 if py3compat.PY3 else long(99))
779 value = Long(99 if py3compat.PY3 else long(99))
780
780
781 class TestLong(TraitTestBase):
781 class TestLong(TraitTestBase):
782
782
783 obj = LongTrait()
783 obj = LongTrait()
784
784
785 _default_value = 99 if py3compat.PY3 else long(99)
785 _default_value = 99 if py3compat.PY3 else long(99)
786 _good_values = [10, -10]
786 _good_values = [10, -10]
787 _bad_values = ['ten', u'ten', [10], {'ten': 10},(10,),
787 _bad_values = ['ten', u'ten', [10], {'ten': 10},(10,),
788 None, 1j, 10.1, -10.1, '10', '-10', '10L', '-10L', '10.1',
788 None, 1j, 10.1, -10.1, '10', '-10', '10L', '-10L', '10.1',
789 '-10.1', u'10', u'-10', u'10L', u'-10L', u'10.1',
789 '-10.1', u'10', u'-10', u'10L', u'-10L', u'10.1',
790 u'-10.1']
790 u'-10.1']
791 if not py3compat.PY3:
791 if not py3compat.PY3:
792 # maxint undefined on py3, because int == long
792 # maxint undefined on py3, because int == long
793 _good_values.extend([long(10), long(-10), 10*sys.maxint, -10*sys.maxint])
793 _good_values.extend([long(10), long(-10), 10*sys.maxint, -10*sys.maxint])
794 _bad_values.extend([[long(10)], (long(10),)])
794 _bad_values.extend([[long(10)], (long(10),)])
795
795
796 @skipif(py3compat.PY3, "not relevant on py3")
796 @skipif(py3compat.PY3, "not relevant on py3")
797 def test_cast_small(self):
797 def test_cast_small(self):
798 """Long casts ints to long"""
798 """Long casts ints to long"""
799 self.obj.value = 10
799 self.obj.value = 10
800 self.assertEqual(type(self.obj.value), long)
800 self.assertEqual(type(self.obj.value), long)
801
801
802
802
803 class IntegerTrait(HasTraits):
803 class IntegerTrait(HasTraits):
804 value = Integer(1)
804 value = Integer(1)
805
805
806 class TestInteger(TestLong):
806 class TestInteger(TestLong):
807 obj = IntegerTrait()
807 obj = IntegerTrait()
808 _default_value = 1
808 _default_value = 1
809
809
810 def coerce(self, n):
810 def coerce(self, n):
811 return int(n)
811 return int(n)
812
812
813 @skipif(py3compat.PY3, "not relevant on py3")
813 @skipif(py3compat.PY3, "not relevant on py3")
814 def test_cast_small(self):
814 def test_cast_small(self):
815 """Integer casts small longs to int"""
815 """Integer casts small longs to int"""
816 if py3compat.PY3:
816 if py3compat.PY3:
817 raise SkipTest("not relevant on py3")
817 raise SkipTest("not relevant on py3")
818
818
819 self.obj.value = long(100)
819 self.obj.value = long(100)
820 self.assertEqual(type(self.obj.value), int)
820 self.assertEqual(type(self.obj.value), int)
821
821
822
822
823 class FloatTrait(HasTraits):
823 class FloatTrait(HasTraits):
824
824
825 value = Float(99.0)
825 value = Float(99.0)
826
826
827 class TestFloat(TraitTestBase):
827 class TestFloat(TraitTestBase):
828
828
829 obj = FloatTrait()
829 obj = FloatTrait()
830
830
831 _default_value = 99.0
831 _default_value = 99.0
832 _good_values = [10, -10, 10.1, -10.1]
832 _good_values = [10, -10, 10.1, -10.1]
833 _bad_values = ['ten', u'ten', [10], {'ten': 10},(10,), None,
833 _bad_values = ['ten', u'ten', [10], {'ten': 10},(10,), None,
834 1j, '10', '-10', '10L', '-10L', '10.1', '-10.1', u'10',
834 1j, '10', '-10', '10L', '-10L', '10.1', '-10.1', u'10',
835 u'-10', u'10L', u'-10L', u'10.1', u'-10.1']
835 u'-10', u'10L', u'-10L', u'10.1', u'-10.1']
836 if not py3compat.PY3:
836 if not py3compat.PY3:
837 _bad_values.extend([long(10), long(-10)])
837 _bad_values.extend([long(10), long(-10)])
838
838
839
839
840 class ComplexTrait(HasTraits):
840 class ComplexTrait(HasTraits):
841
841
842 value = Complex(99.0-99.0j)
842 value = Complex(99.0-99.0j)
843
843
844 class TestComplex(TraitTestBase):
844 class TestComplex(TraitTestBase):
845
845
846 obj = ComplexTrait()
846 obj = ComplexTrait()
847
847
848 _default_value = 99.0-99.0j
848 _default_value = 99.0-99.0j
849 _good_values = [10, -10, 10.1, -10.1, 10j, 10+10j, 10-10j,
849 _good_values = [10, -10, 10.1, -10.1, 10j, 10+10j, 10-10j,
850 10.1j, 10.1+10.1j, 10.1-10.1j]
850 10.1j, 10.1+10.1j, 10.1-10.1j]
851 _bad_values = [u'10L', u'-10L', 'ten', [10], {'ten': 10},(10,), None]
851 _bad_values = [u'10L', u'-10L', 'ten', [10], {'ten': 10},(10,), None]
852 if not py3compat.PY3:
852 if not py3compat.PY3:
853 _bad_values.extend([long(10), long(-10)])
853 _bad_values.extend([long(10), long(-10)])
854
854
855
855
856 class BytesTrait(HasTraits):
856 class BytesTrait(HasTraits):
857
857
858 value = Bytes(b'string')
858 value = Bytes(b'string')
859
859
860 class TestBytes(TraitTestBase):
860 class TestBytes(TraitTestBase):
861
861
862 obj = BytesTrait()
862 obj = BytesTrait()
863
863
864 _default_value = b'string'
864 _default_value = b'string'
865 _good_values = [b'10', b'-10', b'10L',
865 _good_values = [b'10', b'-10', b'10L',
866 b'-10L', b'10.1', b'-10.1', b'string']
866 b'-10L', b'10.1', b'-10.1', b'string']
867 _bad_values = [10, -10, 10.1, -10.1, 1j, [10],
867 _bad_values = [10, -10, 10.1, -10.1, 1j, [10],
868 ['ten'],{'ten': 10},(10,), None, u'string']
868 ['ten'],{'ten': 10},(10,), None, u'string']
869 if not py3compat.PY3:
869 if not py3compat.PY3:
870 _bad_values.extend([long(10), long(-10)])
870 _bad_values.extend([long(10), long(-10)])
871
871
872
872
873 class UnicodeTrait(HasTraits):
873 class UnicodeTrait(HasTraits):
874
874
875 value = Unicode(u'unicode')
875 value = Unicode(u'unicode')
876
876
877 class TestUnicode(TraitTestBase):
877 class TestUnicode(TraitTestBase):
878
878
879 obj = UnicodeTrait()
879 obj = UnicodeTrait()
880
880
881 _default_value = u'unicode'
881 _default_value = u'unicode'
882 _good_values = ['10', '-10', '10L', '-10L', '10.1',
882 _good_values = ['10', '-10', '10L', '-10L', '10.1',
883 '-10.1', '', u'', 'string', u'string', u"€"]
883 '-10.1', '', u'', 'string', u'string', u"€"]
884 _bad_values = [10, -10, 10.1, -10.1, 1j,
884 _bad_values = [10, -10, 10.1, -10.1, 1j,
885 [10], ['ten'], [u'ten'], {'ten': 10},(10,), None]
885 [10], ['ten'], [u'ten'], {'ten': 10},(10,), None]
886 if not py3compat.PY3:
886 if not py3compat.PY3:
887 _bad_values.extend([long(10), long(-10)])
887 _bad_values.extend([long(10), long(-10)])
888
888
889
889
890 class ObjectNameTrait(HasTraits):
890 class ObjectNameTrait(HasTraits):
891 value = ObjectName("abc")
891 value = ObjectName("abc")
892
892
893 class TestObjectName(TraitTestBase):
893 class TestObjectName(TraitTestBase):
894 obj = ObjectNameTrait()
894 obj = ObjectNameTrait()
895
895
896 _default_value = "abc"
896 _default_value = "abc"
897 _good_values = ["a", "gh", "g9", "g_", "_G", u"a345_"]
897 _good_values = ["a", "gh", "g9", "g_", "_G", u"a345_"]
898 _bad_values = [1, "", u"€", "9g", "!", "#abc", "aj@", "a.b", "a()", "a[0]",
898 _bad_values = [1, "", u"€", "9g", "!", "#abc", "aj@", "a.b", "a()", "a[0]",
899 None, object(), object]
899 None, object(), object]
900 if sys.version_info[0] < 3:
900 if sys.version_info[0] < 3:
901 _bad_values.append(u"ΓΎ")
901 _bad_values.append(u"ΓΎ")
902 else:
902 else:
903 _good_values.append(u"ΓΎ") # ΓΎ=1 is valid in Python 3 (PEP 3131).
903 _good_values.append(u"ΓΎ") # ΓΎ=1 is valid in Python 3 (PEP 3131).
904
904
905
905
906 class DottedObjectNameTrait(HasTraits):
906 class DottedObjectNameTrait(HasTraits):
907 value = DottedObjectName("a.b")
907 value = DottedObjectName("a.b")
908
908
909 class TestDottedObjectName(TraitTestBase):
909 class TestDottedObjectName(TraitTestBase):
910 obj = DottedObjectNameTrait()
910 obj = DottedObjectNameTrait()
911
911
912 _default_value = "a.b"
912 _default_value = "a.b"
913 _good_values = ["A", "y.t", "y765.__repr__", "os.path.join", u"os.path.join"]
913 _good_values = ["A", "y.t", "y765.__repr__", "os.path.join", u"os.path.join"]
914 _bad_values = [1, u"abc.€", "_.@", ".", ".abc", "abc.", ".abc.", None]
914 _bad_values = [1, u"abc.€", "_.@", ".", ".abc", "abc.", ".abc.", None]
915 if sys.version_info[0] < 3:
915 if sys.version_info[0] < 3:
916 _bad_values.append(u"t.ΓΎ")
916 _bad_values.append(u"t.ΓΎ")
917 else:
917 else:
918 _good_values.append(u"t.ΓΎ")
918 _good_values.append(u"t.ΓΎ")
919
919
920
920
921 class ColorTrait(HasTraits):
922 value = Color("black")
923
924 class TestColor(TraitTestBase):
925 obj = ColorTrait()
926
927 _good_values = ["blue", "#AA0", "#FFFFFF"]
928 _bad_values = ["vanilla", "blues"]
929
930
931 class TCPAddressTrait(HasTraits):
921 class TCPAddressTrait(HasTraits):
932 value = TCPAddress()
922 value = TCPAddress()
933
923
934 class TestTCPAddress(TraitTestBase):
924 class TestTCPAddress(TraitTestBase):
935
925
936 obj = TCPAddressTrait()
926 obj = TCPAddressTrait()
937
927
938 _default_value = ('127.0.0.1',0)
928 _default_value = ('127.0.0.1',0)
939 _good_values = [('localhost',0),('192.168.0.1',1000),('www.google.com',80)]
929 _good_values = [('localhost',0),('192.168.0.1',1000),('www.google.com',80)]
940 _bad_values = [(0,0),('localhost',10.0),('localhost',-1), None]
930 _bad_values = [(0,0),('localhost',10.0),('localhost',-1), None]
941
931
942 class ListTrait(HasTraits):
932 class ListTrait(HasTraits):
943
933
944 value = List(Int)
934 value = List(Int)
945
935
946 class TestList(TraitTestBase):
936 class TestList(TraitTestBase):
947
937
948 obj = ListTrait()
938 obj = ListTrait()
949
939
950 _default_value = []
940 _default_value = []
951 _good_values = [[], [1], list(range(10)), (1,2)]
941 _good_values = [[], [1], list(range(10)), (1,2)]
952 _bad_values = [10, [1,'a'], 'a']
942 _bad_values = [10, [1,'a'], 'a']
953
943
954 def coerce(self, value):
944 def coerce(self, value):
955 if value is not None:
945 if value is not None:
956 value = list(value)
946 value = list(value)
957 return value
947 return value
958
948
959 class Foo(object):
949 class Foo(object):
960 pass
950 pass
961
951
962 class NoneInstanceListTrait(HasTraits):
952 class NoneInstanceListTrait(HasTraits):
963
953
964 value = List(Instance(Foo, allow_none=False))
954 value = List(Instance(Foo, allow_none=False))
965
955
966 class TestNoneInstanceList(TraitTestBase):
956 class TestNoneInstanceList(TraitTestBase):
967
957
968 obj = NoneInstanceListTrait()
958 obj = NoneInstanceListTrait()
969
959
970 _default_value = []
960 _default_value = []
971 _good_values = [[Foo(), Foo()], []]
961 _good_values = [[Foo(), Foo()], []]
972 _bad_values = [[None], [Foo(), None]]
962 _bad_values = [[None], [Foo(), None]]
973
963
974
964
975 class InstanceListTrait(HasTraits):
965 class InstanceListTrait(HasTraits):
976
966
977 value = List(Instance(__name__+'.Foo'))
967 value = List(Instance(__name__+'.Foo'))
978
968
979 class TestInstanceList(TraitTestBase):
969 class TestInstanceList(TraitTestBase):
980
970
981 obj = InstanceListTrait()
971 obj = InstanceListTrait()
982
972
983 def test_klass(self):
973 def test_klass(self):
984 """Test that the instance klass is properly assigned."""
974 """Test that the instance klass is properly assigned."""
985 self.assertIs(self.obj.traits()['value']._trait.klass, Foo)
975 self.assertIs(self.obj.traits()['value']._trait.klass, Foo)
986
976
987 _default_value = []
977 _default_value = []
988 _good_values = [[Foo(), Foo(), None], []]
978 _good_values = [[Foo(), Foo(), None], []]
989 _bad_values = [['1', 2,], '1', [Foo], None]
979 _bad_values = [['1', 2,], '1', [Foo], None]
990
980
991 class UnionListTrait(HasTraits):
981 class UnionListTrait(HasTraits):
992
982
993 value = List(Int() | Bool())
983 value = List(Int() | Bool())
994
984
995 class TestUnionListTrait(HasTraits):
985 class TestUnionListTrait(HasTraits):
996
986
997 obj = UnionListTrait()
987 obj = UnionListTrait()
998
988
999 _default_value = []
989 _default_value = []
1000 _good_values = [[True, 1], [False, True]]
990 _good_values = [[True, 1], [False, True]]
1001 _bad_values = [[1, 'True'], False]
991 _bad_values = [[1, 'True'], False]
1002
992
1003
993
1004 class LenListTrait(HasTraits):
994 class LenListTrait(HasTraits):
1005
995
1006 value = List(Int, [0], minlen=1, maxlen=2)
996 value = List(Int, [0], minlen=1, maxlen=2)
1007
997
1008 class TestLenList(TraitTestBase):
998 class TestLenList(TraitTestBase):
1009
999
1010 obj = LenListTrait()
1000 obj = LenListTrait()
1011
1001
1012 _default_value = [0]
1002 _default_value = [0]
1013 _good_values = [[1], [1,2], (1,2)]
1003 _good_values = [[1], [1,2], (1,2)]
1014 _bad_values = [10, [1,'a'], 'a', [], list(range(3))]
1004 _bad_values = [10, [1,'a'], 'a', [], list(range(3))]
1015
1005
1016 def coerce(self, value):
1006 def coerce(self, value):
1017 if value is not None:
1007 if value is not None:
1018 value = list(value)
1008 value = list(value)
1019 return value
1009 return value
1020
1010
1021 class TupleTrait(HasTraits):
1011 class TupleTrait(HasTraits):
1022
1012
1023 value = Tuple(Int(allow_none=True))
1013 value = Tuple(Int(allow_none=True))
1024
1014
1025 class TestTupleTrait(TraitTestBase):
1015 class TestTupleTrait(TraitTestBase):
1026
1016
1027 obj = TupleTrait()
1017 obj = TupleTrait()
1028
1018
1029 _default_value = None
1019 _default_value = None
1030 _good_values = [(1,), None, (0,), [1], (None,)]
1020 _good_values = [(1,), None, (0,), [1], (None,)]
1031 _bad_values = [10, (1,2), ('a'), ()]
1021 _bad_values = [10, (1,2), ('a'), ()]
1032
1022
1033 def coerce(self, value):
1023 def coerce(self, value):
1034 if value is not None:
1024 if value is not None:
1035 value = tuple(value)
1025 value = tuple(value)
1036 return value
1026 return value
1037
1027
1038 def test_invalid_args(self):
1028 def test_invalid_args(self):
1039 self.assertRaises(TypeError, Tuple, 5)
1029 self.assertRaises(TypeError, Tuple, 5)
1040 self.assertRaises(TypeError, Tuple, default_value='hello')
1030 self.assertRaises(TypeError, Tuple, default_value='hello')
1041 t = Tuple(Int, CBytes, default_value=(1,5))
1031 t = Tuple(Int, CBytes, default_value=(1,5))
1042
1032
1043 class LooseTupleTrait(HasTraits):
1033 class LooseTupleTrait(HasTraits):
1044
1034
1045 value = Tuple((1,2,3))
1035 value = Tuple((1,2,3))
1046
1036
1047 class TestLooseTupleTrait(TraitTestBase):
1037 class TestLooseTupleTrait(TraitTestBase):
1048
1038
1049 obj = LooseTupleTrait()
1039 obj = LooseTupleTrait()
1050
1040
1051 _default_value = (1,2,3)
1041 _default_value = (1,2,3)
1052 _good_values = [(1,), None, [1], (0,), tuple(range(5)), tuple('hello'), ('a',5), ()]
1042 _good_values = [(1,), None, [1], (0,), tuple(range(5)), tuple('hello'), ('a',5), ()]
1053 _bad_values = [10, 'hello', {}]
1043 _bad_values = [10, 'hello', {}]
1054
1044
1055 def coerce(self, value):
1045 def coerce(self, value):
1056 if value is not None:
1046 if value is not None:
1057 value = tuple(value)
1047 value = tuple(value)
1058 return value
1048 return value
1059
1049
1060 def test_invalid_args(self):
1050 def test_invalid_args(self):
1061 self.assertRaises(TypeError, Tuple, 5)
1051 self.assertRaises(TypeError, Tuple, 5)
1062 self.assertRaises(TypeError, Tuple, default_value='hello')
1052 self.assertRaises(TypeError, Tuple, default_value='hello')
1063 t = Tuple(Int, CBytes, default_value=(1,5))
1053 t = Tuple(Int, CBytes, default_value=(1,5))
1064
1054
1065
1055
1066 class MultiTupleTrait(HasTraits):
1056 class MultiTupleTrait(HasTraits):
1067
1057
1068 value = Tuple(Int, Bytes, default_value=[99,b'bottles'])
1058 value = Tuple(Int, Bytes, default_value=[99,b'bottles'])
1069
1059
1070 class TestMultiTuple(TraitTestBase):
1060 class TestMultiTuple(TraitTestBase):
1071
1061
1072 obj = MultiTupleTrait()
1062 obj = MultiTupleTrait()
1073
1063
1074 _default_value = (99,b'bottles')
1064 _default_value = (99,b'bottles')
1075 _good_values = [(1,b'a'), (2,b'b')]
1065 _good_values = [(1,b'a'), (2,b'b')]
1076 _bad_values = ((),10, b'a', (1,b'a',3), (b'a',1), (1, u'a'))
1066 _bad_values = ((),10, b'a', (1,b'a',3), (b'a',1), (1, u'a'))
1077
1067
1078 class CRegExpTrait(HasTraits):
1068 class CRegExpTrait(HasTraits):
1079
1069
1080 value = CRegExp(r'')
1070 value = CRegExp(r'')
1081
1071
1082 class TestCRegExp(TraitTestBase):
1072 class TestCRegExp(TraitTestBase):
1083
1073
1084 def coerce(self, value):
1074 def coerce(self, value):
1085 return re.compile(value)
1075 return re.compile(value)
1086
1076
1087 obj = CRegExpTrait()
1077 obj = CRegExpTrait()
1088
1078
1089 _default_value = re.compile(r'')
1079 _default_value = re.compile(r'')
1090 _good_values = [r'\d+', re.compile(r'\d+')]
1080 _good_values = [r'\d+', re.compile(r'\d+')]
1091 _bad_values = ['(', None, ()]
1081 _bad_values = ['(', None, ()]
1092
1082
1093 class DictTrait(HasTraits):
1083 class DictTrait(HasTraits):
1094 value = Dict()
1084 value = Dict()
1095
1085
1096 def test_dict_assignment():
1086 def test_dict_assignment():
1097 d = dict()
1087 d = dict()
1098 c = DictTrait()
1088 c = DictTrait()
1099 c.value = d
1089 c.value = d
1100 d['a'] = 5
1090 d['a'] = 5
1101 nt.assert_equal(d, c.value)
1091 nt.assert_equal(d, c.value)
1102 nt.assert_true(c.value is d)
1092 nt.assert_true(c.value is d)
1103
1093
1104 class ValidatedDictTrait(HasTraits):
1094 class ValidatedDictTrait(HasTraits):
1105
1095
1106 value = Dict(Unicode())
1096 value = Dict(Unicode())
1107
1097
1108 class TestInstanceDict(TraitTestBase):
1098 class TestInstanceDict(TraitTestBase):
1109
1099
1110 obj = ValidatedDictTrait()
1100 obj = ValidatedDictTrait()
1111
1101
1112 _default_value = {}
1102 _default_value = {}
1113 _good_values = [{'0': 'foo'}, {'1': 'bar'}]
1103 _good_values = [{'0': 'foo'}, {'1': 'bar'}]
1114 _bad_values = [{'0': 0}, {'1': 1}]
1104 _bad_values = [{'0': 0}, {'1': 1}]
1115
1105
1116
1106
1117 def test_dict_default_value():
1107 def test_dict_default_value():
1118 """Check that the `{}` default value of the Dict traitlet constructor is
1108 """Check that the `{}` default value of the Dict traitlet constructor is
1119 actually copied."""
1109 actually copied."""
1120
1110
1121 d1, d2 = Dict(), Dict()
1111 d1, d2 = Dict(), Dict()
1122 nt.assert_false(d1.get_default_value() is d2.get_default_value())
1112 nt.assert_false(d1.get_default_value() is d2.get_default_value())
1123
1113
1124
1114
1125 class TestValidationHook(TestCase):
1115 class TestValidationHook(TestCase):
1126
1116
1127 def test_parity_trait(self):
1117 def test_parity_trait(self):
1128 """Verify that the early validation hook is effective"""
1118 """Verify that the early validation hook is effective"""
1129
1119
1130 class Parity(HasTraits):
1120 class Parity(HasTraits):
1131
1121
1132 value = Int(0)
1122 value = Int(0)
1133 parity = Enum(['odd', 'even'], default_value='even', allow_none=False)
1123 parity = Enum(['odd', 'even'], default_value='even', allow_none=False)
1134
1124
1135 def _value_validate(self, value, trait):
1125 def _value_validate(self, value, trait):
1136 if self.parity == 'even' and value % 2:
1126 if self.parity == 'even' and value % 2:
1137 raise TraitError('Expected an even number')
1127 raise TraitError('Expected an even number')
1138 if self.parity == 'odd' and (value % 2 == 0):
1128 if self.parity == 'odd' and (value % 2 == 0):
1139 raise TraitError('Expected an odd number')
1129 raise TraitError('Expected an odd number')
1140 return value
1130 return value
1141
1131
1142 u = Parity()
1132 u = Parity()
1143 u.parity = 'odd'
1133 u.parity = 'odd'
1144 u.value = 1 # OK
1134 u.value = 1 # OK
1145 with self.assertRaises(TraitError):
1135 with self.assertRaises(TraitError):
1146 u.value = 2 # Trait Error
1136 u.value = 2 # Trait Error
1147
1137
1148 u.parity = 'even'
1138 u.parity = 'even'
1149 u.value = 2 # OK
1139 u.value = 2 # OK
1150
1140
1151
1141
1152 class TestLink(TestCase):
1142 class TestLink(TestCase):
1153
1143
1154 def test_connect_same(self):
1144 def test_connect_same(self):
1155 """Verify two traitlets of the same type can be linked together using link."""
1145 """Verify two traitlets of the same type can be linked together using link."""
1156
1146
1157 # Create two simple classes with Int traitlets.
1147 # Create two simple classes with Int traitlets.
1158 class A(HasTraits):
1148 class A(HasTraits):
1159 value = Int()
1149 value = Int()
1160 a = A(value=9)
1150 a = A(value=9)
1161 b = A(value=8)
1151 b = A(value=8)
1162
1152
1163 # Conenct the two classes.
1153 # Conenct the two classes.
1164 c = link((a, 'value'), (b, 'value'))
1154 c = link((a, 'value'), (b, 'value'))
1165
1155
1166 # Make sure the values are the same at the point of linking.
1156 # Make sure the values are the same at the point of linking.
1167 self.assertEqual(a.value, b.value)
1157 self.assertEqual(a.value, b.value)
1168
1158
1169 # Change one of the values to make sure they stay in sync.
1159 # Change one of the values to make sure they stay in sync.
1170 a.value = 5
1160 a.value = 5
1171 self.assertEqual(a.value, b.value)
1161 self.assertEqual(a.value, b.value)
1172 b.value = 6
1162 b.value = 6
1173 self.assertEqual(a.value, b.value)
1163 self.assertEqual(a.value, b.value)
1174
1164
1175 def test_link_different(self):
1165 def test_link_different(self):
1176 """Verify two traitlets of different types can be linked together using link."""
1166 """Verify two traitlets of different types can be linked together using link."""
1177
1167
1178 # Create two simple classes with Int traitlets.
1168 # Create two simple classes with Int traitlets.
1179 class A(HasTraits):
1169 class A(HasTraits):
1180 value = Int()
1170 value = Int()
1181 class B(HasTraits):
1171 class B(HasTraits):
1182 count = Int()
1172 count = Int()
1183 a = A(value=9)
1173 a = A(value=9)
1184 b = B(count=8)
1174 b = B(count=8)
1185
1175
1186 # Conenct the two classes.
1176 # Conenct the two classes.
1187 c = link((a, 'value'), (b, 'count'))
1177 c = link((a, 'value'), (b, 'count'))
1188
1178
1189 # Make sure the values are the same at the point of linking.
1179 # Make sure the values are the same at the point of linking.
1190 self.assertEqual(a.value, b.count)
1180 self.assertEqual(a.value, b.count)
1191
1181
1192 # Change one of the values to make sure they stay in sync.
1182 # Change one of the values to make sure they stay in sync.
1193 a.value = 5
1183 a.value = 5
1194 self.assertEqual(a.value, b.count)
1184 self.assertEqual(a.value, b.count)
1195 b.count = 4
1185 b.count = 4
1196 self.assertEqual(a.value, b.count)
1186 self.assertEqual(a.value, b.count)
1197
1187
1198 def test_unlink(self):
1188 def test_unlink(self):
1199 """Verify two linked traitlets can be unlinked."""
1189 """Verify two linked traitlets can be unlinked."""
1200
1190
1201 # Create two simple classes with Int traitlets.
1191 # Create two simple classes with Int traitlets.
1202 class A(HasTraits):
1192 class A(HasTraits):
1203 value = Int()
1193 value = Int()
1204 a = A(value=9)
1194 a = A(value=9)
1205 b = A(value=8)
1195 b = A(value=8)
1206
1196
1207 # Connect the two classes.
1197 # Connect the two classes.
1208 c = link((a, 'value'), (b, 'value'))
1198 c = link((a, 'value'), (b, 'value'))
1209 a.value = 4
1199 a.value = 4
1210 c.unlink()
1200 c.unlink()
1211
1201
1212 # Change one of the values to make sure they don't stay in sync.
1202 # Change one of the values to make sure they don't stay in sync.
1213 a.value = 5
1203 a.value = 5
1214 self.assertNotEqual(a.value, b.value)
1204 self.assertNotEqual(a.value, b.value)
1215
1205
1216 def test_callbacks(self):
1206 def test_callbacks(self):
1217 """Verify two linked traitlets have their callbacks called once."""
1207 """Verify two linked traitlets have their callbacks called once."""
1218
1208
1219 # Create two simple classes with Int traitlets.
1209 # Create two simple classes with Int traitlets.
1220 class A(HasTraits):
1210 class A(HasTraits):
1221 value = Int()
1211 value = Int()
1222 class B(HasTraits):
1212 class B(HasTraits):
1223 count = Int()
1213 count = Int()
1224 a = A(value=9)
1214 a = A(value=9)
1225 b = B(count=8)
1215 b = B(count=8)
1226
1216
1227 # Register callbacks that count.
1217 # Register callbacks that count.
1228 callback_count = []
1218 callback_count = []
1229 def a_callback(name, old, new):
1219 def a_callback(name, old, new):
1230 callback_count.append('a')
1220 callback_count.append('a')
1231 a.on_trait_change(a_callback, 'value')
1221 a.on_trait_change(a_callback, 'value')
1232 def b_callback(name, old, new):
1222 def b_callback(name, old, new):
1233 callback_count.append('b')
1223 callback_count.append('b')
1234 b.on_trait_change(b_callback, 'count')
1224 b.on_trait_change(b_callback, 'count')
1235
1225
1236 # Connect the two classes.
1226 # Connect the two classes.
1237 c = link((a, 'value'), (b, 'count'))
1227 c = link((a, 'value'), (b, 'count'))
1238
1228
1239 # Make sure b's count was set to a's value once.
1229 # Make sure b's count was set to a's value once.
1240 self.assertEqual(''.join(callback_count), 'b')
1230 self.assertEqual(''.join(callback_count), 'b')
1241 del callback_count[:]
1231 del callback_count[:]
1242
1232
1243 # Make sure a's value was set to b's count once.
1233 # Make sure a's value was set to b's count once.
1244 b.count = 5
1234 b.count = 5
1245 self.assertEqual(''.join(callback_count), 'ba')
1235 self.assertEqual(''.join(callback_count), 'ba')
1246 del callback_count[:]
1236 del callback_count[:]
1247
1237
1248 # Make sure b's count was set to a's value once.
1238 # Make sure b's count was set to a's value once.
1249 a.value = 4
1239 a.value = 4
1250 self.assertEqual(''.join(callback_count), 'ab')
1240 self.assertEqual(''.join(callback_count), 'ab')
1251 del callback_count[:]
1241 del callback_count[:]
1252
1242
1253 class TestDirectionalLink(TestCase):
1243 class TestDirectionalLink(TestCase):
1254 def test_connect_same(self):
1244 def test_connect_same(self):
1255 """Verify two traitlets of the same type can be linked together using directional_link."""
1245 """Verify two traitlets of the same type can be linked together using directional_link."""
1256
1246
1257 # Create two simple classes with Int traitlets.
1247 # Create two simple classes with Int traitlets.
1258 class A(HasTraits):
1248 class A(HasTraits):
1259 value = Int()
1249 value = Int()
1260 a = A(value=9)
1250 a = A(value=9)
1261 b = A(value=8)
1251 b = A(value=8)
1262
1252
1263 # Conenct the two classes.
1253 # Conenct the two classes.
1264 c = directional_link((a, 'value'), (b, 'value'))
1254 c = directional_link((a, 'value'), (b, 'value'))
1265
1255
1266 # Make sure the values are the same at the point of linking.
1256 # Make sure the values are the same at the point of linking.
1267 self.assertEqual(a.value, b.value)
1257 self.assertEqual(a.value, b.value)
1268
1258
1269 # Change one the value of the source and check that it synchronizes the target.
1259 # Change one the value of the source and check that it synchronizes the target.
1270 a.value = 5
1260 a.value = 5
1271 self.assertEqual(b.value, 5)
1261 self.assertEqual(b.value, 5)
1272 # Change one the value of the target and check that it has no impact on the source
1262 # Change one the value of the target and check that it has no impact on the source
1273 b.value = 6
1263 b.value = 6
1274 self.assertEqual(a.value, 5)
1264 self.assertEqual(a.value, 5)
1275
1265
1276 def test_link_different(self):
1266 def test_link_different(self):
1277 """Verify two traitlets of different types can be linked together using link."""
1267 """Verify two traitlets of different types can be linked together using link."""
1278
1268
1279 # Create two simple classes with Int traitlets.
1269 # Create two simple classes with Int traitlets.
1280 class A(HasTraits):
1270 class A(HasTraits):
1281 value = Int()
1271 value = Int()
1282 class B(HasTraits):
1272 class B(HasTraits):
1283 count = Int()
1273 count = Int()
1284 a = A(value=9)
1274 a = A(value=9)
1285 b = B(count=8)
1275 b = B(count=8)
1286
1276
1287 # Conenct the two classes.
1277 # Conenct the two classes.
1288 c = directional_link((a, 'value'), (b, 'count'))
1278 c = directional_link((a, 'value'), (b, 'count'))
1289
1279
1290 # Make sure the values are the same at the point of linking.
1280 # Make sure the values are the same at the point of linking.
1291 self.assertEqual(a.value, b.count)
1281 self.assertEqual(a.value, b.count)
1292
1282
1293 # Change one the value of the source and check that it synchronizes the target.
1283 # Change one the value of the source and check that it synchronizes the target.
1294 a.value = 5
1284 a.value = 5
1295 self.assertEqual(b.count, 5)
1285 self.assertEqual(b.count, 5)
1296 # Change one the value of the target and check that it has no impact on the source
1286 # Change one the value of the target and check that it has no impact on the source
1297 b.value = 6
1287 b.value = 6
1298 self.assertEqual(a.value, 5)
1288 self.assertEqual(a.value, 5)
1299
1289
1300 def test_unlink(self):
1290 def test_unlink(self):
1301 """Verify two linked traitlets can be unlinked."""
1291 """Verify two linked traitlets can be unlinked."""
1302
1292
1303 # Create two simple classes with Int traitlets.
1293 # Create two simple classes with Int traitlets.
1304 class A(HasTraits):
1294 class A(HasTraits):
1305 value = Int()
1295 value = Int()
1306 a = A(value=9)
1296 a = A(value=9)
1307 b = A(value=8)
1297 b = A(value=8)
1308
1298
1309 # Connect the two classes.
1299 # Connect the two classes.
1310 c = directional_link((a, 'value'), (b, 'value'))
1300 c = directional_link((a, 'value'), (b, 'value'))
1311 a.value = 4
1301 a.value = 4
1312 c.unlink()
1302 c.unlink()
1313
1303
1314 # Change one of the values to make sure they don't stay in sync.
1304 # Change one of the values to make sure they don't stay in sync.
1315 a.value = 5
1305 a.value = 5
1316 self.assertNotEqual(a.value, b.value)
1306 self.assertNotEqual(a.value, b.value)
1317
1307
1318 class Pickleable(HasTraits):
1308 class Pickleable(HasTraits):
1319 i = Int()
1309 i = Int()
1320 j = Int()
1310 j = Int()
1321
1311
1322 def _i_default(self):
1312 def _i_default(self):
1323 return 1
1313 return 1
1324
1314
1325 def _i_changed(self, name, old, new):
1315 def _i_changed(self, name, old, new):
1326 self.j = new
1316 self.j = new
1327
1317
1328 def test_pickle_hastraits():
1318 def test_pickle_hastraits():
1329 c = Pickleable()
1319 c = Pickleable()
1330 for protocol in range(pickle.HIGHEST_PROTOCOL + 1):
1320 for protocol in range(pickle.HIGHEST_PROTOCOL + 1):
1331 p = pickle.dumps(c, protocol)
1321 p = pickle.dumps(c, protocol)
1332 c2 = pickle.loads(p)
1322 c2 = pickle.loads(p)
1333 nt.assert_equal(c2.i, c.i)
1323 nt.assert_equal(c2.i, c.i)
1334 nt.assert_equal(c2.j, c.j)
1324 nt.assert_equal(c2.j, c.j)
1335
1325
1336 c.i = 5
1326 c.i = 5
1337 for protocol in range(pickle.HIGHEST_PROTOCOL + 1):
1327 for protocol in range(pickle.HIGHEST_PROTOCOL + 1):
1338 p = pickle.dumps(c, protocol)
1328 p = pickle.dumps(c, protocol)
1339 c2 = pickle.loads(p)
1329 c2 = pickle.loads(p)
1340 nt.assert_equal(c2.i, c.i)
1330 nt.assert_equal(c2.i, c.i)
1341 nt.assert_equal(c2.j, c.j)
1331 nt.assert_equal(c2.j, c.j)
1342
1332
1343 class TestEventful(TestCase):
1333 class TestEventful(TestCase):
1344
1334
1345 def test_list(self):
1335 def test_list(self):
1346 """Does the EventfulList work?"""
1336 """Does the EventfulList work?"""
1347 event_cache = []
1337 event_cache = []
1348
1338
1349 class A(HasTraits):
1339 class A(HasTraits):
1350 x = EventfulList([c for c in 'abc'])
1340 x = EventfulList([c for c in 'abc'])
1351 a = A()
1341 a = A()
1352 a.x.on_events(lambda i, x: event_cache.append('insert'), \
1342 a.x.on_events(lambda i, x: event_cache.append('insert'), \
1353 lambda i, x: event_cache.append('set'), \
1343 lambda i, x: event_cache.append('set'), \
1354 lambda i: event_cache.append('del'), \
1344 lambda i: event_cache.append('del'), \
1355 lambda: event_cache.append('reverse'), \
1345 lambda: event_cache.append('reverse'), \
1356 lambda *p, **k: event_cache.append('sort'))
1346 lambda *p, **k: event_cache.append('sort'))
1357
1347
1358 a.x.remove('c')
1348 a.x.remove('c')
1359 # ab
1349 # ab
1360 a.x.insert(0, 'z')
1350 a.x.insert(0, 'z')
1361 # zab
1351 # zab
1362 del a.x[1]
1352 del a.x[1]
1363 # zb
1353 # zb
1364 a.x.reverse()
1354 a.x.reverse()
1365 # bz
1355 # bz
1366 a.x[1] = 'o'
1356 a.x[1] = 'o'
1367 # bo
1357 # bo
1368 a.x.append('a')
1358 a.x.append('a')
1369 # boa
1359 # boa
1370 a.x.sort()
1360 a.x.sort()
1371 # abo
1361 # abo
1372
1362
1373 # Were the correct events captured?
1363 # Were the correct events captured?
1374 self.assertEqual(event_cache, ['del', 'insert', 'del', 'reverse', 'set', 'set', 'sort'])
1364 self.assertEqual(event_cache, ['del', 'insert', 'del', 'reverse', 'set', 'set', 'sort'])
1375
1365
1376 # Is the output correct?
1366 # Is the output correct?
1377 self.assertEqual(a.x, [c for c in 'abo'])
1367 self.assertEqual(a.x, [c for c in 'abo'])
1378
1368
1379 def test_dict(self):
1369 def test_dict(self):
1380 """Does the EventfulDict work?"""
1370 """Does the EventfulDict work?"""
1381 event_cache = []
1371 event_cache = []
1382
1372
1383 class A(HasTraits):
1373 class A(HasTraits):
1384 x = EventfulDict({c: c for c in 'abc'})
1374 x = EventfulDict({c: c for c in 'abc'})
1385 a = A()
1375 a = A()
1386 a.x.on_events(lambda k, v: event_cache.append('add'), \
1376 a.x.on_events(lambda k, v: event_cache.append('add'), \
1387 lambda k, v: event_cache.append('set'), \
1377 lambda k, v: event_cache.append('set'), \
1388 lambda k: event_cache.append('del'))
1378 lambda k: event_cache.append('del'))
1389
1379
1390 del a.x['c']
1380 del a.x['c']
1391 # ab
1381 # ab
1392 a.x['z'] = 1
1382 a.x['z'] = 1
1393 # abz
1383 # abz
1394 a.x['z'] = 'z'
1384 a.x['z'] = 'z'
1395 # abz
1385 # abz
1396 a.x.pop('a')
1386 a.x.pop('a')
1397 # bz
1387 # bz
1398
1388
1399 # Were the correct events captured?
1389 # Were the correct events captured?
1400 self.assertEqual(event_cache, ['del', 'add', 'set', 'del'])
1390 self.assertEqual(event_cache, ['del', 'add', 'set', 'del'])
1401
1391
1402 # Is the output correct?
1392 # Is the output correct?
1403 self.assertEqual(a.x, {c: c for c in 'bz'})
1393 self.assertEqual(a.x, {c: c for c in 'bz'})
1404
1394
1405 ###
1395 ###
1406 # Traits for Forward Declaration Tests
1396 # Traits for Forward Declaration Tests
1407 ###
1397 ###
1408 class ForwardDeclaredInstanceTrait(HasTraits):
1398 class ForwardDeclaredInstanceTrait(HasTraits):
1409
1399
1410 value = ForwardDeclaredInstance('ForwardDeclaredBar')
1400 value = ForwardDeclaredInstance('ForwardDeclaredBar')
1411
1401
1412 class ForwardDeclaredTypeTrait(HasTraits):
1402 class ForwardDeclaredTypeTrait(HasTraits):
1413
1403
1414 value = ForwardDeclaredType('ForwardDeclaredBar')
1404 value = ForwardDeclaredType('ForwardDeclaredBar')
1415
1405
1416 class ForwardDeclaredInstanceListTrait(HasTraits):
1406 class ForwardDeclaredInstanceListTrait(HasTraits):
1417
1407
1418 value = List(ForwardDeclaredInstance('ForwardDeclaredBar'))
1408 value = List(ForwardDeclaredInstance('ForwardDeclaredBar'))
1419
1409
1420 class ForwardDeclaredTypeListTrait(HasTraits):
1410 class ForwardDeclaredTypeListTrait(HasTraits):
1421
1411
1422 value = List(ForwardDeclaredType('ForwardDeclaredBar'))
1412 value = List(ForwardDeclaredType('ForwardDeclaredBar'))
1423 ###
1413 ###
1424 # End Traits for Forward Declaration Tests
1414 # End Traits for Forward Declaration Tests
1425 ###
1415 ###
1426
1416
1427 ###
1417 ###
1428 # Classes for Forward Declaration Tests
1418 # Classes for Forward Declaration Tests
1429 ###
1419 ###
1430 class ForwardDeclaredBar(object):
1420 class ForwardDeclaredBar(object):
1431 pass
1421 pass
1432
1422
1433 class ForwardDeclaredBarSub(ForwardDeclaredBar):
1423 class ForwardDeclaredBarSub(ForwardDeclaredBar):
1434 pass
1424 pass
1435 ###
1425 ###
1436 # End Classes for Forward Declaration Tests
1426 # End Classes for Forward Declaration Tests
1437 ###
1427 ###
1438
1428
1439 ###
1429 ###
1440 # Forward Declaration Tests
1430 # Forward Declaration Tests
1441 ###
1431 ###
1442 class TestForwardDeclaredInstanceTrait(TraitTestBase):
1432 class TestForwardDeclaredInstanceTrait(TraitTestBase):
1443
1433
1444 obj = ForwardDeclaredInstanceTrait()
1434 obj = ForwardDeclaredInstanceTrait()
1445 _default_value = None
1435 _default_value = None
1446 _good_values = [None, ForwardDeclaredBar(), ForwardDeclaredBarSub()]
1436 _good_values = [None, ForwardDeclaredBar(), ForwardDeclaredBarSub()]
1447 _bad_values = ['foo', 3, ForwardDeclaredBar, ForwardDeclaredBarSub]
1437 _bad_values = ['foo', 3, ForwardDeclaredBar, ForwardDeclaredBarSub]
1448
1438
1449 class TestForwardDeclaredTypeTrait(TraitTestBase):
1439 class TestForwardDeclaredTypeTrait(TraitTestBase):
1450
1440
1451 obj = ForwardDeclaredTypeTrait()
1441 obj = ForwardDeclaredTypeTrait()
1452 _default_value = None
1442 _default_value = None
1453 _good_values = [None, ForwardDeclaredBar, ForwardDeclaredBarSub]
1443 _good_values = [None, ForwardDeclaredBar, ForwardDeclaredBarSub]
1454 _bad_values = ['foo', 3, ForwardDeclaredBar(), ForwardDeclaredBarSub()]
1444 _bad_values = ['foo', 3, ForwardDeclaredBar(), ForwardDeclaredBarSub()]
1455
1445
1456 class TestForwardDeclaredInstanceList(TraitTestBase):
1446 class TestForwardDeclaredInstanceList(TraitTestBase):
1457
1447
1458 obj = ForwardDeclaredInstanceListTrait()
1448 obj = ForwardDeclaredInstanceListTrait()
1459
1449
1460 def test_klass(self):
1450 def test_klass(self):
1461 """Test that the instance klass is properly assigned."""
1451 """Test that the instance klass is properly assigned."""
1462 self.assertIs(self.obj.traits()['value']._trait.klass, ForwardDeclaredBar)
1452 self.assertIs(self.obj.traits()['value']._trait.klass, ForwardDeclaredBar)
1463
1453
1464 _default_value = []
1454 _default_value = []
1465 _good_values = [
1455 _good_values = [
1466 [ForwardDeclaredBar(), ForwardDeclaredBarSub(), None],
1456 [ForwardDeclaredBar(), ForwardDeclaredBarSub(), None],
1467 [None],
1457 [None],
1468 [],
1458 [],
1469 ]
1459 ]
1470 _bad_values = [
1460 _bad_values = [
1471 ForwardDeclaredBar(),
1461 ForwardDeclaredBar(),
1472 [ForwardDeclaredBar(), 3],
1462 [ForwardDeclaredBar(), 3],
1473 '1',
1463 '1',
1474 # Note that this is the type, not an instance.
1464 # Note that this is the type, not an instance.
1475 [ForwardDeclaredBar],
1465 [ForwardDeclaredBar],
1476 None,
1466 None,
1477 ]
1467 ]
1478
1468
1479 class TestForwardDeclaredTypeList(TraitTestBase):
1469 class TestForwardDeclaredTypeList(TraitTestBase):
1480
1470
1481 obj = ForwardDeclaredTypeListTrait()
1471 obj = ForwardDeclaredTypeListTrait()
1482
1472
1483 def test_klass(self):
1473 def test_klass(self):
1484 """Test that the instance klass is properly assigned."""
1474 """Test that the instance klass is properly assigned."""
1485 self.assertIs(self.obj.traits()['value']._trait.klass, ForwardDeclaredBar)
1475 self.assertIs(self.obj.traits()['value']._trait.klass, ForwardDeclaredBar)
1486
1476
1487 _default_value = []
1477 _default_value = []
1488 _good_values = [
1478 _good_values = [
1489 [ForwardDeclaredBar, ForwardDeclaredBarSub, None],
1479 [ForwardDeclaredBar, ForwardDeclaredBarSub, None],
1490 [],
1480 [],
1491 [None],
1481 [None],
1492 ]
1482 ]
1493 _bad_values = [
1483 _bad_values = [
1494 ForwardDeclaredBar,
1484 ForwardDeclaredBar,
1495 [ForwardDeclaredBar, 3],
1485 [ForwardDeclaredBar, 3],
1496 '1',
1486 '1',
1497 # Note that this is an instance, not the type.
1487 # Note that this is an instance, not the type.
1498 [ForwardDeclaredBar()],
1488 [ForwardDeclaredBar()],
1499 None,
1489 None,
1500 ]
1490 ]
1501 ###
1491 ###
1502 # End Forward Declaration Tests
1492 # End Forward Declaration Tests
1503 ###
1493 ###
@@ -1,1824 +1,1808 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 A lightweight Traits like module.
3 A lightweight Traits like module.
4
4
5 This is designed to provide a lightweight, simple, pure Python version of
5 This is designed to provide a lightweight, simple, pure Python version of
6 many of the capabilities of enthought.traits. This includes:
6 many of the capabilities of enthought.traits. This includes:
7
7
8 * Validation
8 * Validation
9 * Type specification with defaults
9 * Type specification with defaults
10 * Static and dynamic notification
10 * Static and dynamic notification
11 * Basic predefined types
11 * Basic predefined types
12 * An API that is similar to enthought.traits
12 * An API that is similar to enthought.traits
13
13
14 We don't support:
14 We don't support:
15
15
16 * Delegation
16 * Delegation
17 * Automatic GUI generation
17 * Automatic GUI generation
18 * A full set of trait types. Most importantly, we don't provide container
18 * A full set of trait types. Most importantly, we don't provide container
19 traits (list, dict, tuple) that can trigger notifications if their
19 traits (list, dict, tuple) that can trigger notifications if their
20 contents change.
20 contents change.
21 * API compatibility with enthought.traits
21 * API compatibility with enthought.traits
22
22
23 There are also some important difference in our design:
23 There are also some important difference in our design:
24
24
25 * enthought.traits does not validate default values. We do.
25 * enthought.traits does not validate default values. We do.
26
26
27 We choose to create this module because we need these capabilities, but
27 We choose to create this module because we need these capabilities, but
28 we need them to be pure Python so they work in all Python implementations,
28 we need them to be pure Python so they work in all Python implementations,
29 including Jython and IronPython.
29 including Jython and IronPython.
30
30
31 Inheritance diagram:
31 Inheritance diagram:
32
32
33 .. inheritance-diagram:: IPython.utils.traitlets
33 .. inheritance-diagram:: IPython.utils.traitlets
34 :parts: 3
34 :parts: 3
35 """
35 """
36
36
37 # Copyright (c) IPython Development Team.
37 # Copyright (c) IPython Development Team.
38 # Distributed under the terms of the Modified BSD License.
38 # Distributed under the terms of the Modified BSD License.
39 #
39 #
40 # Adapted from enthought.traits, Copyright (c) Enthought, Inc.,
40 # Adapted from enthought.traits, Copyright (c) Enthought, Inc.,
41 # also under the terms of the Modified BSD License.
41 # also under the terms of the Modified BSD License.
42
42
43 import contextlib
43 import contextlib
44 import inspect
44 import inspect
45 import re
45 import re
46 import sys
46 import sys
47 import types
47 import types
48 from types import FunctionType
48 from types import FunctionType
49 try:
49 try:
50 from types import ClassType, InstanceType
50 from types import ClassType, InstanceType
51 ClassTypes = (ClassType, type)
51 ClassTypes = (ClassType, type)
52 except:
52 except:
53 ClassTypes = (type,)
53 ClassTypes = (type,)
54 from warnings import warn
54 from warnings import warn
55
55
56 from .importstring import import_item
56 from .importstring import import_item
57 from IPython.utils import py3compat
57 from IPython.utils import py3compat
58 from IPython.utils import eventful
58 from IPython.utils import eventful
59 from IPython.utils.py3compat import iteritems, string_types
59 from IPython.utils.py3compat import iteritems, string_types
60 from IPython.testing.skipdoctest import skip_doctest
60 from IPython.testing.skipdoctest import skip_doctest
61
61
62 SequenceTypes = (list, tuple, set, frozenset)
62 SequenceTypes = (list, tuple, set, frozenset)
63
63
64 #-----------------------------------------------------------------------------
64 #-----------------------------------------------------------------------------
65 # Basic classes
65 # Basic classes
66 #-----------------------------------------------------------------------------
66 #-----------------------------------------------------------------------------
67
67
68
68
69 class NoDefaultSpecified ( object ): pass
69 class NoDefaultSpecified ( object ): pass
70 NoDefaultSpecified = NoDefaultSpecified()
70 NoDefaultSpecified = NoDefaultSpecified()
71
71
72
72
73 class Undefined ( object ): pass
73 class Undefined ( object ): pass
74 Undefined = Undefined()
74 Undefined = Undefined()
75
75
76 class TraitError(Exception):
76 class TraitError(Exception):
77 pass
77 pass
78
78
79 #-----------------------------------------------------------------------------
79 #-----------------------------------------------------------------------------
80 # Utilities
80 # Utilities
81 #-----------------------------------------------------------------------------
81 #-----------------------------------------------------------------------------
82
82
83
83
84 def class_of ( object ):
84 def class_of ( object ):
85 """ Returns a string containing the class name of an object with the
85 """ Returns a string containing the class name of an object with the
86 correct indefinite article ('a' or 'an') preceding it (e.g., 'an Image',
86 correct indefinite article ('a' or 'an') preceding it (e.g., 'an Image',
87 'a PlotValue').
87 'a PlotValue').
88 """
88 """
89 if isinstance( object, py3compat.string_types ):
89 if isinstance( object, py3compat.string_types ):
90 return add_article( object )
90 return add_article( object )
91
91
92 return add_article( object.__class__.__name__ )
92 return add_article( object.__class__.__name__ )
93
93
94
94
95 def add_article ( name ):
95 def add_article ( name ):
96 """ Returns a string containing the correct indefinite article ('a' or 'an')
96 """ Returns a string containing the correct indefinite article ('a' or 'an')
97 prefixed to the specified string.
97 prefixed to the specified string.
98 """
98 """
99 if name[:1].lower() in 'aeiou':
99 if name[:1].lower() in 'aeiou':
100 return 'an ' + name
100 return 'an ' + name
101
101
102 return 'a ' + name
102 return 'a ' + name
103
103
104
104
105 def repr_type(obj):
105 def repr_type(obj):
106 """ Return a string representation of a value and its type for readable
106 """ Return a string representation of a value and its type for readable
107 error messages.
107 error messages.
108 """
108 """
109 the_type = type(obj)
109 the_type = type(obj)
110 if (not py3compat.PY3) and the_type is InstanceType:
110 if (not py3compat.PY3) and the_type is InstanceType:
111 # Old-style class.
111 # Old-style class.
112 the_type = obj.__class__
112 the_type = obj.__class__
113 msg = '%r %r' % (obj, the_type)
113 msg = '%r %r' % (obj, the_type)
114 return msg
114 return msg
115
115
116
116
117 def is_trait(t):
117 def is_trait(t):
118 """ Returns whether the given value is an instance or subclass of TraitType.
118 """ Returns whether the given value is an instance or subclass of TraitType.
119 """
119 """
120 return (isinstance(t, TraitType) or
120 return (isinstance(t, TraitType) or
121 (isinstance(t, type) and issubclass(t, TraitType)))
121 (isinstance(t, type) and issubclass(t, TraitType)))
122
122
123
123
124 def parse_notifier_name(name):
124 def parse_notifier_name(name):
125 """Convert the name argument to a list of names.
125 """Convert the name argument to a list of names.
126
126
127 Examples
127 Examples
128 --------
128 --------
129
129
130 >>> parse_notifier_name('a')
130 >>> parse_notifier_name('a')
131 ['a']
131 ['a']
132 >>> parse_notifier_name(['a','b'])
132 >>> parse_notifier_name(['a','b'])
133 ['a', 'b']
133 ['a', 'b']
134 >>> parse_notifier_name(None)
134 >>> parse_notifier_name(None)
135 ['anytrait']
135 ['anytrait']
136 """
136 """
137 if isinstance(name, string_types):
137 if isinstance(name, string_types):
138 return [name]
138 return [name]
139 elif name is None:
139 elif name is None:
140 return ['anytrait']
140 return ['anytrait']
141 elif isinstance(name, (list, tuple)):
141 elif isinstance(name, (list, tuple)):
142 for n in name:
142 for n in name:
143 assert isinstance(n, string_types), "names must be strings"
143 assert isinstance(n, string_types), "names must be strings"
144 return name
144 return name
145
145
146
146
147 class _SimpleTest:
147 class _SimpleTest:
148 def __init__ ( self, value ): self.value = value
148 def __init__ ( self, value ): self.value = value
149 def __call__ ( self, test ):
149 def __call__ ( self, test ):
150 return test == self.value
150 return test == self.value
151 def __repr__(self):
151 def __repr__(self):
152 return "<SimpleTest(%r)" % self.value
152 return "<SimpleTest(%r)" % self.value
153 def __str__(self):
153 def __str__(self):
154 return self.__repr__()
154 return self.__repr__()
155
155
156
156
157 def getmembers(object, predicate=None):
157 def getmembers(object, predicate=None):
158 """A safe version of inspect.getmembers that handles missing attributes.
158 """A safe version of inspect.getmembers that handles missing attributes.
159
159
160 This is useful when there are descriptor based attributes that for
160 This is useful when there are descriptor based attributes that for
161 some reason raise AttributeError even though they exist. This happens
161 some reason raise AttributeError even though they exist. This happens
162 in zope.inteface with the __provides__ attribute.
162 in zope.inteface with the __provides__ attribute.
163 """
163 """
164 results = []
164 results = []
165 for key in dir(object):
165 for key in dir(object):
166 try:
166 try:
167 value = getattr(object, key)
167 value = getattr(object, key)
168 except AttributeError:
168 except AttributeError:
169 pass
169 pass
170 else:
170 else:
171 if not predicate or predicate(value):
171 if not predicate or predicate(value):
172 results.append((key, value))
172 results.append((key, value))
173 results.sort()
173 results.sort()
174 return results
174 return results
175
175
176 def _validate_link(*tuples):
176 def _validate_link(*tuples):
177 """Validate arguments for traitlet link functions"""
177 """Validate arguments for traitlet link functions"""
178 for t in tuples:
178 for t in tuples:
179 if not len(t) == 2:
179 if not len(t) == 2:
180 raise TypeError("Each linked traitlet must be specified as (HasTraits, 'trait_name'), not %r" % t)
180 raise TypeError("Each linked traitlet must be specified as (HasTraits, 'trait_name'), not %r" % t)
181 obj, trait_name = t
181 obj, trait_name = t
182 if not isinstance(obj, HasTraits):
182 if not isinstance(obj, HasTraits):
183 raise TypeError("Each object must be HasTraits, not %r" % type(obj))
183 raise TypeError("Each object must be HasTraits, not %r" % type(obj))
184 if not trait_name in obj.traits():
184 if not trait_name in obj.traits():
185 raise TypeError("%r has no trait %r" % (obj, trait_name))
185 raise TypeError("%r has no trait %r" % (obj, trait_name))
186
186
187 @skip_doctest
187 @skip_doctest
188 class link(object):
188 class link(object):
189 """Link traits from different objects together so they remain in sync.
189 """Link traits from different objects together so they remain in sync.
190
190
191 Parameters
191 Parameters
192 ----------
192 ----------
193 *args : pairs of objects/attributes
193 *args : pairs of objects/attributes
194
194
195 Examples
195 Examples
196 --------
196 --------
197
197
198 >>> c = link((obj1, 'value'), (obj2, 'value'), (obj3, 'value'))
198 >>> c = link((obj1, 'value'), (obj2, 'value'), (obj3, 'value'))
199 >>> obj1.value = 5 # updates other objects as well
199 >>> obj1.value = 5 # updates other objects as well
200 """
200 """
201 updating = False
201 updating = False
202 def __init__(self, *args):
202 def __init__(self, *args):
203 if len(args) < 2:
203 if len(args) < 2:
204 raise TypeError('At least two traitlets must be provided.')
204 raise TypeError('At least two traitlets must be provided.')
205 _validate_link(*args)
205 _validate_link(*args)
206
206
207 self.objects = {}
207 self.objects = {}
208
208
209 initial = getattr(args[0][0], args[0][1])
209 initial = getattr(args[0][0], args[0][1])
210 for obj, attr in args:
210 for obj, attr in args:
211 setattr(obj, attr, initial)
211 setattr(obj, attr, initial)
212
212
213 callback = self._make_closure(obj, attr)
213 callback = self._make_closure(obj, attr)
214 obj.on_trait_change(callback, attr)
214 obj.on_trait_change(callback, attr)
215 self.objects[(obj, attr)] = callback
215 self.objects[(obj, attr)] = callback
216
216
217 @contextlib.contextmanager
217 @contextlib.contextmanager
218 def _busy_updating(self):
218 def _busy_updating(self):
219 self.updating = True
219 self.updating = True
220 try:
220 try:
221 yield
221 yield
222 finally:
222 finally:
223 self.updating = False
223 self.updating = False
224
224
225 def _make_closure(self, sending_obj, sending_attr):
225 def _make_closure(self, sending_obj, sending_attr):
226 def update(name, old, new):
226 def update(name, old, new):
227 self._update(sending_obj, sending_attr, new)
227 self._update(sending_obj, sending_attr, new)
228 return update
228 return update
229
229
230 def _update(self, sending_obj, sending_attr, new):
230 def _update(self, sending_obj, sending_attr, new):
231 if self.updating:
231 if self.updating:
232 return
232 return
233 with self._busy_updating():
233 with self._busy_updating():
234 for obj, attr in self.objects.keys():
234 for obj, attr in self.objects.keys():
235 setattr(obj, attr, new)
235 setattr(obj, attr, new)
236
236
237 def unlink(self):
237 def unlink(self):
238 for key, callback in self.objects.items():
238 for key, callback in self.objects.items():
239 (obj, attr) = key
239 (obj, attr) = key
240 obj.on_trait_change(callback, attr, remove=True)
240 obj.on_trait_change(callback, attr, remove=True)
241
241
242 @skip_doctest
242 @skip_doctest
243 class directional_link(object):
243 class directional_link(object):
244 """Link the trait of a source object with traits of target objects.
244 """Link the trait of a source object with traits of target objects.
245
245
246 Parameters
246 Parameters
247 ----------
247 ----------
248 source : pair of object, name
248 source : pair of object, name
249 targets : pairs of objects/attributes
249 targets : pairs of objects/attributes
250
250
251 Examples
251 Examples
252 --------
252 --------
253
253
254 >>> c = directional_link((src, 'value'), (tgt1, 'value'), (tgt2, 'value'))
254 >>> c = directional_link((src, 'value'), (tgt1, 'value'), (tgt2, 'value'))
255 >>> src.value = 5 # updates target objects
255 >>> src.value = 5 # updates target objects
256 >>> tgt1.value = 6 # does not update other objects
256 >>> tgt1.value = 6 # does not update other objects
257 """
257 """
258 updating = False
258 updating = False
259
259
260 def __init__(self, source, *targets):
260 def __init__(self, source, *targets):
261 if len(targets) < 1:
261 if len(targets) < 1:
262 raise TypeError('At least two traitlets must be provided.')
262 raise TypeError('At least two traitlets must be provided.')
263 _validate_link(source, *targets)
263 _validate_link(source, *targets)
264 self.source = source
264 self.source = source
265 self.targets = targets
265 self.targets = targets
266
266
267 # Update current value
267 # Update current value
268 src_attr_value = getattr(source[0], source[1])
268 src_attr_value = getattr(source[0], source[1])
269 for obj, attr in targets:
269 for obj, attr in targets:
270 setattr(obj, attr, src_attr_value)
270 setattr(obj, attr, src_attr_value)
271
271
272 # Wire
272 # Wire
273 self.source[0].on_trait_change(self._update, self.source[1])
273 self.source[0].on_trait_change(self._update, self.source[1])
274
274
275 @contextlib.contextmanager
275 @contextlib.contextmanager
276 def _busy_updating(self):
276 def _busy_updating(self):
277 self.updating = True
277 self.updating = True
278 try:
278 try:
279 yield
279 yield
280 finally:
280 finally:
281 self.updating = False
281 self.updating = False
282
282
283 def _update(self, name, old, new):
283 def _update(self, name, old, new):
284 if self.updating:
284 if self.updating:
285 return
285 return
286 with self._busy_updating():
286 with self._busy_updating():
287 for obj, attr in self.targets:
287 for obj, attr in self.targets:
288 setattr(obj, attr, new)
288 setattr(obj, attr, new)
289
289
290 def unlink(self):
290 def unlink(self):
291 self.source[0].on_trait_change(self._update, self.source[1], remove=True)
291 self.source[0].on_trait_change(self._update, self.source[1], remove=True)
292 self.source = None
292 self.source = None
293 self.targets = []
293 self.targets = []
294
294
295 dlink = directional_link
295 dlink = directional_link
296
296
297 #-----------------------------------------------------------------------------
297 #-----------------------------------------------------------------------------
298 # Base TraitType for all traits
298 # Base TraitType for all traits
299 #-----------------------------------------------------------------------------
299 #-----------------------------------------------------------------------------
300
300
301
301
302 class TraitType(object):
302 class TraitType(object):
303 """A base class for all trait descriptors.
303 """A base class for all trait descriptors.
304
304
305 Notes
305 Notes
306 -----
306 -----
307 Our implementation of traits is based on Python's descriptor
307 Our implementation of traits is based on Python's descriptor
308 prototol. This class is the base class for all such descriptors. The
308 prototol. This class is the base class for all such descriptors. The
309 only magic we use is a custom metaclass for the main :class:`HasTraits`
309 only magic we use is a custom metaclass for the main :class:`HasTraits`
310 class that does the following:
310 class that does the following:
311
311
312 1. Sets the :attr:`name` attribute of every :class:`TraitType`
312 1. Sets the :attr:`name` attribute of every :class:`TraitType`
313 instance in the class dict to the name of the attribute.
313 instance in the class dict to the name of the attribute.
314 2. Sets the :attr:`this_class` attribute of every :class:`TraitType`
314 2. Sets the :attr:`this_class` attribute of every :class:`TraitType`
315 instance in the class dict to the *class* that declared the trait.
315 instance in the class dict to the *class* that declared the trait.
316 This is used by the :class:`This` trait to allow subclasses to
316 This is used by the :class:`This` trait to allow subclasses to
317 accept superclasses for :class:`This` values.
317 accept superclasses for :class:`This` values.
318 """
318 """
319
319
320
320
321 metadata = {}
321 metadata = {}
322 default_value = Undefined
322 default_value = Undefined
323 allow_none = False
323 allow_none = False
324 info_text = 'any value'
324 info_text = 'any value'
325
325
326 def __init__(self, default_value=NoDefaultSpecified, allow_none=None, **metadata):
326 def __init__(self, default_value=NoDefaultSpecified, allow_none=None, **metadata):
327 """Create a TraitType.
327 """Create a TraitType.
328 """
328 """
329 if default_value is not NoDefaultSpecified:
329 if default_value is not NoDefaultSpecified:
330 self.default_value = default_value
330 self.default_value = default_value
331 if allow_none is not None:
331 if allow_none is not None:
332 self.allow_none = allow_none
332 self.allow_none = allow_none
333
333
334 if 'default' in metadata:
334 if 'default' in metadata:
335 # Warn the user that they probably meant default_value.
335 # Warn the user that they probably meant default_value.
336 warn(
336 warn(
337 "Parameter 'default' passed to TraitType. "
337 "Parameter 'default' passed to TraitType. "
338 "Did you mean 'default_value'?"
338 "Did you mean 'default_value'?"
339 )
339 )
340
340
341 if len(metadata) > 0:
341 if len(metadata) > 0:
342 if len(self.metadata) > 0:
342 if len(self.metadata) > 0:
343 self._metadata = self.metadata.copy()
343 self._metadata = self.metadata.copy()
344 self._metadata.update(metadata)
344 self._metadata.update(metadata)
345 else:
345 else:
346 self._metadata = metadata
346 self._metadata = metadata
347 else:
347 else:
348 self._metadata = self.metadata
348 self._metadata = self.metadata
349
349
350 self.init()
350 self.init()
351
351
352 def init(self):
352 def init(self):
353 pass
353 pass
354
354
355 def get_default_value(self):
355 def get_default_value(self):
356 """Create a new instance of the default value."""
356 """Create a new instance of the default value."""
357 return self.default_value
357 return self.default_value
358
358
359 def instance_init(self):
359 def instance_init(self):
360 """Part of the initialization which may depends on the underlying
360 """Part of the initialization which may depends on the underlying
361 HasTraits instance.
361 HasTraits instance.
362
362
363 It is typically overloaded for specific trait types.
363 It is typically overloaded for specific trait types.
364
364
365 This method is called by :meth:`HasTraits.__new__` and in the
365 This method is called by :meth:`HasTraits.__new__` and in the
366 :meth:`TraitType.instance_init` method of trait types holding
366 :meth:`TraitType.instance_init` method of trait types holding
367 other trait types.
367 other trait types.
368 """
368 """
369 pass
369 pass
370
370
371 def init_default_value(self, obj):
371 def init_default_value(self, obj):
372 """Instantiate the default value for the trait type.
372 """Instantiate the default value for the trait type.
373
373
374 This method is called by :meth:`TraitType.set_default_value` in the
374 This method is called by :meth:`TraitType.set_default_value` in the
375 case a default value is provided at construction time or later when
375 case a default value is provided at construction time or later when
376 accessing the trait value for the first time in
376 accessing the trait value for the first time in
377 :meth:`HasTraits.__get__`.
377 :meth:`HasTraits.__get__`.
378 """
378 """
379 value = self.get_default_value()
379 value = self.get_default_value()
380 value = self._validate(obj, value)
380 value = self._validate(obj, value)
381 obj._trait_values[self.name] = value
381 obj._trait_values[self.name] = value
382 return value
382 return value
383
383
384 def set_default_value(self, obj):
384 def set_default_value(self, obj):
385 """Set the default value on a per instance basis.
385 """Set the default value on a per instance basis.
386
386
387 This method is called by :meth:`HasTraits.__new__` to instantiate and
387 This method is called by :meth:`HasTraits.__new__` to instantiate and
388 validate the default value. The creation and validation of
388 validate the default value. The creation and validation of
389 default values must be delayed until the parent :class:`HasTraits`
389 default values must be delayed until the parent :class:`HasTraits`
390 class has been instantiated.
390 class has been instantiated.
391 Parameters
391 Parameters
392 ----------
392 ----------
393 obj : :class:`HasTraits` instance
393 obj : :class:`HasTraits` instance
394 The parent :class:`HasTraits` instance that has just been
394 The parent :class:`HasTraits` instance that has just been
395 created.
395 created.
396 """
396 """
397 # Check for a deferred initializer defined in the same class as the
397 # Check for a deferred initializer defined in the same class as the
398 # trait declaration or above.
398 # trait declaration or above.
399 mro = type(obj).mro()
399 mro = type(obj).mro()
400 meth_name = '_%s_default' % self.name
400 meth_name = '_%s_default' % self.name
401 for cls in mro[:mro.index(self.this_class)+1]:
401 for cls in mro[:mro.index(self.this_class)+1]:
402 if meth_name in cls.__dict__:
402 if meth_name in cls.__dict__:
403 break
403 break
404 else:
404 else:
405 # We didn't find one. Do static initialization.
405 # We didn't find one. Do static initialization.
406 self.init_default_value(obj)
406 self.init_default_value(obj)
407 return
407 return
408 # Complete the dynamic initialization.
408 # Complete the dynamic initialization.
409 obj._trait_dyn_inits[self.name] = meth_name
409 obj._trait_dyn_inits[self.name] = meth_name
410
410
411 def __get__(self, obj, cls=None):
411 def __get__(self, obj, cls=None):
412 """Get the value of the trait by self.name for the instance.
412 """Get the value of the trait by self.name for the instance.
413
413
414 Default values are instantiated when :meth:`HasTraits.__new__`
414 Default values are instantiated when :meth:`HasTraits.__new__`
415 is called. Thus by the time this method gets called either the
415 is called. Thus by the time this method gets called either the
416 default value or a user defined value (they called :meth:`__set__`)
416 default value or a user defined value (they called :meth:`__set__`)
417 is in the :class:`HasTraits` instance.
417 is in the :class:`HasTraits` instance.
418 """
418 """
419 if obj is None:
419 if obj is None:
420 return self
420 return self
421 else:
421 else:
422 try:
422 try:
423 value = obj._trait_values[self.name]
423 value = obj._trait_values[self.name]
424 except KeyError:
424 except KeyError:
425 # Check for a dynamic initializer.
425 # Check for a dynamic initializer.
426 if self.name in obj._trait_dyn_inits:
426 if self.name in obj._trait_dyn_inits:
427 method = getattr(obj, obj._trait_dyn_inits[self.name])
427 method = getattr(obj, obj._trait_dyn_inits[self.name])
428 value = method()
428 value = method()
429 # FIXME: Do we really validate here?
429 # FIXME: Do we really validate here?
430 value = self._validate(obj, value)
430 value = self._validate(obj, value)
431 obj._trait_values[self.name] = value
431 obj._trait_values[self.name] = value
432 return value
432 return value
433 else:
433 else:
434 return self.init_default_value(obj)
434 return self.init_default_value(obj)
435 except Exception:
435 except Exception:
436 # HasTraits should call set_default_value to populate
436 # HasTraits should call set_default_value to populate
437 # this. So this should never be reached.
437 # this. So this should never be reached.
438 raise TraitError('Unexpected error in TraitType: '
438 raise TraitError('Unexpected error in TraitType: '
439 'default value not set properly')
439 'default value not set properly')
440 else:
440 else:
441 return value
441 return value
442
442
443 def __set__(self, obj, value):
443 def __set__(self, obj, value):
444 new_value = self._validate(obj, value)
444 new_value = self._validate(obj, value)
445 try:
445 try:
446 old_value = obj._trait_values[self.name]
446 old_value = obj._trait_values[self.name]
447 except KeyError:
447 except KeyError:
448 old_value = None
448 old_value = None
449
449
450 obj._trait_values[self.name] = new_value
450 obj._trait_values[self.name] = new_value
451 try:
451 try:
452 silent = bool(old_value == new_value)
452 silent = bool(old_value == new_value)
453 except:
453 except:
454 # if there is an error in comparing, default to notify
454 # if there is an error in comparing, default to notify
455 silent = False
455 silent = False
456 if silent is not True:
456 if silent is not True:
457 # we explicitly compare silent to True just in case the equality
457 # we explicitly compare silent to True just in case the equality
458 # comparison above returns something other than True/False
458 # comparison above returns something other than True/False
459 obj._notify_trait(self.name, old_value, new_value)
459 obj._notify_trait(self.name, old_value, new_value)
460
460
461 def _validate(self, obj, value):
461 def _validate(self, obj, value):
462 if value is None and self.allow_none:
462 if value is None and self.allow_none:
463 return value
463 return value
464 if hasattr(self, 'validate'):
464 if hasattr(self, 'validate'):
465 value = self.validate(obj, value)
465 value = self.validate(obj, value)
466 try:
466 try:
467 obj_validate = getattr(obj, '_%s_validate' % self.name)
467 obj_validate = getattr(obj, '_%s_validate' % self.name)
468 except (AttributeError, RuntimeError):
468 except (AttributeError, RuntimeError):
469 # Qt mixins raise RuntimeError on missing attrs accessed before __init__
469 # Qt mixins raise RuntimeError on missing attrs accessed before __init__
470 pass
470 pass
471 else:
471 else:
472 value = obj_validate(value, self)
472 value = obj_validate(value, self)
473 return value
473 return value
474
474
475 def __or__(self, other):
475 def __or__(self, other):
476 if isinstance(other, Union):
476 if isinstance(other, Union):
477 return Union([self] + other.trait_types)
477 return Union([self] + other.trait_types)
478 else:
478 else:
479 return Union([self, other])
479 return Union([self, other])
480
480
481 def info(self):
481 def info(self):
482 return self.info_text
482 return self.info_text
483
483
484 def error(self, obj, value):
484 def error(self, obj, value):
485 if obj is not None:
485 if obj is not None:
486 e = "The '%s' trait of %s instance must be %s, but a value of %s was specified." \
486 e = "The '%s' trait of %s instance must be %s, but a value of %s was specified." \
487 % (self.name, class_of(obj),
487 % (self.name, class_of(obj),
488 self.info(), repr_type(value))
488 self.info(), repr_type(value))
489 else:
489 else:
490 e = "The '%s' trait must be %s, but a value of %r was specified." \
490 e = "The '%s' trait must be %s, but a value of %r was specified." \
491 % (self.name, self.info(), repr_type(value))
491 % (self.name, self.info(), repr_type(value))
492 raise TraitError(e)
492 raise TraitError(e)
493
493
494 def get_metadata(self, key, default=None):
494 def get_metadata(self, key, default=None):
495 return getattr(self, '_metadata', {}).get(key, default)
495 return getattr(self, '_metadata', {}).get(key, default)
496
496
497 def set_metadata(self, key, value):
497 def set_metadata(self, key, value):
498 getattr(self, '_metadata', {})[key] = value
498 getattr(self, '_metadata', {})[key] = value
499
499
500
500
501 #-----------------------------------------------------------------------------
501 #-----------------------------------------------------------------------------
502 # The HasTraits implementation
502 # The HasTraits implementation
503 #-----------------------------------------------------------------------------
503 #-----------------------------------------------------------------------------
504
504
505
505
506 class MetaHasTraits(type):
506 class MetaHasTraits(type):
507 """A metaclass for HasTraits.
507 """A metaclass for HasTraits.
508
508
509 This metaclass makes sure that any TraitType class attributes are
509 This metaclass makes sure that any TraitType class attributes are
510 instantiated and sets their name attribute.
510 instantiated and sets their name attribute.
511 """
511 """
512
512
513 def __new__(mcls, name, bases, classdict):
513 def __new__(mcls, name, bases, classdict):
514 """Create the HasTraits class.
514 """Create the HasTraits class.
515
515
516 This instantiates all TraitTypes in the class dict and sets their
516 This instantiates all TraitTypes in the class dict and sets their
517 :attr:`name` attribute.
517 :attr:`name` attribute.
518 """
518 """
519 # print "MetaHasTraitlets (mcls, name): ", mcls, name
519 # print "MetaHasTraitlets (mcls, name): ", mcls, name
520 # print "MetaHasTraitlets (bases): ", bases
520 # print "MetaHasTraitlets (bases): ", bases
521 # print "MetaHasTraitlets (classdict): ", classdict
521 # print "MetaHasTraitlets (classdict): ", classdict
522 for k,v in iteritems(classdict):
522 for k,v in iteritems(classdict):
523 if isinstance(v, TraitType):
523 if isinstance(v, TraitType):
524 v.name = k
524 v.name = k
525 elif inspect.isclass(v):
525 elif inspect.isclass(v):
526 if issubclass(v, TraitType):
526 if issubclass(v, TraitType):
527 vinst = v()
527 vinst = v()
528 vinst.name = k
528 vinst.name = k
529 classdict[k] = vinst
529 classdict[k] = vinst
530 return super(MetaHasTraits, mcls).__new__(mcls, name, bases, classdict)
530 return super(MetaHasTraits, mcls).__new__(mcls, name, bases, classdict)
531
531
532 def __init__(cls, name, bases, classdict):
532 def __init__(cls, name, bases, classdict):
533 """Finish initializing the HasTraits class.
533 """Finish initializing the HasTraits class.
534
534
535 This sets the :attr:`this_class` attribute of each TraitType in the
535 This sets the :attr:`this_class` attribute of each TraitType in the
536 class dict to the newly created class ``cls``.
536 class dict to the newly created class ``cls``.
537 """
537 """
538 for k, v in iteritems(classdict):
538 for k, v in iteritems(classdict):
539 if isinstance(v, TraitType):
539 if isinstance(v, TraitType):
540 v.this_class = cls
540 v.this_class = cls
541 super(MetaHasTraits, cls).__init__(name, bases, classdict)
541 super(MetaHasTraits, cls).__init__(name, bases, classdict)
542
542
543 class HasTraits(py3compat.with_metaclass(MetaHasTraits, object)):
543 class HasTraits(py3compat.with_metaclass(MetaHasTraits, object)):
544
544
545 def __new__(cls, *args, **kw):
545 def __new__(cls, *args, **kw):
546 # This is needed because object.__new__ only accepts
546 # This is needed because object.__new__ only accepts
547 # the cls argument.
547 # the cls argument.
548 new_meth = super(HasTraits, cls).__new__
548 new_meth = super(HasTraits, cls).__new__
549 if new_meth is object.__new__:
549 if new_meth is object.__new__:
550 inst = new_meth(cls)
550 inst = new_meth(cls)
551 else:
551 else:
552 inst = new_meth(cls, **kw)
552 inst = new_meth(cls, **kw)
553 inst._trait_values = {}
553 inst._trait_values = {}
554 inst._trait_notifiers = {}
554 inst._trait_notifiers = {}
555 inst._trait_dyn_inits = {}
555 inst._trait_dyn_inits = {}
556 # Here we tell all the TraitType instances to set their default
556 # Here we tell all the TraitType instances to set their default
557 # values on the instance.
557 # values on the instance.
558 for key in dir(cls):
558 for key in dir(cls):
559 # Some descriptors raise AttributeError like zope.interface's
559 # Some descriptors raise AttributeError like zope.interface's
560 # __provides__ attributes even though they exist. This causes
560 # __provides__ attributes even though they exist. This causes
561 # AttributeErrors even though they are listed in dir(cls).
561 # AttributeErrors even though they are listed in dir(cls).
562 try:
562 try:
563 value = getattr(cls, key)
563 value = getattr(cls, key)
564 except AttributeError:
564 except AttributeError:
565 pass
565 pass
566 else:
566 else:
567 if isinstance(value, TraitType):
567 if isinstance(value, TraitType):
568 value.instance_init()
568 value.instance_init()
569 if key not in kw:
569 if key not in kw:
570 value.set_default_value(inst)
570 value.set_default_value(inst)
571
571
572 return inst
572 return inst
573
573
574 def __init__(self, *args, **kw):
574 def __init__(self, *args, **kw):
575 # Allow trait values to be set using keyword arguments.
575 # Allow trait values to be set using keyword arguments.
576 # We need to use setattr for this to trigger validation and
576 # We need to use setattr for this to trigger validation and
577 # notifications.
577 # notifications.
578 for key, value in iteritems(kw):
578 for key, value in iteritems(kw):
579 setattr(self, key, value)
579 setattr(self, key, value)
580
580
581 def _notify_trait(self, name, old_value, new_value):
581 def _notify_trait(self, name, old_value, new_value):
582
582
583 # First dynamic ones
583 # First dynamic ones
584 callables = []
584 callables = []
585 callables.extend(self._trait_notifiers.get(name,[]))
585 callables.extend(self._trait_notifiers.get(name,[]))
586 callables.extend(self._trait_notifiers.get('anytrait',[]))
586 callables.extend(self._trait_notifiers.get('anytrait',[]))
587
587
588 # Now static ones
588 # Now static ones
589 try:
589 try:
590 cb = getattr(self, '_%s_changed' % name)
590 cb = getattr(self, '_%s_changed' % name)
591 except:
591 except:
592 pass
592 pass
593 else:
593 else:
594 callables.append(cb)
594 callables.append(cb)
595
595
596 # Call them all now
596 # Call them all now
597 for c in callables:
597 for c in callables:
598 # Traits catches and logs errors here. I allow them to raise
598 # Traits catches and logs errors here. I allow them to raise
599 if callable(c):
599 if callable(c):
600 argspec = inspect.getargspec(c)
600 argspec = inspect.getargspec(c)
601 nargs = len(argspec[0])
601 nargs = len(argspec[0])
602 # Bound methods have an additional 'self' argument
602 # Bound methods have an additional 'self' argument
603 # I don't know how to treat unbound methods, but they
603 # I don't know how to treat unbound methods, but they
604 # can't really be used for callbacks.
604 # can't really be used for callbacks.
605 if isinstance(c, types.MethodType):
605 if isinstance(c, types.MethodType):
606 offset = -1
606 offset = -1
607 else:
607 else:
608 offset = 0
608 offset = 0
609 if nargs + offset == 0:
609 if nargs + offset == 0:
610 c()
610 c()
611 elif nargs + offset == 1:
611 elif nargs + offset == 1:
612 c(name)
612 c(name)
613 elif nargs + offset == 2:
613 elif nargs + offset == 2:
614 c(name, new_value)
614 c(name, new_value)
615 elif nargs + offset == 3:
615 elif nargs + offset == 3:
616 c(name, old_value, new_value)
616 c(name, old_value, new_value)
617 else:
617 else:
618 raise TraitError('a trait changed callback '
618 raise TraitError('a trait changed callback '
619 'must have 0-3 arguments.')
619 'must have 0-3 arguments.')
620 else:
620 else:
621 raise TraitError('a trait changed callback '
621 raise TraitError('a trait changed callback '
622 'must be callable.')
622 'must be callable.')
623
623
624
624
625 def _add_notifiers(self, handler, name):
625 def _add_notifiers(self, handler, name):
626 if name not in self._trait_notifiers:
626 if name not in self._trait_notifiers:
627 nlist = []
627 nlist = []
628 self._trait_notifiers[name] = nlist
628 self._trait_notifiers[name] = nlist
629 else:
629 else:
630 nlist = self._trait_notifiers[name]
630 nlist = self._trait_notifiers[name]
631 if handler not in nlist:
631 if handler not in nlist:
632 nlist.append(handler)
632 nlist.append(handler)
633
633
634 def _remove_notifiers(self, handler, name):
634 def _remove_notifiers(self, handler, name):
635 if name in self._trait_notifiers:
635 if name in self._trait_notifiers:
636 nlist = self._trait_notifiers[name]
636 nlist = self._trait_notifiers[name]
637 try:
637 try:
638 index = nlist.index(handler)
638 index = nlist.index(handler)
639 except ValueError:
639 except ValueError:
640 pass
640 pass
641 else:
641 else:
642 del nlist[index]
642 del nlist[index]
643
643
644 def on_trait_change(self, handler, name=None, remove=False):
644 def on_trait_change(self, handler, name=None, remove=False):
645 """Setup a handler to be called when a trait changes.
645 """Setup a handler to be called when a trait changes.
646
646
647 This is used to setup dynamic notifications of trait changes.
647 This is used to setup dynamic notifications of trait changes.
648
648
649 Static handlers can be created by creating methods on a HasTraits
649 Static handlers can be created by creating methods on a HasTraits
650 subclass with the naming convention '_[traitname]_changed'. Thus,
650 subclass with the naming convention '_[traitname]_changed'. Thus,
651 to create static handler for the trait 'a', create the method
651 to create static handler for the trait 'a', create the method
652 _a_changed(self, name, old, new) (fewer arguments can be used, see
652 _a_changed(self, name, old, new) (fewer arguments can be used, see
653 below).
653 below).
654
654
655 Parameters
655 Parameters
656 ----------
656 ----------
657 handler : callable
657 handler : callable
658 A callable that is called when a trait changes. Its
658 A callable that is called when a trait changes. Its
659 signature can be handler(), handler(name), handler(name, new)
659 signature can be handler(), handler(name), handler(name, new)
660 or handler(name, old, new).
660 or handler(name, old, new).
661 name : list, str, None
661 name : list, str, None
662 If None, the handler will apply to all traits. If a list
662 If None, the handler will apply to all traits. If a list
663 of str, handler will apply to all names in the list. If a
663 of str, handler will apply to all names in the list. If a
664 str, the handler will apply just to that name.
664 str, the handler will apply just to that name.
665 remove : bool
665 remove : bool
666 If False (the default), then install the handler. If True
666 If False (the default), then install the handler. If True
667 then unintall it.
667 then unintall it.
668 """
668 """
669 if remove:
669 if remove:
670 names = parse_notifier_name(name)
670 names = parse_notifier_name(name)
671 for n in names:
671 for n in names:
672 self._remove_notifiers(handler, n)
672 self._remove_notifiers(handler, n)
673 else:
673 else:
674 names = parse_notifier_name(name)
674 names = parse_notifier_name(name)
675 for n in names:
675 for n in names:
676 self._add_notifiers(handler, n)
676 self._add_notifiers(handler, n)
677
677
678 @classmethod
678 @classmethod
679 def class_trait_names(cls, **metadata):
679 def class_trait_names(cls, **metadata):
680 """Get a list of all the names of this class' traits.
680 """Get a list of all the names of this class' traits.
681
681
682 This method is just like the :meth:`trait_names` method,
682 This method is just like the :meth:`trait_names` method,
683 but is unbound.
683 but is unbound.
684 """
684 """
685 return cls.class_traits(**metadata).keys()
685 return cls.class_traits(**metadata).keys()
686
686
687 @classmethod
687 @classmethod
688 def class_traits(cls, **metadata):
688 def class_traits(cls, **metadata):
689 """Get a `dict` of all the traits of this class. The dictionary
689 """Get a `dict` of all the traits of this class. The dictionary
690 is keyed on the name and the values are the TraitType objects.
690 is keyed on the name and the values are the TraitType objects.
691
691
692 This method is just like the :meth:`traits` method, but is unbound.
692 This method is just like the :meth:`traits` method, but is unbound.
693
693
694 The TraitTypes returned don't know anything about the values
694 The TraitTypes returned don't know anything about the values
695 that the various HasTrait's instances are holding.
695 that the various HasTrait's instances are holding.
696
696
697 The metadata kwargs allow functions to be passed in which
697 The metadata kwargs allow functions to be passed in which
698 filter traits based on metadata values. The functions should
698 filter traits based on metadata values. The functions should
699 take a single value as an argument and return a boolean. If
699 take a single value as an argument and return a boolean. If
700 any function returns False, then the trait is not included in
700 any function returns False, then the trait is not included in
701 the output. This does not allow for any simple way of
701 the output. This does not allow for any simple way of
702 testing that a metadata name exists and has any
702 testing that a metadata name exists and has any
703 value because get_metadata returns None if a metadata key
703 value because get_metadata returns None if a metadata key
704 doesn't exist.
704 doesn't exist.
705 """
705 """
706 traits = dict([memb for memb in getmembers(cls) if
706 traits = dict([memb for memb in getmembers(cls) if
707 isinstance(memb[1], TraitType)])
707 isinstance(memb[1], TraitType)])
708
708
709 if len(metadata) == 0:
709 if len(metadata) == 0:
710 return traits
710 return traits
711
711
712 for meta_name, meta_eval in metadata.items():
712 for meta_name, meta_eval in metadata.items():
713 if type(meta_eval) is not FunctionType:
713 if type(meta_eval) is not FunctionType:
714 metadata[meta_name] = _SimpleTest(meta_eval)
714 metadata[meta_name] = _SimpleTest(meta_eval)
715
715
716 result = {}
716 result = {}
717 for name, trait in traits.items():
717 for name, trait in traits.items():
718 for meta_name, meta_eval in metadata.items():
718 for meta_name, meta_eval in metadata.items():
719 if not meta_eval(trait.get_metadata(meta_name)):
719 if not meta_eval(trait.get_metadata(meta_name)):
720 break
720 break
721 else:
721 else:
722 result[name] = trait
722 result[name] = trait
723
723
724 return result
724 return result
725
725
726 def trait_names(self, **metadata):
726 def trait_names(self, **metadata):
727 """Get a list of all the names of this class' traits."""
727 """Get a list of all the names of this class' traits."""
728 return self.traits(**metadata).keys()
728 return self.traits(**metadata).keys()
729
729
730 def traits(self, **metadata):
730 def traits(self, **metadata):
731 """Get a `dict` of all the traits of this class. The dictionary
731 """Get a `dict` of all the traits of this class. The dictionary
732 is keyed on the name and the values are the TraitType objects.
732 is keyed on the name and the values are the TraitType objects.
733
733
734 The TraitTypes returned don't know anything about the values
734 The TraitTypes returned don't know anything about the values
735 that the various HasTrait's instances are holding.
735 that the various HasTrait's instances are holding.
736
736
737 The metadata kwargs allow functions to be passed in which
737 The metadata kwargs allow functions to be passed in which
738 filter traits based on metadata values. The functions should
738 filter traits based on metadata values. The functions should
739 take a single value as an argument and return a boolean. If
739 take a single value as an argument and return a boolean. If
740 any function returns False, then the trait is not included in
740 any function returns False, then the trait is not included in
741 the output. This does not allow for any simple way of
741 the output. This does not allow for any simple way of
742 testing that a metadata name exists and has any
742 testing that a metadata name exists and has any
743 value because get_metadata returns None if a metadata key
743 value because get_metadata returns None if a metadata key
744 doesn't exist.
744 doesn't exist.
745 """
745 """
746 traits = dict([memb for memb in getmembers(self.__class__) if
746 traits = dict([memb for memb in getmembers(self.__class__) if
747 isinstance(memb[1], TraitType)])
747 isinstance(memb[1], TraitType)])
748
748
749 if len(metadata) == 0:
749 if len(metadata) == 0:
750 return traits
750 return traits
751
751
752 for meta_name, meta_eval in metadata.items():
752 for meta_name, meta_eval in metadata.items():
753 if type(meta_eval) is not FunctionType:
753 if type(meta_eval) is not FunctionType:
754 metadata[meta_name] = _SimpleTest(meta_eval)
754 metadata[meta_name] = _SimpleTest(meta_eval)
755
755
756 result = {}
756 result = {}
757 for name, trait in traits.items():
757 for name, trait in traits.items():
758 for meta_name, meta_eval in metadata.items():
758 for meta_name, meta_eval in metadata.items():
759 if not meta_eval(trait.get_metadata(meta_name)):
759 if not meta_eval(trait.get_metadata(meta_name)):
760 break
760 break
761 else:
761 else:
762 result[name] = trait
762 result[name] = trait
763
763
764 return result
764 return result
765
765
766 def trait_metadata(self, traitname, key, default=None):
766 def trait_metadata(self, traitname, key, default=None):
767 """Get metadata values for trait by key."""
767 """Get metadata values for trait by key."""
768 try:
768 try:
769 trait = getattr(self.__class__, traitname)
769 trait = getattr(self.__class__, traitname)
770 except AttributeError:
770 except AttributeError:
771 raise TraitError("Class %s does not have a trait named %s" %
771 raise TraitError("Class %s does not have a trait named %s" %
772 (self.__class__.__name__, traitname))
772 (self.__class__.__name__, traitname))
773 else:
773 else:
774 return trait.get_metadata(key, default)
774 return trait.get_metadata(key, default)
775
775
776 #-----------------------------------------------------------------------------
776 #-----------------------------------------------------------------------------
777 # Actual TraitTypes implementations/subclasses
777 # Actual TraitTypes implementations/subclasses
778 #-----------------------------------------------------------------------------
778 #-----------------------------------------------------------------------------
779
779
780 #-----------------------------------------------------------------------------
780 #-----------------------------------------------------------------------------
781 # TraitTypes subclasses for handling classes and instances of classes
781 # TraitTypes subclasses for handling classes and instances of classes
782 #-----------------------------------------------------------------------------
782 #-----------------------------------------------------------------------------
783
783
784
784
785 class ClassBasedTraitType(TraitType):
785 class ClassBasedTraitType(TraitType):
786 """
786 """
787 A trait with error reporting and string -> type resolution for Type,
787 A trait with error reporting and string -> type resolution for Type,
788 Instance and This.
788 Instance and This.
789 """
789 """
790
790
791 def _resolve_string(self, string):
791 def _resolve_string(self, string):
792 """
792 """
793 Resolve a string supplied for a type into an actual object.
793 Resolve a string supplied for a type into an actual object.
794 """
794 """
795 return import_item(string)
795 return import_item(string)
796
796
797 def error(self, obj, value):
797 def error(self, obj, value):
798 kind = type(value)
798 kind = type(value)
799 if (not py3compat.PY3) and kind is InstanceType:
799 if (not py3compat.PY3) and kind is InstanceType:
800 msg = 'class %s' % value.__class__.__name__
800 msg = 'class %s' % value.__class__.__name__
801 else:
801 else:
802 msg = '%s (i.e. %s)' % ( str( kind )[1:-1], repr( value ) )
802 msg = '%s (i.e. %s)' % ( str( kind )[1:-1], repr( value ) )
803
803
804 if obj is not None:
804 if obj is not None:
805 e = "The '%s' trait of %s instance must be %s, but a value of %s was specified." \
805 e = "The '%s' trait of %s instance must be %s, but a value of %s was specified." \
806 % (self.name, class_of(obj),
806 % (self.name, class_of(obj),
807 self.info(), msg)
807 self.info(), msg)
808 else:
808 else:
809 e = "The '%s' trait must be %s, but a value of %r was specified." \
809 e = "The '%s' trait must be %s, but a value of %r was specified." \
810 % (self.name, self.info(), msg)
810 % (self.name, self.info(), msg)
811
811
812 raise TraitError(e)
812 raise TraitError(e)
813
813
814
814
815 class Type(ClassBasedTraitType):
815 class Type(ClassBasedTraitType):
816 """A trait whose value must be a subclass of a specified class."""
816 """A trait whose value must be a subclass of a specified class."""
817
817
818 def __init__ (self, default_value=None, klass=None, allow_none=True, **metadata ):
818 def __init__ (self, default_value=None, klass=None, allow_none=True, **metadata ):
819 """Construct a Type trait
819 """Construct a Type trait
820
820
821 A Type trait specifies that its values must be subclasses of
821 A Type trait specifies that its values must be subclasses of
822 a particular class.
822 a particular class.
823
823
824 If only ``default_value`` is given, it is used for the ``klass`` as
824 If only ``default_value`` is given, it is used for the ``klass`` as
825 well.
825 well.
826
826
827 Parameters
827 Parameters
828 ----------
828 ----------
829 default_value : class, str or None
829 default_value : class, str or None
830 The default value must be a subclass of klass. If an str,
830 The default value must be a subclass of klass. If an str,
831 the str must be a fully specified class name, like 'foo.bar.Bah'.
831 the str must be a fully specified class name, like 'foo.bar.Bah'.
832 The string is resolved into real class, when the parent
832 The string is resolved into real class, when the parent
833 :class:`HasTraits` class is instantiated.
833 :class:`HasTraits` class is instantiated.
834 klass : class, str, None
834 klass : class, str, None
835 Values of this trait must be a subclass of klass. The klass
835 Values of this trait must be a subclass of klass. The klass
836 may be specified in a string like: 'foo.bar.MyClass'.
836 may be specified in a string like: 'foo.bar.MyClass'.
837 The string is resolved into real class, when the parent
837 The string is resolved into real class, when the parent
838 :class:`HasTraits` class is instantiated.
838 :class:`HasTraits` class is instantiated.
839 allow_none : bool [ default True ]
839 allow_none : bool [ default True ]
840 Indicates whether None is allowed as an assignable value. Even if
840 Indicates whether None is allowed as an assignable value. Even if
841 ``False``, the default value may be ``None``.
841 ``False``, the default value may be ``None``.
842 """
842 """
843 if default_value is None:
843 if default_value is None:
844 if klass is None:
844 if klass is None:
845 klass = object
845 klass = object
846 elif klass is None:
846 elif klass is None:
847 klass = default_value
847 klass = default_value
848
848
849 if not (inspect.isclass(klass) or isinstance(klass, py3compat.string_types)):
849 if not (inspect.isclass(klass) or isinstance(klass, py3compat.string_types)):
850 raise TraitError("A Type trait must specify a class.")
850 raise TraitError("A Type trait must specify a class.")
851
851
852 self.klass = klass
852 self.klass = klass
853
853
854 super(Type, self).__init__(default_value, allow_none=allow_none, **metadata)
854 super(Type, self).__init__(default_value, allow_none=allow_none, **metadata)
855
855
856 def validate(self, obj, value):
856 def validate(self, obj, value):
857 """Validates that the value is a valid object instance."""
857 """Validates that the value is a valid object instance."""
858 if isinstance(value, py3compat.string_types):
858 if isinstance(value, py3compat.string_types):
859 try:
859 try:
860 value = self._resolve_string(value)
860 value = self._resolve_string(value)
861 except ImportError:
861 except ImportError:
862 raise TraitError("The '%s' trait of %s instance must be a type, but "
862 raise TraitError("The '%s' trait of %s instance must be a type, but "
863 "%r could not be imported" % (self.name, obj, value))
863 "%r could not be imported" % (self.name, obj, value))
864 try:
864 try:
865 if issubclass(value, self.klass):
865 if issubclass(value, self.klass):
866 return value
866 return value
867 except:
867 except:
868 pass
868 pass
869
869
870 self.error(obj, value)
870 self.error(obj, value)
871
871
872 def info(self):
872 def info(self):
873 """ Returns a description of the trait."""
873 """ Returns a description of the trait."""
874 if isinstance(self.klass, py3compat.string_types):
874 if isinstance(self.klass, py3compat.string_types):
875 klass = self.klass
875 klass = self.klass
876 else:
876 else:
877 klass = self.klass.__name__
877 klass = self.klass.__name__
878 result = 'a subclass of ' + klass
878 result = 'a subclass of ' + klass
879 if self.allow_none:
879 if self.allow_none:
880 return result + ' or None'
880 return result + ' or None'
881 return result
881 return result
882
882
883 def instance_init(self):
883 def instance_init(self):
884 self._resolve_classes()
884 self._resolve_classes()
885 super(Type, self).instance_init()
885 super(Type, self).instance_init()
886
886
887 def _resolve_classes(self):
887 def _resolve_classes(self):
888 if isinstance(self.klass, py3compat.string_types):
888 if isinstance(self.klass, py3compat.string_types):
889 self.klass = self._resolve_string(self.klass)
889 self.klass = self._resolve_string(self.klass)
890 if isinstance(self.default_value, py3compat.string_types):
890 if isinstance(self.default_value, py3compat.string_types):
891 self.default_value = self._resolve_string(self.default_value)
891 self.default_value = self._resolve_string(self.default_value)
892
892
893 def get_default_value(self):
893 def get_default_value(self):
894 return self.default_value
894 return self.default_value
895
895
896
896
897 class DefaultValueGenerator(object):
897 class DefaultValueGenerator(object):
898 """A class for generating new default value instances."""
898 """A class for generating new default value instances."""
899
899
900 def __init__(self, *args, **kw):
900 def __init__(self, *args, **kw):
901 self.args = args
901 self.args = args
902 self.kw = kw
902 self.kw = kw
903
903
904 def generate(self, klass):
904 def generate(self, klass):
905 return klass(*self.args, **self.kw)
905 return klass(*self.args, **self.kw)
906
906
907
907
908 class Instance(ClassBasedTraitType):
908 class Instance(ClassBasedTraitType):
909 """A trait whose value must be an instance of a specified class.
909 """A trait whose value must be an instance of a specified class.
910
910
911 The value can also be an instance of a subclass of the specified class.
911 The value can also be an instance of a subclass of the specified class.
912
912
913 Subclasses can declare default classes by overriding the klass attribute
913 Subclasses can declare default classes by overriding the klass attribute
914 """
914 """
915
915
916 klass = None
916 klass = None
917
917
918 def __init__(self, klass=None, args=None, kw=None,
918 def __init__(self, klass=None, args=None, kw=None,
919 allow_none=True, **metadata ):
919 allow_none=True, **metadata ):
920 """Construct an Instance trait.
920 """Construct an Instance trait.
921
921
922 This trait allows values that are instances of a particular
922 This trait allows values that are instances of a particular
923 class or its subclasses. Our implementation is quite different
923 class or its subclasses. Our implementation is quite different
924 from that of enthough.traits as we don't allow instances to be used
924 from that of enthough.traits as we don't allow instances to be used
925 for klass and we handle the ``args`` and ``kw`` arguments differently.
925 for klass and we handle the ``args`` and ``kw`` arguments differently.
926
926
927 Parameters
927 Parameters
928 ----------
928 ----------
929 klass : class, str
929 klass : class, str
930 The class that forms the basis for the trait. Class names
930 The class that forms the basis for the trait. Class names
931 can also be specified as strings, like 'foo.bar.Bar'.
931 can also be specified as strings, like 'foo.bar.Bar'.
932 args : tuple
932 args : tuple
933 Positional arguments for generating the default value.
933 Positional arguments for generating the default value.
934 kw : dict
934 kw : dict
935 Keyword arguments for generating the default value.
935 Keyword arguments for generating the default value.
936 allow_none : bool [default True]
936 allow_none : bool [default True]
937 Indicates whether None is allowed as a value.
937 Indicates whether None is allowed as a value.
938
938
939 Notes
939 Notes
940 -----
940 -----
941 If both ``args`` and ``kw`` are None, then the default value is None.
941 If both ``args`` and ``kw`` are None, then the default value is None.
942 If ``args`` is a tuple and ``kw`` is a dict, then the default is
942 If ``args`` is a tuple and ``kw`` is a dict, then the default is
943 created as ``klass(*args, **kw)``. If exactly one of ``args`` or ``kw`` is
943 created as ``klass(*args, **kw)``. If exactly one of ``args`` or ``kw`` is
944 None, the None is replaced by ``()`` or ``{}``, respectively.
944 None, the None is replaced by ``()`` or ``{}``, respectively.
945 """
945 """
946 if klass is None:
946 if klass is None:
947 klass = self.klass
947 klass = self.klass
948
948
949 if (klass is not None) and (inspect.isclass(klass) or isinstance(klass, py3compat.string_types)):
949 if (klass is not None) and (inspect.isclass(klass) or isinstance(klass, py3compat.string_types)):
950 self.klass = klass
950 self.klass = klass
951 else:
951 else:
952 raise TraitError('The klass attribute must be a class'
952 raise TraitError('The klass attribute must be a class'
953 ' not: %r' % klass)
953 ' not: %r' % klass)
954
954
955 # self.klass is a class, so handle default_value
955 # self.klass is a class, so handle default_value
956 if args is None and kw is None:
956 if args is None and kw is None:
957 default_value = None
957 default_value = None
958 else:
958 else:
959 if args is None:
959 if args is None:
960 # kw is not None
960 # kw is not None
961 args = ()
961 args = ()
962 elif kw is None:
962 elif kw is None:
963 # args is not None
963 # args is not None
964 kw = {}
964 kw = {}
965
965
966 if not isinstance(kw, dict):
966 if not isinstance(kw, dict):
967 raise TraitError("The 'kw' argument must be a dict or None.")
967 raise TraitError("The 'kw' argument must be a dict or None.")
968 if not isinstance(args, tuple):
968 if not isinstance(args, tuple):
969 raise TraitError("The 'args' argument must be a tuple or None.")
969 raise TraitError("The 'args' argument must be a tuple or None.")
970
970
971 default_value = DefaultValueGenerator(*args, **kw)
971 default_value = DefaultValueGenerator(*args, **kw)
972
972
973 super(Instance, self).__init__(default_value, allow_none=allow_none, **metadata)
973 super(Instance, self).__init__(default_value, allow_none=allow_none, **metadata)
974
974
975 def validate(self, obj, value):
975 def validate(self, obj, value):
976 if isinstance(value, self.klass):
976 if isinstance(value, self.klass):
977 return value
977 return value
978 else:
978 else:
979 self.error(obj, value)
979 self.error(obj, value)
980
980
981 def info(self):
981 def info(self):
982 if isinstance(self.klass, py3compat.string_types):
982 if isinstance(self.klass, py3compat.string_types):
983 klass = self.klass
983 klass = self.klass
984 else:
984 else:
985 klass = self.klass.__name__
985 klass = self.klass.__name__
986 result = class_of(klass)
986 result = class_of(klass)
987 if self.allow_none:
987 if self.allow_none:
988 return result + ' or None'
988 return result + ' or None'
989
989
990 return result
990 return result
991
991
992 def instance_init(self):
992 def instance_init(self):
993 self._resolve_classes()
993 self._resolve_classes()
994 super(Instance, self).instance_init()
994 super(Instance, self).instance_init()
995
995
996 def _resolve_classes(self):
996 def _resolve_classes(self):
997 if isinstance(self.klass, py3compat.string_types):
997 if isinstance(self.klass, py3compat.string_types):
998 self.klass = self._resolve_string(self.klass)
998 self.klass = self._resolve_string(self.klass)
999
999
1000 def get_default_value(self):
1000 def get_default_value(self):
1001 """Instantiate a default value instance.
1001 """Instantiate a default value instance.
1002
1002
1003 This is called when the containing HasTraits classes'
1003 This is called when the containing HasTraits classes'
1004 :meth:`__new__` method is called to ensure that a unique instance
1004 :meth:`__new__` method is called to ensure that a unique instance
1005 is created for each HasTraits instance.
1005 is created for each HasTraits instance.
1006 """
1006 """
1007 dv = self.default_value
1007 dv = self.default_value
1008 if isinstance(dv, DefaultValueGenerator):
1008 if isinstance(dv, DefaultValueGenerator):
1009 return dv.generate(self.klass)
1009 return dv.generate(self.klass)
1010 else:
1010 else:
1011 return dv
1011 return dv
1012
1012
1013
1013
1014 class ForwardDeclaredMixin(object):
1014 class ForwardDeclaredMixin(object):
1015 """
1015 """
1016 Mixin for forward-declared versions of Instance and Type.
1016 Mixin for forward-declared versions of Instance and Type.
1017 """
1017 """
1018 def _resolve_string(self, string):
1018 def _resolve_string(self, string):
1019 """
1019 """
1020 Find the specified class name by looking for it in the module in which
1020 Find the specified class name by looking for it in the module in which
1021 our this_class attribute was defined.
1021 our this_class attribute was defined.
1022 """
1022 """
1023 modname = self.this_class.__module__
1023 modname = self.this_class.__module__
1024 return import_item('.'.join([modname, string]))
1024 return import_item('.'.join([modname, string]))
1025
1025
1026
1026
1027 class ForwardDeclaredType(ForwardDeclaredMixin, Type):
1027 class ForwardDeclaredType(ForwardDeclaredMixin, Type):
1028 """
1028 """
1029 Forward-declared version of Type.
1029 Forward-declared version of Type.
1030 """
1030 """
1031 pass
1031 pass
1032
1032
1033
1033
1034 class ForwardDeclaredInstance(ForwardDeclaredMixin, Instance):
1034 class ForwardDeclaredInstance(ForwardDeclaredMixin, Instance):
1035 """
1035 """
1036 Forward-declared version of Instance.
1036 Forward-declared version of Instance.
1037 """
1037 """
1038 pass
1038 pass
1039
1039
1040
1040
1041 class This(ClassBasedTraitType):
1041 class This(ClassBasedTraitType):
1042 """A trait for instances of the class containing this trait.
1042 """A trait for instances of the class containing this trait.
1043
1043
1044 Because how how and when class bodies are executed, the ``This``
1044 Because how how and when class bodies are executed, the ``This``
1045 trait can only have a default value of None. This, and because we
1045 trait can only have a default value of None. This, and because we
1046 always validate default values, ``allow_none`` is *always* true.
1046 always validate default values, ``allow_none`` is *always* true.
1047 """
1047 """
1048
1048
1049 info_text = 'an instance of the same type as the receiver or None'
1049 info_text = 'an instance of the same type as the receiver or None'
1050
1050
1051 def __init__(self, **metadata):
1051 def __init__(self, **metadata):
1052 super(This, self).__init__(None, **metadata)
1052 super(This, self).__init__(None, **metadata)
1053
1053
1054 def validate(self, obj, value):
1054 def validate(self, obj, value):
1055 # What if value is a superclass of obj.__class__? This is
1055 # What if value is a superclass of obj.__class__? This is
1056 # complicated if it was the superclass that defined the This
1056 # complicated if it was the superclass that defined the This
1057 # trait.
1057 # trait.
1058 if isinstance(value, self.this_class) or (value is None):
1058 if isinstance(value, self.this_class) or (value is None):
1059 return value
1059 return value
1060 else:
1060 else:
1061 self.error(obj, value)
1061 self.error(obj, value)
1062
1062
1063
1063
1064 class Union(TraitType):
1064 class Union(TraitType):
1065 """A trait type representing a Union type."""
1065 """A trait type representing a Union type."""
1066
1066
1067 def __init__(self, trait_types, **metadata):
1067 def __init__(self, trait_types, **metadata):
1068 """Construct a Union trait.
1068 """Construct a Union trait.
1069
1069
1070 This trait allows values that are allowed by at least one of the
1070 This trait allows values that are allowed by at least one of the
1071 specified trait types. A Union traitlet cannot have metadata on
1071 specified trait types. A Union traitlet cannot have metadata on
1072 its own, besides the metadata of the listed types.
1072 its own, besides the metadata of the listed types.
1073
1073
1074 Parameters
1074 Parameters
1075 ----------
1075 ----------
1076 trait_types: sequence
1076 trait_types: sequence
1077 The list of trait types of length at least 1.
1077 The list of trait types of length at least 1.
1078
1078
1079 Notes
1079 Notes
1080 -----
1080 -----
1081 Union([Float(), Bool(), Int()]) attempts to validate the provided values
1081 Union([Float(), Bool(), Int()]) attempts to validate the provided values
1082 with the validation function of Float, then Bool, and finally Int.
1082 with the validation function of Float, then Bool, and finally Int.
1083 """
1083 """
1084 self.trait_types = trait_types
1084 self.trait_types = trait_types
1085 self.info_text = " or ".join([tt.info_text for tt in self.trait_types])
1085 self.info_text = " or ".join([tt.info_text for tt in self.trait_types])
1086 self.default_value = self.trait_types[0].get_default_value()
1086 self.default_value = self.trait_types[0].get_default_value()
1087 super(Union, self).__init__(**metadata)
1087 super(Union, self).__init__(**metadata)
1088
1088
1089 def instance_init(self):
1089 def instance_init(self):
1090 for trait_type in self.trait_types:
1090 for trait_type in self.trait_types:
1091 trait_type.name = self.name
1091 trait_type.name = self.name
1092 trait_type.this_class = self.this_class
1092 trait_type.this_class = self.this_class
1093 trait_type.instance_init()
1093 trait_type.instance_init()
1094 super(Union, self).instance_init()
1094 super(Union, self).instance_init()
1095
1095
1096 def validate(self, obj, value):
1096 def validate(self, obj, value):
1097 for trait_type in self.trait_types:
1097 for trait_type in self.trait_types:
1098 try:
1098 try:
1099 v = trait_type._validate(obj, value)
1099 v = trait_type._validate(obj, value)
1100 self._metadata = trait_type._metadata
1100 self._metadata = trait_type._metadata
1101 return v
1101 return v
1102 except TraitError:
1102 except TraitError:
1103 continue
1103 continue
1104 self.error(obj, value)
1104 self.error(obj, value)
1105
1105
1106 def __or__(self, other):
1106 def __or__(self, other):
1107 if isinstance(other, Union):
1107 if isinstance(other, Union):
1108 return Union(self.trait_types + other.trait_types)
1108 return Union(self.trait_types + other.trait_types)
1109 else:
1109 else:
1110 return Union(self.trait_types + [other])
1110 return Union(self.trait_types + [other])
1111
1111
1112 #-----------------------------------------------------------------------------
1112 #-----------------------------------------------------------------------------
1113 # Basic TraitTypes implementations/subclasses
1113 # Basic TraitTypes implementations/subclasses
1114 #-----------------------------------------------------------------------------
1114 #-----------------------------------------------------------------------------
1115
1115
1116
1116
1117 class Any(TraitType):
1117 class Any(TraitType):
1118 default_value = None
1118 default_value = None
1119 info_text = 'any value'
1119 info_text = 'any value'
1120
1120
1121
1121
1122 class Int(TraitType):
1122 class Int(TraitType):
1123 """An int trait."""
1123 """An int trait."""
1124
1124
1125 default_value = 0
1125 default_value = 0
1126 info_text = 'an int'
1126 info_text = 'an int'
1127
1127
1128 def validate(self, obj, value):
1128 def validate(self, obj, value):
1129 if isinstance(value, int):
1129 if isinstance(value, int):
1130 return value
1130 return value
1131 self.error(obj, value)
1131 self.error(obj, value)
1132
1132
1133 class CInt(Int):
1133 class CInt(Int):
1134 """A casting version of the int trait."""
1134 """A casting version of the int trait."""
1135
1135
1136 def validate(self, obj, value):
1136 def validate(self, obj, value):
1137 try:
1137 try:
1138 return int(value)
1138 return int(value)
1139 except:
1139 except:
1140 self.error(obj, value)
1140 self.error(obj, value)
1141
1141
1142 if py3compat.PY3:
1142 if py3compat.PY3:
1143 Long, CLong = Int, CInt
1143 Long, CLong = Int, CInt
1144 Integer = Int
1144 Integer = Int
1145 else:
1145 else:
1146 class Long(TraitType):
1146 class Long(TraitType):
1147 """A long integer trait."""
1147 """A long integer trait."""
1148
1148
1149 default_value = 0
1149 default_value = 0
1150 info_text = 'a long'
1150 info_text = 'a long'
1151
1151
1152 def validate(self, obj, value):
1152 def validate(self, obj, value):
1153 if isinstance(value, long):
1153 if isinstance(value, long):
1154 return value
1154 return value
1155 if isinstance(value, int):
1155 if isinstance(value, int):
1156 return long(value)
1156 return long(value)
1157 self.error(obj, value)
1157 self.error(obj, value)
1158
1158
1159
1159
1160 class CLong(Long):
1160 class CLong(Long):
1161 """A casting version of the long integer trait."""
1161 """A casting version of the long integer trait."""
1162
1162
1163 def validate(self, obj, value):
1163 def validate(self, obj, value):
1164 try:
1164 try:
1165 return long(value)
1165 return long(value)
1166 except:
1166 except:
1167 self.error(obj, value)
1167 self.error(obj, value)
1168
1168
1169 class Integer(TraitType):
1169 class Integer(TraitType):
1170 """An integer trait.
1170 """An integer trait.
1171
1171
1172 Longs that are unnecessary (<= sys.maxint) are cast to ints."""
1172 Longs that are unnecessary (<= sys.maxint) are cast to ints."""
1173
1173
1174 default_value = 0
1174 default_value = 0
1175 info_text = 'an integer'
1175 info_text = 'an integer'
1176
1176
1177 def validate(self, obj, value):
1177 def validate(self, obj, value):
1178 if isinstance(value, int):
1178 if isinstance(value, int):
1179 return value
1179 return value
1180 if isinstance(value, long):
1180 if isinstance(value, long):
1181 # downcast longs that fit in int:
1181 # downcast longs that fit in int:
1182 # note that int(n > sys.maxint) returns a long, so
1182 # note that int(n > sys.maxint) returns a long, so
1183 # we don't need a condition on this cast
1183 # we don't need a condition on this cast
1184 return int(value)
1184 return int(value)
1185 if sys.platform == "cli":
1185 if sys.platform == "cli":
1186 from System import Int64
1186 from System import Int64
1187 if isinstance(value, Int64):
1187 if isinstance(value, Int64):
1188 return int(value)
1188 return int(value)
1189 self.error(obj, value)
1189 self.error(obj, value)
1190
1190
1191
1191
1192 class Float(TraitType):
1192 class Float(TraitType):
1193 """A float trait."""
1193 """A float trait."""
1194
1194
1195 default_value = 0.0
1195 default_value = 0.0
1196 info_text = 'a float'
1196 info_text = 'a float'
1197
1197
1198 def validate(self, obj, value):
1198 def validate(self, obj, value):
1199 if isinstance(value, float):
1199 if isinstance(value, float):
1200 return value
1200 return value
1201 if isinstance(value, int):
1201 if isinstance(value, int):
1202 return float(value)
1202 return float(value)
1203 self.error(obj, value)
1203 self.error(obj, value)
1204
1204
1205
1205
1206 class CFloat(Float):
1206 class CFloat(Float):
1207 """A casting version of the float trait."""
1207 """A casting version of the float trait."""
1208
1208
1209 def validate(self, obj, value):
1209 def validate(self, obj, value):
1210 try:
1210 try:
1211 return float(value)
1211 return float(value)
1212 except:
1212 except:
1213 self.error(obj, value)
1213 self.error(obj, value)
1214
1214
1215 class Complex(TraitType):
1215 class Complex(TraitType):
1216 """A trait for complex numbers."""
1216 """A trait for complex numbers."""
1217
1217
1218 default_value = 0.0 + 0.0j
1218 default_value = 0.0 + 0.0j
1219 info_text = 'a complex number'
1219 info_text = 'a complex number'
1220
1220
1221 def validate(self, obj, value):
1221 def validate(self, obj, value):
1222 if isinstance(value, complex):
1222 if isinstance(value, complex):
1223 return value
1223 return value
1224 if isinstance(value, (float, int)):
1224 if isinstance(value, (float, int)):
1225 return complex(value)
1225 return complex(value)
1226 self.error(obj, value)
1226 self.error(obj, value)
1227
1227
1228
1228
1229 class CComplex(Complex):
1229 class CComplex(Complex):
1230 """A casting version of the complex number trait."""
1230 """A casting version of the complex number trait."""
1231
1231
1232 def validate (self, obj, value):
1232 def validate (self, obj, value):
1233 try:
1233 try:
1234 return complex(value)
1234 return complex(value)
1235 except:
1235 except:
1236 self.error(obj, value)
1236 self.error(obj, value)
1237
1237
1238 # We should always be explicit about whether we're using bytes or unicode, both
1238 # We should always be explicit about whether we're using bytes or unicode, both
1239 # for Python 3 conversion and for reliable unicode behaviour on Python 2. So
1239 # for Python 3 conversion and for reliable unicode behaviour on Python 2. So
1240 # we don't have a Str type.
1240 # we don't have a Str type.
1241 class Bytes(TraitType):
1241 class Bytes(TraitType):
1242 """A trait for byte strings."""
1242 """A trait for byte strings."""
1243
1243
1244 default_value = b''
1244 default_value = b''
1245 info_text = 'a bytes object'
1245 info_text = 'a bytes object'
1246
1246
1247 def validate(self, obj, value):
1247 def validate(self, obj, value):
1248 if isinstance(value, bytes):
1248 if isinstance(value, bytes):
1249 return value
1249 return value
1250 self.error(obj, value)
1250 self.error(obj, value)
1251
1251
1252
1252
1253 class CBytes(Bytes):
1253 class CBytes(Bytes):
1254 """A casting version of the byte string trait."""
1254 """A casting version of the byte string trait."""
1255
1255
1256 def validate(self, obj, value):
1256 def validate(self, obj, value):
1257 try:
1257 try:
1258 return bytes(value)
1258 return bytes(value)
1259 except:
1259 except:
1260 self.error(obj, value)
1260 self.error(obj, value)
1261
1261
1262
1262
1263 class Unicode(TraitType):
1263 class Unicode(TraitType):
1264 """A trait for unicode strings."""
1264 """A trait for unicode strings."""
1265
1265
1266 default_value = u''
1266 default_value = u''
1267 info_text = 'a unicode string'
1267 info_text = 'a unicode string'
1268
1268
1269 def validate(self, obj, value):
1269 def validate(self, obj, value):
1270 if isinstance(value, py3compat.unicode_type):
1270 if isinstance(value, py3compat.unicode_type):
1271 return value
1271 return value
1272 if isinstance(value, bytes):
1272 if isinstance(value, bytes):
1273 try:
1273 try:
1274 return value.decode('ascii', 'strict')
1274 return value.decode('ascii', 'strict')
1275 except UnicodeDecodeError:
1275 except UnicodeDecodeError:
1276 msg = "Could not decode {!r} for unicode trait '{}' of {} instance."
1276 msg = "Could not decode {!r} for unicode trait '{}' of {} instance."
1277 raise TraitError(msg.format(value, self.name, class_of(obj)))
1277 raise TraitError(msg.format(value, self.name, class_of(obj)))
1278 self.error(obj, value)
1278 self.error(obj, value)
1279
1279
1280
1280
1281 class CUnicode(Unicode):
1281 class CUnicode(Unicode):
1282 """A casting version of the unicode trait."""
1282 """A casting version of the unicode trait."""
1283
1283
1284 def validate(self, obj, value):
1284 def validate(self, obj, value):
1285 try:
1285 try:
1286 return py3compat.unicode_type(value)
1286 return py3compat.unicode_type(value)
1287 except:
1287 except:
1288 self.error(obj, value)
1288 self.error(obj, value)
1289
1289
1290
1290
1291 class _CoercedString(TraitType):
1291 class _CoercedString(TraitType):
1292
1292
1293 if py3compat.PY3:
1293 if py3compat.PY3:
1294 # Python 3:
1294 # Python 3:
1295 _coerce_str = staticmethod(lambda _,s: s)
1295 _coerce_str = staticmethod(lambda _,s: s)
1296 else:
1296 else:
1297 # Python 2:
1297 # Python 2:
1298 def _coerce_str(self, obj, value):
1298 def _coerce_str(self, obj, value):
1299 "In Python 2, coerce ascii-only unicode to str"
1299 "In Python 2, coerce ascii-only unicode to str"
1300 if isinstance(value, unicode):
1300 if isinstance(value, unicode):
1301 try:
1301 try:
1302 return str(value)
1302 return str(value)
1303 except UnicodeEncodeError:
1303 except UnicodeEncodeError:
1304 self.error(obj, value)
1304 self.error(obj, value)
1305 return value
1305 return value
1306
1306
1307
1307
1308 class ObjectName(_CoercedString):
1308 class ObjectName(_CoercedString):
1309 """A string holding a valid object name in this version of Python.
1309 """A string holding a valid object name in this version of Python.
1310
1310
1311 This does not check that the name exists in any scope."""
1311 This does not check that the name exists in any scope."""
1312
1312
1313 info_text = "a valid object identifier in Python"
1313 info_text = "a valid object identifier in Python"
1314
1314
1315 def validate(self, obj, value):
1315 def validate(self, obj, value):
1316 value = self._coerce_str(obj, value)
1316 value = self._coerce_str(obj, value)
1317
1317
1318 if isinstance(value, string_types) and py3compat.isidentifier(value):
1318 if isinstance(value, string_types) and py3compat.isidentifier(value):
1319 return value
1319 return value
1320 self.error(obj, value)
1320 self.error(obj, value)
1321
1321
1322
1322
1323 class DottedObjectName(ObjectName):
1323 class DottedObjectName(ObjectName):
1324 """A string holding a valid dotted object name in Python, such as A.b3._c"""
1324 """A string holding a valid dotted object name in Python, such as A.b3._c"""
1325
1325
1326 def validate(self, obj, value):
1326 def validate(self, obj, value):
1327 value = self._coerce_str(obj, value)
1327 value = self._coerce_str(obj, value)
1328
1328
1329 if isinstance(value, string_types) and py3compat.isidentifier(value, dotted=True):
1329 if isinstance(value, string_types) and py3compat.isidentifier(value, dotted=True):
1330 return value
1330 return value
1331 self.error(obj, value)
1331 self.error(obj, value)
1332
1332
1333
1333
1334 _color_names = ['aliceblue', 'antiquewhite', 'aqua', 'aquamarine', 'azure', 'beige', 'bisque', 'black', 'blanchedalmond', 'blue', 'blueviolet', 'brown', 'burlywood', 'cadetblue', 'chartreuse', 'chocolate', 'coral', 'cornflowerblue', 'cornsilk', 'crimson', 'cyan', 'darkblue', 'darkcyan', 'darkgoldenrod', 'darkgray', 'darkgreen', 'darkkhaki', 'darkmagenta', 'darkolivegreen', 'darkorange', 'darkorchid', 'darkred', 'darksalmon', 'darkseagreen', 'darkslateblue', 'darkslategray', 'darkturquoise', 'darkviolet', 'deeppink', 'deepskyblue', 'dimgray', 'dodgerblue', 'firebrick', 'floralwhite', 'forestgreen', 'fuchsia', 'gainsboro', 'ghostwhite', 'gold', 'goldenrod', 'gray', 'green', 'greenyellow', 'honeydew', 'hotpink', 'indianred ', 'indigo ', 'ivory', 'khaki', 'lavender', 'lavenderblush', 'lawngreen', 'lemonchiffon', 'lightblue', 'lightcoral', 'lightcyan', 'lightgoldenrodyellow', 'lightgray', 'lightgreen', 'lightpink', 'lightsalmon', 'lightseagreen', 'lightskyblue', 'lightslategray', 'lightsteelblue', 'lightyellow', 'lime', 'limegreen', 'linen', 'magenta', 'maroon', 'mediumaquamarine', 'mediumblue', 'mediumorchid', 'mediumpurple', 'mediumseagreen', 'mediumslateblue', 'mediumspringgreen', 'mediumturquoise', 'mediumvioletred', 'midnightblue', 'mintcream', 'mistyrose', 'moccasin', 'navajowhite', 'navy', 'oldlace', 'olive', 'olivedrab', 'orange', 'orangered', 'orchid', 'palegoldenrod', 'palegreen', 'paleturquoise', 'palevioletred', 'papayawhip', 'peachpuff', 'peru', 'pink', 'plum', 'powderblue', 'purple', 'rebeccapurple', 'red', 'rosybrown', 'royalblue', 'saddlebrown', 'salmon', 'sandybrown', 'seagreen', 'seashell', 'sienna', 'silver', 'skyblue', 'slateblue', 'slategray', 'snow', 'springgreen', 'steelblue', 'tan', 'teal', 'thistle', 'tomato', 'turquoise', 'violet', 'wheat', 'white', 'whitesmoke', 'yellow', 'yellowgreen']
1335 _color_re = re.compile(r'#[a-fA-F0-9]{3}(?:[a-fA-F0-9]{3})?$')
1336
1337
1338 class Color(_CoercedString):
1339 """A string holding a valid HTML color such as 'blue', '#060482', '#A80'"""
1340
1341 info_text = 'a valid HTML color'
1342
1343 def validate(self, obj, value):
1344 value = self._coerce_str(obj, value)
1345 if value.lower() in _color_names or _color_re.match(value):
1346 return value
1347 self.error(obj, value)
1348
1349
1350 class Bool(TraitType):
1334 class Bool(TraitType):
1351 """A boolean (True, False) trait."""
1335 """A boolean (True, False) trait."""
1352
1336
1353 default_value = False
1337 default_value = False
1354 info_text = 'a boolean'
1338 info_text = 'a boolean'
1355
1339
1356 def validate(self, obj, value):
1340 def validate(self, obj, value):
1357 if isinstance(value, bool):
1341 if isinstance(value, bool):
1358 return value
1342 return value
1359 self.error(obj, value)
1343 self.error(obj, value)
1360
1344
1361
1345
1362 class CBool(Bool):
1346 class CBool(Bool):
1363 """A casting version of the boolean trait."""
1347 """A casting version of the boolean trait."""
1364
1348
1365 def validate(self, obj, value):
1349 def validate(self, obj, value):
1366 try:
1350 try:
1367 return bool(value)
1351 return bool(value)
1368 except:
1352 except:
1369 self.error(obj, value)
1353 self.error(obj, value)
1370
1354
1371
1355
1372 class Enum(TraitType):
1356 class Enum(TraitType):
1373 """An enum that whose value must be in a given sequence."""
1357 """An enum that whose value must be in a given sequence."""
1374
1358
1375 def __init__(self, values, default_value=None, **metadata):
1359 def __init__(self, values, default_value=None, **metadata):
1376 self.values = values
1360 self.values = values
1377 super(Enum, self).__init__(default_value, **metadata)
1361 super(Enum, self).__init__(default_value, **metadata)
1378
1362
1379 def validate(self, obj, value):
1363 def validate(self, obj, value):
1380 if value in self.values:
1364 if value in self.values:
1381 return value
1365 return value
1382 self.error(obj, value)
1366 self.error(obj, value)
1383
1367
1384 def info(self):
1368 def info(self):
1385 """ Returns a description of the trait."""
1369 """ Returns a description of the trait."""
1386 result = 'any of ' + repr(self.values)
1370 result = 'any of ' + repr(self.values)
1387 if self.allow_none:
1371 if self.allow_none:
1388 return result + ' or None'
1372 return result + ' or None'
1389 return result
1373 return result
1390
1374
1391 class CaselessStrEnum(Enum):
1375 class CaselessStrEnum(Enum):
1392 """An enum of strings that are caseless in validate."""
1376 """An enum of strings that are caseless in validate."""
1393
1377
1394 def validate(self, obj, value):
1378 def validate(self, obj, value):
1395 if not isinstance(value, py3compat.string_types):
1379 if not isinstance(value, py3compat.string_types):
1396 self.error(obj, value)
1380 self.error(obj, value)
1397
1381
1398 for v in self.values:
1382 for v in self.values:
1399 if v.lower() == value.lower():
1383 if v.lower() == value.lower():
1400 return v
1384 return v
1401 self.error(obj, value)
1385 self.error(obj, value)
1402
1386
1403 class Container(Instance):
1387 class Container(Instance):
1404 """An instance of a container (list, set, etc.)
1388 """An instance of a container (list, set, etc.)
1405
1389
1406 To be subclassed by overriding klass.
1390 To be subclassed by overriding klass.
1407 """
1391 """
1408 klass = None
1392 klass = None
1409 _cast_types = ()
1393 _cast_types = ()
1410 _valid_defaults = SequenceTypes
1394 _valid_defaults = SequenceTypes
1411 _trait = None
1395 _trait = None
1412
1396
1413 def __init__(self, trait=None, default_value=None, allow_none=False,
1397 def __init__(self, trait=None, default_value=None, allow_none=False,
1414 **metadata):
1398 **metadata):
1415 """Create a container trait type from a list, set, or tuple.
1399 """Create a container trait type from a list, set, or tuple.
1416
1400
1417 The default value is created by doing ``List(default_value)``,
1401 The default value is created by doing ``List(default_value)``,
1418 which creates a copy of the ``default_value``.
1402 which creates a copy of the ``default_value``.
1419
1403
1420 ``trait`` can be specified, which restricts the type of elements
1404 ``trait`` can be specified, which restricts the type of elements
1421 in the container to that TraitType.
1405 in the container to that TraitType.
1422
1406
1423 If only one arg is given and it is not a Trait, it is taken as
1407 If only one arg is given and it is not a Trait, it is taken as
1424 ``default_value``:
1408 ``default_value``:
1425
1409
1426 ``c = List([1,2,3])``
1410 ``c = List([1,2,3])``
1427
1411
1428 Parameters
1412 Parameters
1429 ----------
1413 ----------
1430
1414
1431 trait : TraitType [ optional ]
1415 trait : TraitType [ optional ]
1432 the type for restricting the contents of the Container. If unspecified,
1416 the type for restricting the contents of the Container. If unspecified,
1433 types are not checked.
1417 types are not checked.
1434
1418
1435 default_value : SequenceType [ optional ]
1419 default_value : SequenceType [ optional ]
1436 The default value for the Trait. Must be list/tuple/set, and
1420 The default value for the Trait. Must be list/tuple/set, and
1437 will be cast to the container type.
1421 will be cast to the container type.
1438
1422
1439 allow_none : bool [ default False ]
1423 allow_none : bool [ default False ]
1440 Whether to allow the value to be None
1424 Whether to allow the value to be None
1441
1425
1442 **metadata : any
1426 **metadata : any
1443 further keys for extensions to the Trait (e.g. config)
1427 further keys for extensions to the Trait (e.g. config)
1444
1428
1445 """
1429 """
1446 # allow List([values]):
1430 # allow List([values]):
1447 if default_value is None and not is_trait(trait):
1431 if default_value is None and not is_trait(trait):
1448 default_value = trait
1432 default_value = trait
1449 trait = None
1433 trait = None
1450
1434
1451 if default_value is None:
1435 if default_value is None:
1452 args = ()
1436 args = ()
1453 elif isinstance(default_value, self._valid_defaults):
1437 elif isinstance(default_value, self._valid_defaults):
1454 args = (default_value,)
1438 args = (default_value,)
1455 else:
1439 else:
1456 raise TypeError('default value of %s was %s' %(self.__class__.__name__, default_value))
1440 raise TypeError('default value of %s was %s' %(self.__class__.__name__, default_value))
1457
1441
1458 if is_trait(trait):
1442 if is_trait(trait):
1459 self._trait = trait() if isinstance(trait, type) else trait
1443 self._trait = trait() if isinstance(trait, type) else trait
1460 self._trait.name = 'element'
1444 self._trait.name = 'element'
1461 elif trait is not None:
1445 elif trait is not None:
1462 raise TypeError("`trait` must be a Trait or None, got %s"%repr_type(trait))
1446 raise TypeError("`trait` must be a Trait or None, got %s"%repr_type(trait))
1463
1447
1464 super(Container,self).__init__(klass=self.klass, args=args,
1448 super(Container,self).__init__(klass=self.klass, args=args,
1465 allow_none=allow_none, **metadata)
1449 allow_none=allow_none, **metadata)
1466
1450
1467 def element_error(self, obj, element, validator):
1451 def element_error(self, obj, element, validator):
1468 e = "Element of the '%s' trait of %s instance must be %s, but a value of %s was specified." \
1452 e = "Element of the '%s' trait of %s instance must be %s, but a value of %s was specified." \
1469 % (self.name, class_of(obj), validator.info(), repr_type(element))
1453 % (self.name, class_of(obj), validator.info(), repr_type(element))
1470 raise TraitError(e)
1454 raise TraitError(e)
1471
1455
1472 def validate(self, obj, value):
1456 def validate(self, obj, value):
1473 if isinstance(value, self._cast_types):
1457 if isinstance(value, self._cast_types):
1474 value = self.klass(value)
1458 value = self.klass(value)
1475 value = super(Container, self).validate(obj, value)
1459 value = super(Container, self).validate(obj, value)
1476 if value is None:
1460 if value is None:
1477 return value
1461 return value
1478
1462
1479 value = self.validate_elements(obj, value)
1463 value = self.validate_elements(obj, value)
1480
1464
1481 return value
1465 return value
1482
1466
1483 def validate_elements(self, obj, value):
1467 def validate_elements(self, obj, value):
1484 validated = []
1468 validated = []
1485 if self._trait is None or isinstance(self._trait, Any):
1469 if self._trait is None or isinstance(self._trait, Any):
1486 return value
1470 return value
1487 for v in value:
1471 for v in value:
1488 try:
1472 try:
1489 v = self._trait._validate(obj, v)
1473 v = self._trait._validate(obj, v)
1490 except TraitError:
1474 except TraitError:
1491 self.element_error(obj, v, self._trait)
1475 self.element_error(obj, v, self._trait)
1492 else:
1476 else:
1493 validated.append(v)
1477 validated.append(v)
1494 return self.klass(validated)
1478 return self.klass(validated)
1495
1479
1496 def instance_init(self):
1480 def instance_init(self):
1497 if isinstance(self._trait, TraitType):
1481 if isinstance(self._trait, TraitType):
1498 self._trait.this_class = self.this_class
1482 self._trait.this_class = self.this_class
1499 self._trait.instance_init()
1483 self._trait.instance_init()
1500 super(Container, self).instance_init()
1484 super(Container, self).instance_init()
1501
1485
1502
1486
1503 class List(Container):
1487 class List(Container):
1504 """An instance of a Python list."""
1488 """An instance of a Python list."""
1505 klass = list
1489 klass = list
1506 _cast_types = (tuple,)
1490 _cast_types = (tuple,)
1507
1491
1508 def __init__(self, trait=None, default_value=None, minlen=0, maxlen=sys.maxsize, **metadata):
1492 def __init__(self, trait=None, default_value=None, minlen=0, maxlen=sys.maxsize, **metadata):
1509 """Create a List trait type from a list, set, or tuple.
1493 """Create a List trait type from a list, set, or tuple.
1510
1494
1511 The default value is created by doing ``List(default_value)``,
1495 The default value is created by doing ``List(default_value)``,
1512 which creates a copy of the ``default_value``.
1496 which creates a copy of the ``default_value``.
1513
1497
1514 ``trait`` can be specified, which restricts the type of elements
1498 ``trait`` can be specified, which restricts the type of elements
1515 in the container to that TraitType.
1499 in the container to that TraitType.
1516
1500
1517 If only one arg is given and it is not a Trait, it is taken as
1501 If only one arg is given and it is not a Trait, it is taken as
1518 ``default_value``:
1502 ``default_value``:
1519
1503
1520 ``c = List([1,2,3])``
1504 ``c = List([1,2,3])``
1521
1505
1522 Parameters
1506 Parameters
1523 ----------
1507 ----------
1524
1508
1525 trait : TraitType [ optional ]
1509 trait : TraitType [ optional ]
1526 the type for restricting the contents of the Container. If unspecified,
1510 the type for restricting the contents of the Container. If unspecified,
1527 types are not checked.
1511 types are not checked.
1528
1512
1529 default_value : SequenceType [ optional ]
1513 default_value : SequenceType [ optional ]
1530 The default value for the Trait. Must be list/tuple/set, and
1514 The default value for the Trait. Must be list/tuple/set, and
1531 will be cast to the container type.
1515 will be cast to the container type.
1532
1516
1533 minlen : Int [ default 0 ]
1517 minlen : Int [ default 0 ]
1534 The minimum length of the input list
1518 The minimum length of the input list
1535
1519
1536 maxlen : Int [ default sys.maxsize ]
1520 maxlen : Int [ default sys.maxsize ]
1537 The maximum length of the input list
1521 The maximum length of the input list
1538
1522
1539 allow_none : bool [ default False ]
1523 allow_none : bool [ default False ]
1540 Whether to allow the value to be None
1524 Whether to allow the value to be None
1541
1525
1542 **metadata : any
1526 **metadata : any
1543 further keys for extensions to the Trait (e.g. config)
1527 further keys for extensions to the Trait (e.g. config)
1544
1528
1545 """
1529 """
1546 self._minlen = minlen
1530 self._minlen = minlen
1547 self._maxlen = maxlen
1531 self._maxlen = maxlen
1548 super(List, self).__init__(trait=trait, default_value=default_value,
1532 super(List, self).__init__(trait=trait, default_value=default_value,
1549 **metadata)
1533 **metadata)
1550
1534
1551 def length_error(self, obj, value):
1535 def length_error(self, obj, value):
1552 e = "The '%s' trait of %s instance must be of length %i <= L <= %i, but a value of %s was specified." \
1536 e = "The '%s' trait of %s instance must be of length %i <= L <= %i, but a value of %s was specified." \
1553 % (self.name, class_of(obj), self._minlen, self._maxlen, value)
1537 % (self.name, class_of(obj), self._minlen, self._maxlen, value)
1554 raise TraitError(e)
1538 raise TraitError(e)
1555
1539
1556 def validate_elements(self, obj, value):
1540 def validate_elements(self, obj, value):
1557 length = len(value)
1541 length = len(value)
1558 if length < self._minlen or length > self._maxlen:
1542 if length < self._minlen or length > self._maxlen:
1559 self.length_error(obj, value)
1543 self.length_error(obj, value)
1560
1544
1561 return super(List, self).validate_elements(obj, value)
1545 return super(List, self).validate_elements(obj, value)
1562
1546
1563 def validate(self, obj, value):
1547 def validate(self, obj, value):
1564 value = super(List, self).validate(obj, value)
1548 value = super(List, self).validate(obj, value)
1565 value = self.validate_elements(obj, value)
1549 value = self.validate_elements(obj, value)
1566 return value
1550 return value
1567
1551
1568
1552
1569 class Set(List):
1553 class Set(List):
1570 """An instance of a Python set."""
1554 """An instance of a Python set."""
1571 klass = set
1555 klass = set
1572 _cast_types = (tuple, list)
1556 _cast_types = (tuple, list)
1573
1557
1574
1558
1575 class Tuple(Container):
1559 class Tuple(Container):
1576 """An instance of a Python tuple."""
1560 """An instance of a Python tuple."""
1577 klass = tuple
1561 klass = tuple
1578 _cast_types = (list,)
1562 _cast_types = (list,)
1579
1563
1580 def __init__(self, *traits, **metadata):
1564 def __init__(self, *traits, **metadata):
1581 """Tuple(*traits, default_value=None, **medatata)
1565 """Tuple(*traits, default_value=None, **medatata)
1582
1566
1583 Create a tuple from a list, set, or tuple.
1567 Create a tuple from a list, set, or tuple.
1584
1568
1585 Create a fixed-type tuple with Traits:
1569 Create a fixed-type tuple with Traits:
1586
1570
1587 ``t = Tuple(Int, Str, CStr)``
1571 ``t = Tuple(Int, Str, CStr)``
1588
1572
1589 would be length 3, with Int,Str,CStr for each element.
1573 would be length 3, with Int,Str,CStr for each element.
1590
1574
1591 If only one arg is given and it is not a Trait, it is taken as
1575 If only one arg is given and it is not a Trait, it is taken as
1592 default_value:
1576 default_value:
1593
1577
1594 ``t = Tuple((1,2,3))``
1578 ``t = Tuple((1,2,3))``
1595
1579
1596 Otherwise, ``default_value`` *must* be specified by keyword.
1580 Otherwise, ``default_value`` *must* be specified by keyword.
1597
1581
1598 Parameters
1582 Parameters
1599 ----------
1583 ----------
1600
1584
1601 *traits : TraitTypes [ optional ]
1585 *traits : TraitTypes [ optional ]
1602 the tsype for restricting the contents of the Tuple. If unspecified,
1586 the tsype for restricting the contents of the Tuple. If unspecified,
1603 types are not checked. If specified, then each positional argument
1587 types are not checked. If specified, then each positional argument
1604 corresponds to an element of the tuple. Tuples defined with traits
1588 corresponds to an element of the tuple. Tuples defined with traits
1605 are of fixed length.
1589 are of fixed length.
1606
1590
1607 default_value : SequenceType [ optional ]
1591 default_value : SequenceType [ optional ]
1608 The default value for the Tuple. Must be list/tuple/set, and
1592 The default value for the Tuple. Must be list/tuple/set, and
1609 will be cast to a tuple. If `traits` are specified, the
1593 will be cast to a tuple. If `traits` are specified, the
1610 `default_value` must conform to the shape and type they specify.
1594 `default_value` must conform to the shape and type they specify.
1611
1595
1612 allow_none : bool [ default False ]
1596 allow_none : bool [ default False ]
1613 Whether to allow the value to be None
1597 Whether to allow the value to be None
1614
1598
1615 **metadata : any
1599 **metadata : any
1616 further keys for extensions to the Trait (e.g. config)
1600 further keys for extensions to the Trait (e.g. config)
1617
1601
1618 """
1602 """
1619 default_value = metadata.pop('default_value', None)
1603 default_value = metadata.pop('default_value', None)
1620 allow_none = metadata.pop('allow_none', True)
1604 allow_none = metadata.pop('allow_none', True)
1621
1605
1622 # allow Tuple((values,)):
1606 # allow Tuple((values,)):
1623 if len(traits) == 1 and default_value is None and not is_trait(traits[0]):
1607 if len(traits) == 1 and default_value is None and not is_trait(traits[0]):
1624 default_value = traits[0]
1608 default_value = traits[0]
1625 traits = ()
1609 traits = ()
1626
1610
1627 if default_value is None:
1611 if default_value is None:
1628 args = ()
1612 args = ()
1629 elif isinstance(default_value, self._valid_defaults):
1613 elif isinstance(default_value, self._valid_defaults):
1630 args = (default_value,)
1614 args = (default_value,)
1631 else:
1615 else:
1632 raise TypeError('default value of %s was %s' %(self.__class__.__name__, default_value))
1616 raise TypeError('default value of %s was %s' %(self.__class__.__name__, default_value))
1633
1617
1634 self._traits = []
1618 self._traits = []
1635 for trait in traits:
1619 for trait in traits:
1636 t = trait() if isinstance(trait, type) else trait
1620 t = trait() if isinstance(trait, type) else trait
1637 t.name = 'element'
1621 t.name = 'element'
1638 self._traits.append(t)
1622 self._traits.append(t)
1639
1623
1640 if self._traits and default_value is None:
1624 if self._traits and default_value is None:
1641 # don't allow default to be an empty container if length is specified
1625 # don't allow default to be an empty container if length is specified
1642 args = None
1626 args = None
1643 super(Container,self).__init__(klass=self.klass, args=args, **metadata)
1627 super(Container,self).__init__(klass=self.klass, args=args, **metadata)
1644
1628
1645 def validate_elements(self, obj, value):
1629 def validate_elements(self, obj, value):
1646 if not self._traits:
1630 if not self._traits:
1647 # nothing to validate
1631 # nothing to validate
1648 return value
1632 return value
1649 if len(value) != len(self._traits):
1633 if len(value) != len(self._traits):
1650 e = "The '%s' trait of %s instance requires %i elements, but a value of %s was specified." \
1634 e = "The '%s' trait of %s instance requires %i elements, but a value of %s was specified." \
1651 % (self.name, class_of(obj), len(self._traits), repr_type(value))
1635 % (self.name, class_of(obj), len(self._traits), repr_type(value))
1652 raise TraitError(e)
1636 raise TraitError(e)
1653
1637
1654 validated = []
1638 validated = []
1655 for t, v in zip(self._traits, value):
1639 for t, v in zip(self._traits, value):
1656 try:
1640 try:
1657 v = t._validate(obj, v)
1641 v = t._validate(obj, v)
1658 except TraitError:
1642 except TraitError:
1659 self.element_error(obj, v, t)
1643 self.element_error(obj, v, t)
1660 else:
1644 else:
1661 validated.append(v)
1645 validated.append(v)
1662 return tuple(validated)
1646 return tuple(validated)
1663
1647
1664 def instance_init(self):
1648 def instance_init(self):
1665 for trait in self._traits:
1649 for trait in self._traits:
1666 if isinstance(trait, TraitType):
1650 if isinstance(trait, TraitType):
1667 trait.this_class = self.this_class
1651 trait.this_class = self.this_class
1668 trait.instance_init()
1652 trait.instance_init()
1669 super(Container, self).instance_init()
1653 super(Container, self).instance_init()
1670
1654
1671
1655
1672 class Dict(Instance):
1656 class Dict(Instance):
1673 """An instance of a Python dict."""
1657 """An instance of a Python dict."""
1674 _trait = None
1658 _trait = None
1675
1659
1676 def __init__(self, trait=None, default_value=NoDefaultSpecified, allow_none=False, **metadata):
1660 def __init__(self, trait=None, default_value=NoDefaultSpecified, allow_none=False, **metadata):
1677 """Create a dict trait type from a dict.
1661 """Create a dict trait type from a dict.
1678
1662
1679 The default value is created by doing ``dict(default_value)``,
1663 The default value is created by doing ``dict(default_value)``,
1680 which creates a copy of the ``default_value``.
1664 which creates a copy of the ``default_value``.
1681
1665
1682 trait : TraitType [ optional ]
1666 trait : TraitType [ optional ]
1683 the type for restricting the contents of the Container. If unspecified,
1667 the type for restricting the contents of the Container. If unspecified,
1684 types are not checked.
1668 types are not checked.
1685
1669
1686 default_value : SequenceType [ optional ]
1670 default_value : SequenceType [ optional ]
1687 The default value for the Dict. Must be dict, tuple, or None, and
1671 The default value for the Dict. Must be dict, tuple, or None, and
1688 will be cast to a dict if not None. If `trait` is specified, the
1672 will be cast to a dict if not None. If `trait` is specified, the
1689 `default_value` must conform to the constraints it specifies.
1673 `default_value` must conform to the constraints it specifies.
1690
1674
1691 allow_none : bool [ default False ]
1675 allow_none : bool [ default False ]
1692 Whether to allow the value to be None
1676 Whether to allow the value to be None
1693
1677
1694 """
1678 """
1695 if default_value is NoDefaultSpecified and trait is not None:
1679 if default_value is NoDefaultSpecified and trait is not None:
1696 if not is_trait(trait):
1680 if not is_trait(trait):
1697 default_value = trait
1681 default_value = trait
1698 trait = None
1682 trait = None
1699 if default_value is NoDefaultSpecified:
1683 if default_value is NoDefaultSpecified:
1700 default_value = {}
1684 default_value = {}
1701 if default_value is None:
1685 if default_value is None:
1702 args = None
1686 args = None
1703 elif isinstance(default_value, dict):
1687 elif isinstance(default_value, dict):
1704 args = (default_value,)
1688 args = (default_value,)
1705 elif isinstance(default_value, SequenceTypes):
1689 elif isinstance(default_value, SequenceTypes):
1706 args = (default_value,)
1690 args = (default_value,)
1707 else:
1691 else:
1708 raise TypeError('default value of Dict was %s' % default_value)
1692 raise TypeError('default value of Dict was %s' % default_value)
1709
1693
1710 if is_trait(trait):
1694 if is_trait(trait):
1711 self._trait = trait() if isinstance(trait, type) else trait
1695 self._trait = trait() if isinstance(trait, type) else trait
1712 self._trait.name = 'element'
1696 self._trait.name = 'element'
1713 elif trait is not None:
1697 elif trait is not None:
1714 raise TypeError("`trait` must be a Trait or None, got %s"%repr_type(trait))
1698 raise TypeError("`trait` must be a Trait or None, got %s"%repr_type(trait))
1715
1699
1716 super(Dict,self).__init__(klass=dict, args=args,
1700 super(Dict,self).__init__(klass=dict, args=args,
1717 allow_none=allow_none, **metadata)
1701 allow_none=allow_none, **metadata)
1718
1702
1719 def element_error(self, obj, element, validator):
1703 def element_error(self, obj, element, validator):
1720 e = "Element of the '%s' trait of %s instance must be %s, but a value of %s was specified." \
1704 e = "Element of the '%s' trait of %s instance must be %s, but a value of %s was specified." \
1721 % (self.name, class_of(obj), validator.info(), repr_type(element))
1705 % (self.name, class_of(obj), validator.info(), repr_type(element))
1722 raise TraitError(e)
1706 raise TraitError(e)
1723
1707
1724 def validate(self, obj, value):
1708 def validate(self, obj, value):
1725 value = super(Dict, self).validate(obj, value)
1709 value = super(Dict, self).validate(obj, value)
1726 if value is None:
1710 if value is None:
1727 return value
1711 return value
1728 value = self.validate_elements(obj, value)
1712 value = self.validate_elements(obj, value)
1729 return value
1713 return value
1730
1714
1731 def validate_elements(self, obj, value):
1715 def validate_elements(self, obj, value):
1732 if self._trait is None or isinstance(self._trait, Any):
1716 if self._trait is None or isinstance(self._trait, Any):
1733 return value
1717 return value
1734 validated = {}
1718 validated = {}
1735 for key in value:
1719 for key in value:
1736 v = value[key]
1720 v = value[key]
1737 try:
1721 try:
1738 v = self._trait._validate(obj, v)
1722 v = self._trait._validate(obj, v)
1739 except TraitError:
1723 except TraitError:
1740 self.element_error(obj, v, self._trait)
1724 self.element_error(obj, v, self._trait)
1741 else:
1725 else:
1742 validated[key] = v
1726 validated[key] = v
1743 return self.klass(validated)
1727 return self.klass(validated)
1744
1728
1745 def instance_init(self):
1729 def instance_init(self):
1746 if isinstance(self._trait, TraitType):
1730 if isinstance(self._trait, TraitType):
1747 self._trait.this_class = self.this_class
1731 self._trait.this_class = self.this_class
1748 self._trait.instance_init()
1732 self._trait.instance_init()
1749 super(Dict, self).instance_init()
1733 super(Dict, self).instance_init()
1750
1734
1751
1735
1752 class EventfulDict(Instance):
1736 class EventfulDict(Instance):
1753 """An instance of an EventfulDict."""
1737 """An instance of an EventfulDict."""
1754
1738
1755 def __init__(self, default_value={}, allow_none=False, **metadata):
1739 def __init__(self, default_value={}, allow_none=False, **metadata):
1756 """Create a EventfulDict trait type from a dict.
1740 """Create a EventfulDict trait type from a dict.
1757
1741
1758 The default value is created by doing
1742 The default value is created by doing
1759 ``eventful.EvenfulDict(default_value)``, which creates a copy of the
1743 ``eventful.EvenfulDict(default_value)``, which creates a copy of the
1760 ``default_value``.
1744 ``default_value``.
1761 """
1745 """
1762 if default_value is None:
1746 if default_value is None:
1763 args = None
1747 args = None
1764 elif isinstance(default_value, dict):
1748 elif isinstance(default_value, dict):
1765 args = (default_value,)
1749 args = (default_value,)
1766 elif isinstance(default_value, SequenceTypes):
1750 elif isinstance(default_value, SequenceTypes):
1767 args = (default_value,)
1751 args = (default_value,)
1768 else:
1752 else:
1769 raise TypeError('default value of EventfulDict was %s' % default_value)
1753 raise TypeError('default value of EventfulDict was %s' % default_value)
1770
1754
1771 super(EventfulDict, self).__init__(klass=eventful.EventfulDict, args=args,
1755 super(EventfulDict, self).__init__(klass=eventful.EventfulDict, args=args,
1772 allow_none=allow_none, **metadata)
1756 allow_none=allow_none, **metadata)
1773
1757
1774
1758
1775 class EventfulList(Instance):
1759 class EventfulList(Instance):
1776 """An instance of an EventfulList."""
1760 """An instance of an EventfulList."""
1777
1761
1778 def __init__(self, default_value=None, allow_none=False, **metadata):
1762 def __init__(self, default_value=None, allow_none=False, **metadata):
1779 """Create a EventfulList trait type from a dict.
1763 """Create a EventfulList trait type from a dict.
1780
1764
1781 The default value is created by doing
1765 The default value is created by doing
1782 ``eventful.EvenfulList(default_value)``, which creates a copy of the
1766 ``eventful.EvenfulList(default_value)``, which creates a copy of the
1783 ``default_value``.
1767 ``default_value``.
1784 """
1768 """
1785 if default_value is None:
1769 if default_value is None:
1786 args = ((),)
1770 args = ((),)
1787 else:
1771 else:
1788 args = (default_value,)
1772 args = (default_value,)
1789
1773
1790 super(EventfulList, self).__init__(klass=eventful.EventfulList, args=args,
1774 super(EventfulList, self).__init__(klass=eventful.EventfulList, args=args,
1791 allow_none=allow_none, **metadata)
1775 allow_none=allow_none, **metadata)
1792
1776
1793
1777
1794 class TCPAddress(TraitType):
1778 class TCPAddress(TraitType):
1795 """A trait for an (ip, port) tuple.
1779 """A trait for an (ip, port) tuple.
1796
1780
1797 This allows for both IPv4 IP addresses as well as hostnames.
1781 This allows for both IPv4 IP addresses as well as hostnames.
1798 """
1782 """
1799
1783
1800 default_value = ('127.0.0.1', 0)
1784 default_value = ('127.0.0.1', 0)
1801 info_text = 'an (ip, port) tuple'
1785 info_text = 'an (ip, port) tuple'
1802
1786
1803 def validate(self, obj, value):
1787 def validate(self, obj, value):
1804 if isinstance(value, tuple):
1788 if isinstance(value, tuple):
1805 if len(value) == 2:
1789 if len(value) == 2:
1806 if isinstance(value[0], py3compat.string_types) and isinstance(value[1], int):
1790 if isinstance(value[0], py3compat.string_types) and isinstance(value[1], int):
1807 port = value[1]
1791 port = value[1]
1808 if port >= 0 and port <= 65535:
1792 if port >= 0 and port <= 65535:
1809 return value
1793 return value
1810 self.error(obj, value)
1794 self.error(obj, value)
1811
1795
1812 class CRegExp(TraitType):
1796 class CRegExp(TraitType):
1813 """A casting compiled regular expression trait.
1797 """A casting compiled regular expression trait.
1814
1798
1815 Accepts both strings and compiled regular expressions. The resulting
1799 Accepts both strings and compiled regular expressions. The resulting
1816 attribute will be a compiled regular expression."""
1800 attribute will be a compiled regular expression."""
1817
1801
1818 info_text = 'a regular expression'
1802 info_text = 'a regular expression'
1819
1803
1820 def validate(self, obj, value):
1804 def validate(self, obj, value):
1821 try:
1805 try:
1822 return re.compile(value)
1806 return re.compile(value)
1823 except:
1807 except:
1824 self.error(obj, value)
1808 self.error(obj, value)
General Comments 0
You need to be logged in to leave comments. Login now