##// END OF EJS Templates
Fixes for parallel code on Python 3.
Thomas Kluyver -
Show More
@@ -1,445 +1,445
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 """
3 """
4 The IPython controller application.
4 The IPython controller application.
5
5
6 Authors:
6 Authors:
7
7
8 * Brian Granger
8 * Brian Granger
9 * MinRK
9 * MinRK
10
10
11 """
11 """
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Copyright (C) 2008-2011 The IPython Development Team
14 # Copyright (C) 2008-2011 The IPython Development Team
15 #
15 #
16 # Distributed under the terms of the BSD License. The full license is in
16 # Distributed under the terms of the BSD License. The full license is in
17 # the file COPYING, distributed as part of this software.
17 # the file COPYING, distributed as part of this software.
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21 # Imports
21 # Imports
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23
23
24 from __future__ import with_statement
24 from __future__ import with_statement
25
25
26 import os
26 import os
27 import socket
27 import socket
28 import stat
28 import stat
29 import sys
29 import sys
30
30
31 from multiprocessing import Process
31 from multiprocessing import Process
32
32
33 import zmq
33 import zmq
34 from zmq.devices import ProcessMonitoredQueue
34 from zmq.devices import ProcessMonitoredQueue
35 from zmq.log.handlers import PUBHandler
35 from zmq.log.handlers import PUBHandler
36 from zmq.utils import jsonapi as json
36 from zmq.utils import jsonapi as json
37
37
38 from IPython.core.profiledir import ProfileDir
38 from IPython.core.profiledir import ProfileDir
39
39
40 from IPython.parallel.apps.baseapp import (
40 from IPython.parallel.apps.baseapp import (
41 BaseParallelApplication,
41 BaseParallelApplication,
42 base_aliases,
42 base_aliases,
43 base_flags,
43 base_flags,
44 catch_config_error,
44 catch_config_error,
45 )
45 )
46 from IPython.utils.importstring import import_item
46 from IPython.utils.importstring import import_item
47 from IPython.utils.traitlets import Instance, Unicode, Bool, List, Dict, TraitError
47 from IPython.utils.traitlets import Instance, Unicode, Bool, List, Dict, TraitError
48
48
49 from IPython.zmq.session import (
49 from IPython.zmq.session import (
50 Session, session_aliases, session_flags, default_secure
50 Session, session_aliases, session_flags, default_secure
51 )
51 )
52
52
53 from IPython.parallel.controller.heartmonitor import HeartMonitor
53 from IPython.parallel.controller.heartmonitor import HeartMonitor
54 from IPython.parallel.controller.hub import HubFactory
54 from IPython.parallel.controller.hub import HubFactory
55 from IPython.parallel.controller.scheduler import TaskScheduler,launch_scheduler
55 from IPython.parallel.controller.scheduler import TaskScheduler,launch_scheduler
56 from IPython.parallel.controller.sqlitedb import SQLiteDB
56 from IPython.parallel.controller.sqlitedb import SQLiteDB
57
57
58 from IPython.parallel.util import signal_children, split_url, asbytes, disambiguate_url
58 from IPython.parallel.util import signal_children, split_url, asbytes, disambiguate_url
59
59
60 # conditional import of MongoDB backend class
60 # conditional import of MongoDB backend class
61
61
62 try:
62 try:
63 from IPython.parallel.controller.mongodb import MongoDB
63 from IPython.parallel.controller.mongodb import MongoDB
64 except ImportError:
64 except ImportError:
65 maybe_mongo = []
65 maybe_mongo = []
66 else:
66 else:
67 maybe_mongo = [MongoDB]
67 maybe_mongo = [MongoDB]
68
68
69
69
70 #-----------------------------------------------------------------------------
70 #-----------------------------------------------------------------------------
71 # Module level variables
71 # Module level variables
72 #-----------------------------------------------------------------------------
72 #-----------------------------------------------------------------------------
73
73
74
74
75 #: The default config file name for this application
75 #: The default config file name for this application
76 default_config_file_name = u'ipcontroller_config.py'
76 default_config_file_name = u'ipcontroller_config.py'
77
77
78
78
79 _description = """Start the IPython controller for parallel computing.
79 _description = """Start the IPython controller for parallel computing.
80
80
81 The IPython controller provides a gateway between the IPython engines and
81 The IPython controller provides a gateway between the IPython engines and
82 clients. The controller needs to be started before the engines and can be
82 clients. The controller needs to be started before the engines and can be
83 configured using command line options or using a cluster directory. Cluster
83 configured using command line options or using a cluster directory. Cluster
84 directories contain config, log and security files and are usually located in
84 directories contain config, log and security files and are usually located in
85 your ipython directory and named as "profile_name". See the `profile`
85 your ipython directory and named as "profile_name". See the `profile`
86 and `profile-dir` options for details.
86 and `profile-dir` options for details.
87 """
87 """
88
88
89 _examples = """
89 _examples = """
90 ipcontroller --ip=192.168.0.1 --port=1000 # listen on ip, port for engines
90 ipcontroller --ip=192.168.0.1 --port=1000 # listen on ip, port for engines
91 ipcontroller --scheme=pure # use the pure zeromq scheduler
91 ipcontroller --scheme=pure # use the pure zeromq scheduler
92 """
92 """
93
93
94
94
95 #-----------------------------------------------------------------------------
95 #-----------------------------------------------------------------------------
96 # The main application
96 # The main application
97 #-----------------------------------------------------------------------------
97 #-----------------------------------------------------------------------------
98 flags = {}
98 flags = {}
99 flags.update(base_flags)
99 flags.update(base_flags)
100 flags.update({
100 flags.update({
101 'usethreads' : ( {'IPControllerApp' : {'use_threads' : True}},
101 'usethreads' : ( {'IPControllerApp' : {'use_threads' : True}},
102 'Use threads instead of processes for the schedulers'),
102 'Use threads instead of processes for the schedulers'),
103 'sqlitedb' : ({'HubFactory' : {'db_class' : 'IPython.parallel.controller.sqlitedb.SQLiteDB'}},
103 'sqlitedb' : ({'HubFactory' : {'db_class' : 'IPython.parallel.controller.sqlitedb.SQLiteDB'}},
104 'use the SQLiteDB backend'),
104 'use the SQLiteDB backend'),
105 'mongodb' : ({'HubFactory' : {'db_class' : 'IPython.parallel.controller.mongodb.MongoDB'}},
105 'mongodb' : ({'HubFactory' : {'db_class' : 'IPython.parallel.controller.mongodb.MongoDB'}},
106 'use the MongoDB backend'),
106 'use the MongoDB backend'),
107 'dictdb' : ({'HubFactory' : {'db_class' : 'IPython.parallel.controller.dictdb.DictDB'}},
107 'dictdb' : ({'HubFactory' : {'db_class' : 'IPython.parallel.controller.dictdb.DictDB'}},
108 'use the in-memory DictDB backend'),
108 'use the in-memory DictDB backend'),
109 'reuse' : ({'IPControllerApp' : {'reuse_files' : True}},
109 'reuse' : ({'IPControllerApp' : {'reuse_files' : True}},
110 'reuse existing json connection files')
110 'reuse existing json connection files')
111 })
111 })
112
112
113 flags.update(session_flags)
113 flags.update(session_flags)
114
114
115 aliases = dict(
115 aliases = dict(
116 ssh = 'IPControllerApp.ssh_server',
116 ssh = 'IPControllerApp.ssh_server',
117 enginessh = 'IPControllerApp.engine_ssh_server',
117 enginessh = 'IPControllerApp.engine_ssh_server',
118 location = 'IPControllerApp.location',
118 location = 'IPControllerApp.location',
119
119
120 url = 'HubFactory.url',
120 url = 'HubFactory.url',
121 ip = 'HubFactory.ip',
121 ip = 'HubFactory.ip',
122 transport = 'HubFactory.transport',
122 transport = 'HubFactory.transport',
123 port = 'HubFactory.regport',
123 port = 'HubFactory.regport',
124
124
125 ping = 'HeartMonitor.period',
125 ping = 'HeartMonitor.period',
126
126
127 scheme = 'TaskScheduler.scheme_name',
127 scheme = 'TaskScheduler.scheme_name',
128 hwm = 'TaskScheduler.hwm',
128 hwm = 'TaskScheduler.hwm',
129 )
129 )
130 aliases.update(base_aliases)
130 aliases.update(base_aliases)
131 aliases.update(session_aliases)
131 aliases.update(session_aliases)
132
132
133
133
134 class IPControllerApp(BaseParallelApplication):
134 class IPControllerApp(BaseParallelApplication):
135
135
136 name = u'ipcontroller'
136 name = u'ipcontroller'
137 description = _description
137 description = _description
138 examples = _examples
138 examples = _examples
139 config_file_name = Unicode(default_config_file_name)
139 config_file_name = Unicode(default_config_file_name)
140 classes = [ProfileDir, Session, HubFactory, TaskScheduler, HeartMonitor, SQLiteDB] + maybe_mongo
140 classes = [ProfileDir, Session, HubFactory, TaskScheduler, HeartMonitor, SQLiteDB] + maybe_mongo
141
141
142 # change default to True
142 # change default to True
143 auto_create = Bool(True, config=True,
143 auto_create = Bool(True, config=True,
144 help="""Whether to create profile dir if it doesn't exist.""")
144 help="""Whether to create profile dir if it doesn't exist.""")
145
145
146 reuse_files = Bool(False, config=True,
146 reuse_files = Bool(False, config=True,
147 help='Whether to reuse existing json connection files.'
147 help='Whether to reuse existing json connection files.'
148 )
148 )
149 ssh_server = Unicode(u'', config=True,
149 ssh_server = Unicode(u'', config=True,
150 help="""ssh url for clients to use when connecting to the Controller
150 help="""ssh url for clients to use when connecting to the Controller
151 processes. It should be of the form: [user@]server[:port]. The
151 processes. It should be of the form: [user@]server[:port]. The
152 Controller's listening addresses must be accessible from the ssh server""",
152 Controller's listening addresses must be accessible from the ssh server""",
153 )
153 )
154 engine_ssh_server = Unicode(u'', config=True,
154 engine_ssh_server = Unicode(u'', config=True,
155 help="""ssh url for engines to use when connecting to the Controller
155 help="""ssh url for engines to use when connecting to the Controller
156 processes. It should be of the form: [user@]server[:port]. The
156 processes. It should be of the form: [user@]server[:port]. The
157 Controller's listening addresses must be accessible from the ssh server""",
157 Controller's listening addresses must be accessible from the ssh server""",
158 )
158 )
159 location = Unicode(u'', config=True,
159 location = Unicode(u'', config=True,
160 help="""The external IP or domain name of the Controller, used for disambiguating
160 help="""The external IP or domain name of the Controller, used for disambiguating
161 engine and client connections.""",
161 engine and client connections.""",
162 )
162 )
163 import_statements = List([], config=True,
163 import_statements = List([], config=True,
164 help="import statements to be run at startup. Necessary in some environments"
164 help="import statements to be run at startup. Necessary in some environments"
165 )
165 )
166
166
167 use_threads = Bool(False, config=True,
167 use_threads = Bool(False, config=True,
168 help='Use threads instead of processes for the schedulers',
168 help='Use threads instead of processes for the schedulers',
169 )
169 )
170
170
171 engine_json_file = Unicode('ipcontroller-engine.json', config=True,
171 engine_json_file = Unicode('ipcontroller-engine.json', config=True,
172 help="JSON filename where engine connection info will be stored.")
172 help="JSON filename where engine connection info will be stored.")
173 client_json_file = Unicode('ipcontroller-client.json', config=True,
173 client_json_file = Unicode('ipcontroller-client.json', config=True,
174 help="JSON filename where client connection info will be stored.")
174 help="JSON filename where client connection info will be stored.")
175
175
176 def _cluster_id_changed(self, name, old, new):
176 def _cluster_id_changed(self, name, old, new):
177 super(IPControllerApp, self)._cluster_id_changed(name, old, new)
177 super(IPControllerApp, self)._cluster_id_changed(name, old, new)
178 self.engine_json_file = "%s-engine.json" % self.name
178 self.engine_json_file = "%s-engine.json" % self.name
179 self.client_json_file = "%s-client.json" % self.name
179 self.client_json_file = "%s-client.json" % self.name
180
180
181
181
182 # internal
182 # internal
183 children = List()
183 children = List()
184 mq_class = Unicode('zmq.devices.ProcessMonitoredQueue')
184 mq_class = Unicode('zmq.devices.ProcessMonitoredQueue')
185
185
186 def _use_threads_changed(self, name, old, new):
186 def _use_threads_changed(self, name, old, new):
187 self.mq_class = 'zmq.devices.%sMonitoredQueue'%('Thread' if new else 'Process')
187 self.mq_class = 'zmq.devices.%sMonitoredQueue'%('Thread' if new else 'Process')
188
188
189 aliases = Dict(aliases)
189 aliases = Dict(aliases)
190 flags = Dict(flags)
190 flags = Dict(flags)
191
191
192
192
193 def save_connection_dict(self, fname, cdict):
193 def save_connection_dict(self, fname, cdict):
194 """save a connection dict to json file."""
194 """save a connection dict to json file."""
195 c = self.config
195 c = self.config
196 url = cdict['url']
196 url = cdict['url']
197 location = cdict['location']
197 location = cdict['location']
198 if not location:
198 if not location:
199 try:
199 try:
200 proto,ip,port = split_url(url)
200 proto,ip,port = split_url(url)
201 except AssertionError:
201 except AssertionError:
202 pass
202 pass
203 else:
203 else:
204 try:
204 try:
205 location = socket.gethostbyname_ex(socket.gethostname())[2][-1]
205 location = socket.gethostbyname_ex(socket.gethostname())[2][-1]
206 except (socket.gaierror, IndexError):
206 except (socket.gaierror, IndexError):
207 self.log.warn("Could not identify this machine's IP, assuming 127.0.0.1."
207 self.log.warn("Could not identify this machine's IP, assuming 127.0.0.1."
208 " You may need to specify '--location=<external_ip_address>' to help"
208 " You may need to specify '--location=<external_ip_address>' to help"
209 " IPython decide when to connect via loopback.")
209 " IPython decide when to connect via loopback.")
210 location = '127.0.0.1'
210 location = '127.0.0.1'
211 cdict['location'] = location
211 cdict['location'] = location
212 fname = os.path.join(self.profile_dir.security_dir, fname)
212 fname = os.path.join(self.profile_dir.security_dir, fname)
213 with open(fname, 'wb') as f:
213 with open(fname, 'wb') as f:
214 f.write(json.dumps(cdict, indent=2))
214 f.write(json.dumps(cdict, indent=2))
215 os.chmod(fname, stat.S_IRUSR|stat.S_IWUSR)
215 os.chmod(fname, stat.S_IRUSR|stat.S_IWUSR)
216
216
217 def load_config_from_json(self):
217 def load_config_from_json(self):
218 """load config from existing json connector files."""
218 """load config from existing json connector files."""
219 c = self.config
219 c = self.config
220 self.log.debug("loading config from JSON")
220 self.log.debug("loading config from JSON")
221 # load from engine config
221 # load from engine config
222 with open(os.path.join(self.profile_dir.security_dir, self.engine_json_file)) as f:
222 with open(os.path.join(self.profile_dir.security_dir, self.engine_json_file)) as f:
223 cfg = json.loads(f.read())
223 cfg = json.loads(f.read())
224 key = c.Session.key = asbytes(cfg['exec_key'])
224 key = c.Session.key = asbytes(cfg['exec_key'])
225 xport,addr = cfg['url'].split('://')
225 xport,addr = cfg['url'].split('://')
226 c.HubFactory.engine_transport = xport
226 c.HubFactory.engine_transport = xport
227 ip,ports = addr.split(':')
227 ip,ports = addr.split(':')
228 c.HubFactory.engine_ip = ip
228 c.HubFactory.engine_ip = ip
229 c.HubFactory.regport = int(ports)
229 c.HubFactory.regport = int(ports)
230 self.location = cfg['location']
230 self.location = cfg['location']
231 if not self.engine_ssh_server:
231 if not self.engine_ssh_server:
232 self.engine_ssh_server = cfg['ssh']
232 self.engine_ssh_server = cfg['ssh']
233 # load client config
233 # load client config
234 with open(os.path.join(self.profile_dir.security_dir, self.client_json_file)) as f:
234 with open(os.path.join(self.profile_dir.security_dir, self.client_json_file)) as f:
235 cfg = json.loads(f.read())
235 cfg = json.loads(f.read())
236 assert key == cfg['exec_key'], "exec_key mismatch between engine and client keys"
236 assert key == cfg['exec_key'], "exec_key mismatch between engine and client keys"
237 xport,addr = cfg['url'].split('://')
237 xport,addr = cfg['url'].split('://')
238 c.HubFactory.client_transport = xport
238 c.HubFactory.client_transport = xport
239 ip,ports = addr.split(':')
239 ip,ports = addr.split(':')
240 c.HubFactory.client_ip = ip
240 c.HubFactory.client_ip = ip
241 if not self.ssh_server:
241 if not self.ssh_server:
242 self.ssh_server = cfg['ssh']
242 self.ssh_server = cfg['ssh']
243 assert int(ports) == c.HubFactory.regport, "regport mismatch"
243 assert int(ports) == c.HubFactory.regport, "regport mismatch"
244
244
245 def load_secondary_config(self):
245 def load_secondary_config(self):
246 """secondary config, loading from JSON and setting defaults"""
246 """secondary config, loading from JSON and setting defaults"""
247 if self.reuse_files:
247 if self.reuse_files:
248 try:
248 try:
249 self.load_config_from_json()
249 self.load_config_from_json()
250 except (AssertionError,IOError) as e:
250 except (AssertionError,IOError) as e:
251 self.log.error("Could not load config from JSON: %s" % e)
251 self.log.error("Could not load config from JSON: %s" % e)
252 self.reuse_files=False
252 self.reuse_files=False
253 # switch Session.key default to secure
253 # switch Session.key default to secure
254 default_secure(self.config)
254 default_secure(self.config)
255 self.log.debug("Config changed")
255 self.log.debug("Config changed")
256 self.log.debug(repr(self.config))
256 self.log.debug(repr(self.config))
257
257
258 def init_hub(self):
258 def init_hub(self):
259 c = self.config
259 c = self.config
260
260
261 self.do_import_statements()
261 self.do_import_statements()
262
262
263 try:
263 try:
264 self.factory = HubFactory(config=c, log=self.log)
264 self.factory = HubFactory(config=c, log=self.log)
265 # self.start_logging()
265 # self.start_logging()
266 self.factory.init_hub()
266 self.factory.init_hub()
267 except TraitError:
267 except TraitError:
268 raise
268 raise
269 except Exception:
269 except Exception:
270 self.log.error("Couldn't construct the Controller", exc_info=True)
270 self.log.error("Couldn't construct the Controller", exc_info=True)
271 self.exit(1)
271 self.exit(1)
272
272
273 if not self.reuse_files:
273 if not self.reuse_files:
274 # save to new json config files
274 # save to new json config files
275 f = self.factory
275 f = self.factory
276 cdict = {'exec_key' : f.session.key,
276 cdict = {'exec_key' : f.session.key.decode('ascii'),
277 'ssh' : self.ssh_server,
277 'ssh' : self.ssh_server,
278 'url' : "%s://%s:%s"%(f.client_transport, f.client_ip, f.regport),
278 'url' : "%s://%s:%s"%(f.client_transport, f.client_ip, f.regport),
279 'location' : self.location
279 'location' : self.location
280 }
280 }
281 self.save_connection_dict(self.client_json_file, cdict)
281 self.save_connection_dict(self.client_json_file, cdict)
282 edict = cdict
282 edict = cdict
283 edict['url']="%s://%s:%s"%((f.client_transport, f.client_ip, f.regport))
283 edict['url']="%s://%s:%s"%((f.client_transport, f.client_ip, f.regport))
284 edict['ssh'] = self.engine_ssh_server
284 edict['ssh'] = self.engine_ssh_server
285 self.save_connection_dict(self.engine_json_file, edict)
285 self.save_connection_dict(self.engine_json_file, edict)
286
286
287 #
287 #
288 def init_schedulers(self):
288 def init_schedulers(self):
289 children = self.children
289 children = self.children
290 mq = import_item(str(self.mq_class))
290 mq = import_item(str(self.mq_class))
291
291
292 hub = self.factory
292 hub = self.factory
293 # disambiguate url, in case of *
293 # disambiguate url, in case of *
294 monitor_url = disambiguate_url(hub.monitor_url)
294 monitor_url = disambiguate_url(hub.monitor_url)
295 # maybe_inproc = 'inproc://monitor' if self.use_threads else monitor_url
295 # maybe_inproc = 'inproc://monitor' if self.use_threads else monitor_url
296 # IOPub relay (in a Process)
296 # IOPub relay (in a Process)
297 q = mq(zmq.PUB, zmq.SUB, zmq.PUB, b'N/A',b'iopub')
297 q = mq(zmq.PUB, zmq.SUB, zmq.PUB, b'N/A',b'iopub')
298 q.bind_in(hub.client_info['iopub'])
298 q.bind_in(hub.client_info['iopub'])
299 q.bind_out(hub.engine_info['iopub'])
299 q.bind_out(hub.engine_info['iopub'])
300 q.setsockopt_out(zmq.SUBSCRIBE, b'')
300 q.setsockopt_out(zmq.SUBSCRIBE, b'')
301 q.connect_mon(monitor_url)
301 q.connect_mon(monitor_url)
302 q.daemon=True
302 q.daemon=True
303 children.append(q)
303 children.append(q)
304
304
305 # Multiplexer Queue (in a Process)
305 # Multiplexer Queue (in a Process)
306 q = mq(zmq.ROUTER, zmq.ROUTER, zmq.PUB, b'in', b'out')
306 q = mq(zmq.ROUTER, zmq.ROUTER, zmq.PUB, b'in', b'out')
307 q.bind_in(hub.client_info['mux'])
307 q.bind_in(hub.client_info['mux'])
308 q.setsockopt_in(zmq.IDENTITY, b'mux')
308 q.setsockopt_in(zmq.IDENTITY, b'mux')
309 q.bind_out(hub.engine_info['mux'])
309 q.bind_out(hub.engine_info['mux'])
310 q.connect_mon(monitor_url)
310 q.connect_mon(monitor_url)
311 q.daemon=True
311 q.daemon=True
312 children.append(q)
312 children.append(q)
313
313
314 # Control Queue (in a Process)
314 # Control Queue (in a Process)
315 q = mq(zmq.ROUTER, zmq.ROUTER, zmq.PUB, b'incontrol', b'outcontrol')
315 q = mq(zmq.ROUTER, zmq.ROUTER, zmq.PUB, b'incontrol', b'outcontrol')
316 q.bind_in(hub.client_info['control'])
316 q.bind_in(hub.client_info['control'])
317 q.setsockopt_in(zmq.IDENTITY, b'control')
317 q.setsockopt_in(zmq.IDENTITY, b'control')
318 q.bind_out(hub.engine_info['control'])
318 q.bind_out(hub.engine_info['control'])
319 q.connect_mon(monitor_url)
319 q.connect_mon(monitor_url)
320 q.daemon=True
320 q.daemon=True
321 children.append(q)
321 children.append(q)
322 try:
322 try:
323 scheme = self.config.TaskScheduler.scheme_name
323 scheme = self.config.TaskScheduler.scheme_name
324 except AttributeError:
324 except AttributeError:
325 scheme = TaskScheduler.scheme_name.get_default_value()
325 scheme = TaskScheduler.scheme_name.get_default_value()
326 # Task Queue (in a Process)
326 # Task Queue (in a Process)
327 if scheme == 'pure':
327 if scheme == 'pure':
328 self.log.warn("task::using pure XREQ Task scheduler")
328 self.log.warn("task::using pure XREQ Task scheduler")
329 q = mq(zmq.ROUTER, zmq.DEALER, zmq.PUB, b'intask', b'outtask')
329 q = mq(zmq.ROUTER, zmq.DEALER, zmq.PUB, b'intask', b'outtask')
330 # q.setsockopt_out(zmq.HWM, hub.hwm)
330 # q.setsockopt_out(zmq.HWM, hub.hwm)
331 q.bind_in(hub.client_info['task'][1])
331 q.bind_in(hub.client_info['task'][1])
332 q.setsockopt_in(zmq.IDENTITY, b'task')
332 q.setsockopt_in(zmq.IDENTITY, b'task')
333 q.bind_out(hub.engine_info['task'])
333 q.bind_out(hub.engine_info['task'])
334 q.connect_mon(monitor_url)
334 q.connect_mon(monitor_url)
335 q.daemon=True
335 q.daemon=True
336 children.append(q)
336 children.append(q)
337 elif scheme == 'none':
337 elif scheme == 'none':
338 self.log.warn("task::using no Task scheduler")
338 self.log.warn("task::using no Task scheduler")
339
339
340 else:
340 else:
341 self.log.info("task::using Python %s Task scheduler"%scheme)
341 self.log.info("task::using Python %s Task scheduler"%scheme)
342 sargs = (hub.client_info['task'][1], hub.engine_info['task'],
342 sargs = (hub.client_info['task'][1], hub.engine_info['task'],
343 monitor_url, disambiguate_url(hub.client_info['notification']))
343 monitor_url, disambiguate_url(hub.client_info['notification']))
344 kwargs = dict(logname='scheduler', loglevel=self.log_level,
344 kwargs = dict(logname='scheduler', loglevel=self.log_level,
345 log_url = self.log_url, config=dict(self.config))
345 log_url = self.log_url, config=dict(self.config))
346 if 'Process' in self.mq_class:
346 if 'Process' in self.mq_class:
347 # run the Python scheduler in a Process
347 # run the Python scheduler in a Process
348 q = Process(target=launch_scheduler, args=sargs, kwargs=kwargs)
348 q = Process(target=launch_scheduler, args=sargs, kwargs=kwargs)
349 q.daemon=True
349 q.daemon=True
350 children.append(q)
350 children.append(q)
351 else:
351 else:
352 # single-threaded Controller
352 # single-threaded Controller
353 kwargs['in_thread'] = True
353 kwargs['in_thread'] = True
354 launch_scheduler(*sargs, **kwargs)
354 launch_scheduler(*sargs, **kwargs)
355
355
356
356
357 def save_urls(self):
357 def save_urls(self):
358 """save the registration urls to files."""
358 """save the registration urls to files."""
359 c = self.config
359 c = self.config
360
360
361 sec_dir = self.profile_dir.security_dir
361 sec_dir = self.profile_dir.security_dir
362 cf = self.factory
362 cf = self.factory
363
363
364 with open(os.path.join(sec_dir, 'ipcontroller-engine.url'), 'w') as f:
364 with open(os.path.join(sec_dir, 'ipcontroller-engine.url'), 'w') as f:
365 f.write("%s://%s:%s"%(cf.engine_transport, cf.engine_ip, cf.regport))
365 f.write("%s://%s:%s"%(cf.engine_transport, cf.engine_ip, cf.regport))
366
366
367 with open(os.path.join(sec_dir, 'ipcontroller-client.url'), 'w') as f:
367 with open(os.path.join(sec_dir, 'ipcontroller-client.url'), 'w') as f:
368 f.write("%s://%s:%s"%(cf.client_transport, cf.client_ip, cf.regport))
368 f.write("%s://%s:%s"%(cf.client_transport, cf.client_ip, cf.regport))
369
369
370
370
371 def do_import_statements(self):
371 def do_import_statements(self):
372 statements = self.import_statements
372 statements = self.import_statements
373 for s in statements:
373 for s in statements:
374 try:
374 try:
375 self.log.msg("Executing statement: '%s'" % s)
375 self.log.msg("Executing statement: '%s'" % s)
376 exec s in globals(), locals()
376 exec s in globals(), locals()
377 except:
377 except:
378 self.log.msg("Error running statement: %s" % s)
378 self.log.msg("Error running statement: %s" % s)
379
379
380 def forward_logging(self):
380 def forward_logging(self):
381 if self.log_url:
381 if self.log_url:
382 self.log.info("Forwarding logging to %s"%self.log_url)
382 self.log.info("Forwarding logging to %s"%self.log_url)
383 context = zmq.Context.instance()
383 context = zmq.Context.instance()
384 lsock = context.socket(zmq.PUB)
384 lsock = context.socket(zmq.PUB)
385 lsock.connect(self.log_url)
385 lsock.connect(self.log_url)
386 handler = PUBHandler(lsock)
386 handler = PUBHandler(lsock)
387 self.log.removeHandler(self._log_handler)
387 self.log.removeHandler(self._log_handler)
388 handler.root_topic = 'controller'
388 handler.root_topic = 'controller'
389 handler.setLevel(self.log_level)
389 handler.setLevel(self.log_level)
390 self.log.addHandler(handler)
390 self.log.addHandler(handler)
391 self._log_handler = handler
391 self._log_handler = handler
392
392
393 @catch_config_error
393 @catch_config_error
394 def initialize(self, argv=None):
394 def initialize(self, argv=None):
395 super(IPControllerApp, self).initialize(argv)
395 super(IPControllerApp, self).initialize(argv)
396 self.forward_logging()
396 self.forward_logging()
397 self.load_secondary_config()
397 self.load_secondary_config()
398 self.init_hub()
398 self.init_hub()
399 self.init_schedulers()
399 self.init_schedulers()
400
400
401 def start(self):
401 def start(self):
402 # Start the subprocesses:
402 # Start the subprocesses:
403 self.factory.start()
403 self.factory.start()
404 child_procs = []
404 child_procs = []
405 for child in self.children:
405 for child in self.children:
406 child.start()
406 child.start()
407 if isinstance(child, ProcessMonitoredQueue):
407 if isinstance(child, ProcessMonitoredQueue):
408 child_procs.append(child.launcher)
408 child_procs.append(child.launcher)
409 elif isinstance(child, Process):
409 elif isinstance(child, Process):
410 child_procs.append(child)
410 child_procs.append(child)
411 if child_procs:
411 if child_procs:
412 signal_children(child_procs)
412 signal_children(child_procs)
413
413
414 self.write_pid_file(overwrite=True)
414 self.write_pid_file(overwrite=True)
415
415
416 try:
416 try:
417 self.factory.loop.start()
417 self.factory.loop.start()
418 except KeyboardInterrupt:
418 except KeyboardInterrupt:
419 self.log.critical("Interrupted, Exiting...\n")
419 self.log.critical("Interrupted, Exiting...\n")
420
420
421
421
422
422
423 def launch_new_instance():
423 def launch_new_instance():
424 """Create and run the IPython controller"""
424 """Create and run the IPython controller"""
425 if sys.platform == 'win32':
425 if sys.platform == 'win32':
426 # make sure we don't get called from a multiprocessing subprocess
426 # make sure we don't get called from a multiprocessing subprocess
427 # this can result in infinite Controllers being started on Windows
427 # this can result in infinite Controllers being started on Windows
428 # which doesn't have a proper fork, so multiprocessing is wonky
428 # which doesn't have a proper fork, so multiprocessing is wonky
429
429
430 # this only comes up when IPython has been installed using vanilla
430 # this only comes up when IPython has been installed using vanilla
431 # setuptools, and *not* distribute.
431 # setuptools, and *not* distribute.
432 import multiprocessing
432 import multiprocessing
433 p = multiprocessing.current_process()
433 p = multiprocessing.current_process()
434 # the main process has name 'MainProcess'
434 # the main process has name 'MainProcess'
435 # subprocesses will have names like 'Process-1'
435 # subprocesses will have names like 'Process-1'
436 if p.name != 'MainProcess':
436 if p.name != 'MainProcess':
437 # we are a subprocess, don't start another Controller!
437 # we are a subprocess, don't start another Controller!
438 return
438 return
439 app = IPControllerApp.instance()
439 app = IPControllerApp.instance()
440 app.initialize()
440 app.initialize()
441 app.start()
441 app.start()
442
442
443
443
444 if __name__ == '__main__':
444 if __name__ == '__main__':
445 launch_new_instance()
445 launch_new_instance()
@@ -1,1394 +1,1394
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 Authors:
31 Authors:
32
32
33 * Brian Granger
33 * Brian Granger
34 * Enthought, Inc. Some of the code in this file comes from enthought.traits
34 * Enthought, Inc. Some of the code in this file comes from enthought.traits
35 and is licensed under the BSD license. Also, many of the ideas also come
35 and is licensed under the BSD license. Also, many of the ideas also come
36 from enthought.traits even though our implementation is very different.
36 from enthought.traits even though our implementation is very different.
37 """
37 """
38
38
39 #-----------------------------------------------------------------------------
39 #-----------------------------------------------------------------------------
40 # Copyright (C) 2008-2009 The IPython Development Team
40 # Copyright (C) 2008-2009 The IPython Development Team
41 #
41 #
42 # Distributed under the terms of the BSD License. The full license is in
42 # Distributed under the terms of the BSD License. The full license is in
43 # the file COPYING, distributed as part of this software.
43 # the file COPYING, distributed as part of this software.
44 #-----------------------------------------------------------------------------
44 #-----------------------------------------------------------------------------
45
45
46 #-----------------------------------------------------------------------------
46 #-----------------------------------------------------------------------------
47 # Imports
47 # Imports
48 #-----------------------------------------------------------------------------
48 #-----------------------------------------------------------------------------
49
49
50
50
51 import inspect
51 import inspect
52 import re
52 import re
53 import sys
53 import sys
54 import types
54 import types
55 from types import FunctionType
55 from types import FunctionType
56 try:
56 try:
57 from types import ClassType, InstanceType
57 from types import ClassType, InstanceType
58 ClassTypes = (ClassType, type)
58 ClassTypes = (ClassType, type)
59 except:
59 except:
60 ClassTypes = (type,)
60 ClassTypes = (type,)
61
61
62 from .importstring import import_item
62 from .importstring import import_item
63 from IPython.utils import py3compat
63 from IPython.utils import py3compat
64
64
65 SequenceTypes = (list, tuple, set, frozenset)
65 SequenceTypes = (list, tuple, set, frozenset)
66
66
67 #-----------------------------------------------------------------------------
67 #-----------------------------------------------------------------------------
68 # Basic classes
68 # Basic classes
69 #-----------------------------------------------------------------------------
69 #-----------------------------------------------------------------------------
70
70
71
71
72 class NoDefaultSpecified ( object ): pass
72 class NoDefaultSpecified ( object ): pass
73 NoDefaultSpecified = NoDefaultSpecified()
73 NoDefaultSpecified = NoDefaultSpecified()
74
74
75
75
76 class Undefined ( object ): pass
76 class Undefined ( object ): pass
77 Undefined = Undefined()
77 Undefined = Undefined()
78
78
79 class TraitError(Exception):
79 class TraitError(Exception):
80 pass
80 pass
81
81
82 #-----------------------------------------------------------------------------
82 #-----------------------------------------------------------------------------
83 # Utilities
83 # Utilities
84 #-----------------------------------------------------------------------------
84 #-----------------------------------------------------------------------------
85
85
86
86
87 def class_of ( object ):
87 def class_of ( object ):
88 """ Returns a string containing the class name of an object with the
88 """ Returns a string containing the class name of an object with the
89 correct indefinite article ('a' or 'an') preceding it (e.g., 'an Image',
89 correct indefinite article ('a' or 'an') preceding it (e.g., 'an Image',
90 'a PlotValue').
90 'a PlotValue').
91 """
91 """
92 if isinstance( object, basestring ):
92 if isinstance( object, basestring ):
93 return add_article( object )
93 return add_article( object )
94
94
95 return add_article( object.__class__.__name__ )
95 return add_article( object.__class__.__name__ )
96
96
97
97
98 def add_article ( name ):
98 def add_article ( name ):
99 """ Returns a string containing the correct indefinite article ('a' or 'an')
99 """ Returns a string containing the correct indefinite article ('a' or 'an')
100 prefixed to the specified string.
100 prefixed to the specified string.
101 """
101 """
102 if name[:1].lower() in 'aeiou':
102 if name[:1].lower() in 'aeiou':
103 return 'an ' + name
103 return 'an ' + name
104
104
105 return 'a ' + name
105 return 'a ' + name
106
106
107
107
108 def repr_type(obj):
108 def repr_type(obj):
109 """ Return a string representation of a value and its type for readable
109 """ Return a string representation of a value and its type for readable
110 error messages.
110 error messages.
111 """
111 """
112 the_type = type(obj)
112 the_type = type(obj)
113 if (not py3compat.PY3) and the_type is InstanceType:
113 if (not py3compat.PY3) and the_type is InstanceType:
114 # Old-style class.
114 # Old-style class.
115 the_type = obj.__class__
115 the_type = obj.__class__
116 msg = '%r %r' % (obj, the_type)
116 msg = '%r %r' % (obj, the_type)
117 return msg
117 return msg
118
118
119
119
120 def parse_notifier_name(name):
120 def parse_notifier_name(name):
121 """Convert the name argument to a list of names.
121 """Convert the name argument to a list of names.
122
122
123 Examples
123 Examples
124 --------
124 --------
125
125
126 >>> parse_notifier_name('a')
126 >>> parse_notifier_name('a')
127 ['a']
127 ['a']
128 >>> parse_notifier_name(['a','b'])
128 >>> parse_notifier_name(['a','b'])
129 ['a', 'b']
129 ['a', 'b']
130 >>> parse_notifier_name(None)
130 >>> parse_notifier_name(None)
131 ['anytrait']
131 ['anytrait']
132 """
132 """
133 if isinstance(name, str):
133 if isinstance(name, str):
134 return [name]
134 return [name]
135 elif name is None:
135 elif name is None:
136 return ['anytrait']
136 return ['anytrait']
137 elif isinstance(name, (list, tuple)):
137 elif isinstance(name, (list, tuple)):
138 for n in name:
138 for n in name:
139 assert isinstance(n, str), "names must be strings"
139 assert isinstance(n, str), "names must be strings"
140 return name
140 return name
141
141
142
142
143 class _SimpleTest:
143 class _SimpleTest:
144 def __init__ ( self, value ): self.value = value
144 def __init__ ( self, value ): self.value = value
145 def __call__ ( self, test ):
145 def __call__ ( self, test ):
146 return test == self.value
146 return test == self.value
147 def __repr__(self):
147 def __repr__(self):
148 return "<SimpleTest(%r)" % self.value
148 return "<SimpleTest(%r)" % self.value
149 def __str__(self):
149 def __str__(self):
150 return self.__repr__()
150 return self.__repr__()
151
151
152
152
153 def getmembers(object, predicate=None):
153 def getmembers(object, predicate=None):
154 """A safe version of inspect.getmembers that handles missing attributes.
154 """A safe version of inspect.getmembers that handles missing attributes.
155
155
156 This is useful when there are descriptor based attributes that for
156 This is useful when there are descriptor based attributes that for
157 some reason raise AttributeError even though they exist. This happens
157 some reason raise AttributeError even though they exist. This happens
158 in zope.inteface with the __provides__ attribute.
158 in zope.inteface with the __provides__ attribute.
159 """
159 """
160 results = []
160 results = []
161 for key in dir(object):
161 for key in dir(object):
162 try:
162 try:
163 value = getattr(object, key)
163 value = getattr(object, key)
164 except AttributeError:
164 except AttributeError:
165 pass
165 pass
166 else:
166 else:
167 if not predicate or predicate(value):
167 if not predicate or predicate(value):
168 results.append((key, value))
168 results.append((key, value))
169 results.sort()
169 results.sort()
170 return results
170 return results
171
171
172
172
173 #-----------------------------------------------------------------------------
173 #-----------------------------------------------------------------------------
174 # Base TraitType for all traits
174 # Base TraitType for all traits
175 #-----------------------------------------------------------------------------
175 #-----------------------------------------------------------------------------
176
176
177
177
178 class TraitType(object):
178 class TraitType(object):
179 """A base class for all trait descriptors.
179 """A base class for all trait descriptors.
180
180
181 Notes
181 Notes
182 -----
182 -----
183 Our implementation of traits is based on Python's descriptor
183 Our implementation of traits is based on Python's descriptor
184 prototol. This class is the base class for all such descriptors. The
184 prototol. This class is the base class for all such descriptors. The
185 only magic we use is a custom metaclass for the main :class:`HasTraits`
185 only magic we use is a custom metaclass for the main :class:`HasTraits`
186 class that does the following:
186 class that does the following:
187
187
188 1. Sets the :attr:`name` attribute of every :class:`TraitType`
188 1. Sets the :attr:`name` attribute of every :class:`TraitType`
189 instance in the class dict to the name of the attribute.
189 instance in the class dict to the name of the attribute.
190 2. Sets the :attr:`this_class` attribute of every :class:`TraitType`
190 2. Sets the :attr:`this_class` attribute of every :class:`TraitType`
191 instance in the class dict to the *class* that declared the trait.
191 instance in the class dict to the *class* that declared the trait.
192 This is used by the :class:`This` trait to allow subclasses to
192 This is used by the :class:`This` trait to allow subclasses to
193 accept superclasses for :class:`This` values.
193 accept superclasses for :class:`This` values.
194 """
194 """
195
195
196
196
197 metadata = {}
197 metadata = {}
198 default_value = Undefined
198 default_value = Undefined
199 info_text = 'any value'
199 info_text = 'any value'
200
200
201 def __init__(self, default_value=NoDefaultSpecified, **metadata):
201 def __init__(self, default_value=NoDefaultSpecified, **metadata):
202 """Create a TraitType.
202 """Create a TraitType.
203 """
203 """
204 if default_value is not NoDefaultSpecified:
204 if default_value is not NoDefaultSpecified:
205 self.default_value = default_value
205 self.default_value = default_value
206
206
207 if len(metadata) > 0:
207 if len(metadata) > 0:
208 if len(self.metadata) > 0:
208 if len(self.metadata) > 0:
209 self._metadata = self.metadata.copy()
209 self._metadata = self.metadata.copy()
210 self._metadata.update(metadata)
210 self._metadata.update(metadata)
211 else:
211 else:
212 self._metadata = metadata
212 self._metadata = metadata
213 else:
213 else:
214 self._metadata = self.metadata
214 self._metadata = self.metadata
215
215
216 self.init()
216 self.init()
217
217
218 def init(self):
218 def init(self):
219 pass
219 pass
220
220
221 def get_default_value(self):
221 def get_default_value(self):
222 """Create a new instance of the default value."""
222 """Create a new instance of the default value."""
223 return self.default_value
223 return self.default_value
224
224
225 def instance_init(self, obj):
225 def instance_init(self, obj):
226 """This is called by :meth:`HasTraits.__new__` to finish init'ing.
226 """This is called by :meth:`HasTraits.__new__` to finish init'ing.
227
227
228 Some stages of initialization must be delayed until the parent
228 Some stages of initialization must be delayed until the parent
229 :class:`HasTraits` instance has been created. This method is
229 :class:`HasTraits` instance has been created. This method is
230 called in :meth:`HasTraits.__new__` after the instance has been
230 called in :meth:`HasTraits.__new__` after the instance has been
231 created.
231 created.
232
232
233 This method trigger the creation and validation of default values
233 This method trigger the creation and validation of default values
234 and also things like the resolution of str given class names in
234 and also things like the resolution of str given class names in
235 :class:`Type` and :class`Instance`.
235 :class:`Type` and :class`Instance`.
236
236
237 Parameters
237 Parameters
238 ----------
238 ----------
239 obj : :class:`HasTraits` instance
239 obj : :class:`HasTraits` instance
240 The parent :class:`HasTraits` instance that has just been
240 The parent :class:`HasTraits` instance that has just been
241 created.
241 created.
242 """
242 """
243 self.set_default_value(obj)
243 self.set_default_value(obj)
244
244
245 def set_default_value(self, obj):
245 def set_default_value(self, obj):
246 """Set the default value on a per instance basis.
246 """Set the default value on a per instance basis.
247
247
248 This method is called by :meth:`instance_init` to create and
248 This method is called by :meth:`instance_init` to create and
249 validate the default value. The creation and validation of
249 validate the default value. The creation and validation of
250 default values must be delayed until the parent :class:`HasTraits`
250 default values must be delayed until the parent :class:`HasTraits`
251 class has been instantiated.
251 class has been instantiated.
252 """
252 """
253 # Check for a deferred initializer defined in the same class as the
253 # Check for a deferred initializer defined in the same class as the
254 # trait declaration or above.
254 # trait declaration or above.
255 mro = type(obj).mro()
255 mro = type(obj).mro()
256 meth_name = '_%s_default' % self.name
256 meth_name = '_%s_default' % self.name
257 for cls in mro[:mro.index(self.this_class)+1]:
257 for cls in mro[:mro.index(self.this_class)+1]:
258 if meth_name in cls.__dict__:
258 if meth_name in cls.__dict__:
259 break
259 break
260 else:
260 else:
261 # We didn't find one. Do static initialization.
261 # We didn't find one. Do static initialization.
262 dv = self.get_default_value()
262 dv = self.get_default_value()
263 newdv = self._validate(obj, dv)
263 newdv = self._validate(obj, dv)
264 obj._trait_values[self.name] = newdv
264 obj._trait_values[self.name] = newdv
265 return
265 return
266 # Complete the dynamic initialization.
266 # Complete the dynamic initialization.
267 obj._trait_dyn_inits[self.name] = cls.__dict__[meth_name]
267 obj._trait_dyn_inits[self.name] = cls.__dict__[meth_name]
268
268
269 def __get__(self, obj, cls=None):
269 def __get__(self, obj, cls=None):
270 """Get the value of the trait by self.name for the instance.
270 """Get the value of the trait by self.name for the instance.
271
271
272 Default values are instantiated when :meth:`HasTraits.__new__`
272 Default values are instantiated when :meth:`HasTraits.__new__`
273 is called. Thus by the time this method gets called either the
273 is called. Thus by the time this method gets called either the
274 default value or a user defined value (they called :meth:`__set__`)
274 default value or a user defined value (they called :meth:`__set__`)
275 is in the :class:`HasTraits` instance.
275 is in the :class:`HasTraits` instance.
276 """
276 """
277 if obj is None:
277 if obj is None:
278 return self
278 return self
279 else:
279 else:
280 try:
280 try:
281 value = obj._trait_values[self.name]
281 value = obj._trait_values[self.name]
282 except KeyError:
282 except KeyError:
283 # Check for a dynamic initializer.
283 # Check for a dynamic initializer.
284 if self.name in obj._trait_dyn_inits:
284 if self.name in obj._trait_dyn_inits:
285 value = obj._trait_dyn_inits[self.name](obj)
285 value = obj._trait_dyn_inits[self.name](obj)
286 # FIXME: Do we really validate here?
286 # FIXME: Do we really validate here?
287 value = self._validate(obj, value)
287 value = self._validate(obj, value)
288 obj._trait_values[self.name] = value
288 obj._trait_values[self.name] = value
289 return value
289 return value
290 else:
290 else:
291 raise TraitError('Unexpected error in TraitType: '
291 raise TraitError('Unexpected error in TraitType: '
292 'both default value and dynamic initializer are '
292 'both default value and dynamic initializer are '
293 'absent.')
293 'absent.')
294 except Exception:
294 except Exception:
295 # HasTraits should call set_default_value to populate
295 # HasTraits should call set_default_value to populate
296 # this. So this should never be reached.
296 # this. So this should never be reached.
297 raise TraitError('Unexpected error in TraitType: '
297 raise TraitError('Unexpected error in TraitType: '
298 'default value not set properly')
298 'default value not set properly')
299 else:
299 else:
300 return value
300 return value
301
301
302 def __set__(self, obj, value):
302 def __set__(self, obj, value):
303 new_value = self._validate(obj, value)
303 new_value = self._validate(obj, value)
304 old_value = self.__get__(obj)
304 old_value = self.__get__(obj)
305 if old_value != new_value:
305 if old_value != new_value:
306 obj._trait_values[self.name] = new_value
306 obj._trait_values[self.name] = new_value
307 obj._notify_trait(self.name, old_value, new_value)
307 obj._notify_trait(self.name, old_value, new_value)
308
308
309 def _validate(self, obj, value):
309 def _validate(self, obj, value):
310 if hasattr(self, 'validate'):
310 if hasattr(self, 'validate'):
311 return self.validate(obj, value)
311 return self.validate(obj, value)
312 elif hasattr(self, 'is_valid_for'):
312 elif hasattr(self, 'is_valid_for'):
313 valid = self.is_valid_for(value)
313 valid = self.is_valid_for(value)
314 if valid:
314 if valid:
315 return value
315 return value
316 else:
316 else:
317 raise TraitError('invalid value for type: %r' % value)
317 raise TraitError('invalid value for type: %r' % value)
318 elif hasattr(self, 'value_for'):
318 elif hasattr(self, 'value_for'):
319 return self.value_for(value)
319 return self.value_for(value)
320 else:
320 else:
321 return value
321 return value
322
322
323 def info(self):
323 def info(self):
324 return self.info_text
324 return self.info_text
325
325
326 def error(self, obj, value):
326 def error(self, obj, value):
327 if obj is not None:
327 if obj is not None:
328 e = "The '%s' trait of %s instance must be %s, but a value of %s was specified." \
328 e = "The '%s' trait of %s instance must be %s, but a value of %s was specified." \
329 % (self.name, class_of(obj),
329 % (self.name, class_of(obj),
330 self.info(), repr_type(value))
330 self.info(), repr_type(value))
331 else:
331 else:
332 e = "The '%s' trait must be %s, but a value of %r was specified." \
332 e = "The '%s' trait must be %s, but a value of %r was specified." \
333 % (self.name, self.info(), repr_type(value))
333 % (self.name, self.info(), repr_type(value))
334 raise TraitError(e)
334 raise TraitError(e)
335
335
336 def get_metadata(self, key):
336 def get_metadata(self, key):
337 return getattr(self, '_metadata', {}).get(key, None)
337 return getattr(self, '_metadata', {}).get(key, None)
338
338
339 def set_metadata(self, key, value):
339 def set_metadata(self, key, value):
340 getattr(self, '_metadata', {})[key] = value
340 getattr(self, '_metadata', {})[key] = value
341
341
342
342
343 #-----------------------------------------------------------------------------
343 #-----------------------------------------------------------------------------
344 # The HasTraits implementation
344 # The HasTraits implementation
345 #-----------------------------------------------------------------------------
345 #-----------------------------------------------------------------------------
346
346
347
347
348 class MetaHasTraits(type):
348 class MetaHasTraits(type):
349 """A metaclass for HasTraits.
349 """A metaclass for HasTraits.
350
350
351 This metaclass makes sure that any TraitType class attributes are
351 This metaclass makes sure that any TraitType class attributes are
352 instantiated and sets their name attribute.
352 instantiated and sets their name attribute.
353 """
353 """
354
354
355 def __new__(mcls, name, bases, classdict):
355 def __new__(mcls, name, bases, classdict):
356 """Create the HasTraits class.
356 """Create the HasTraits class.
357
357
358 This instantiates all TraitTypes in the class dict and sets their
358 This instantiates all TraitTypes in the class dict and sets their
359 :attr:`name` attribute.
359 :attr:`name` attribute.
360 """
360 """
361 # print "MetaHasTraitlets (mcls, name): ", mcls, name
361 # print "MetaHasTraitlets (mcls, name): ", mcls, name
362 # print "MetaHasTraitlets (bases): ", bases
362 # print "MetaHasTraitlets (bases): ", bases
363 # print "MetaHasTraitlets (classdict): ", classdict
363 # print "MetaHasTraitlets (classdict): ", classdict
364 for k,v in classdict.iteritems():
364 for k,v in classdict.iteritems():
365 if isinstance(v, TraitType):
365 if isinstance(v, TraitType):
366 v.name = k
366 v.name = k
367 elif inspect.isclass(v):
367 elif inspect.isclass(v):
368 if issubclass(v, TraitType):
368 if issubclass(v, TraitType):
369 vinst = v()
369 vinst = v()
370 vinst.name = k
370 vinst.name = k
371 classdict[k] = vinst
371 classdict[k] = vinst
372 return super(MetaHasTraits, mcls).__new__(mcls, name, bases, classdict)
372 return super(MetaHasTraits, mcls).__new__(mcls, name, bases, classdict)
373
373
374 def __init__(cls, name, bases, classdict):
374 def __init__(cls, name, bases, classdict):
375 """Finish initializing the HasTraits class.
375 """Finish initializing the HasTraits class.
376
376
377 This sets the :attr:`this_class` attribute of each TraitType in the
377 This sets the :attr:`this_class` attribute of each TraitType in the
378 class dict to the newly created class ``cls``.
378 class dict to the newly created class ``cls``.
379 """
379 """
380 for k, v in classdict.iteritems():
380 for k, v in classdict.iteritems():
381 if isinstance(v, TraitType):
381 if isinstance(v, TraitType):
382 v.this_class = cls
382 v.this_class = cls
383 super(MetaHasTraits, cls).__init__(name, bases, classdict)
383 super(MetaHasTraits, cls).__init__(name, bases, classdict)
384
384
385 class HasTraits(object):
385 class HasTraits(object):
386
386
387 __metaclass__ = MetaHasTraits
387 __metaclass__ = MetaHasTraits
388
388
389 def __new__(cls, **kw):
389 def __new__(cls, **kw):
390 # This is needed because in Python 2.6 object.__new__ only accepts
390 # This is needed because in Python 2.6 object.__new__ only accepts
391 # the cls argument.
391 # the cls argument.
392 new_meth = super(HasTraits, cls).__new__
392 new_meth = super(HasTraits, cls).__new__
393 if new_meth is object.__new__:
393 if new_meth is object.__new__:
394 inst = new_meth(cls)
394 inst = new_meth(cls)
395 else:
395 else:
396 inst = new_meth(cls, **kw)
396 inst = new_meth(cls, **kw)
397 inst._trait_values = {}
397 inst._trait_values = {}
398 inst._trait_notifiers = {}
398 inst._trait_notifiers = {}
399 inst._trait_dyn_inits = {}
399 inst._trait_dyn_inits = {}
400 # Here we tell all the TraitType instances to set their default
400 # Here we tell all the TraitType instances to set their default
401 # values on the instance.
401 # values on the instance.
402 for key in dir(cls):
402 for key in dir(cls):
403 # Some descriptors raise AttributeError like zope.interface's
403 # Some descriptors raise AttributeError like zope.interface's
404 # __provides__ attributes even though they exist. This causes
404 # __provides__ attributes even though they exist. This causes
405 # AttributeErrors even though they are listed in dir(cls).
405 # AttributeErrors even though they are listed in dir(cls).
406 try:
406 try:
407 value = getattr(cls, key)
407 value = getattr(cls, key)
408 except AttributeError:
408 except AttributeError:
409 pass
409 pass
410 else:
410 else:
411 if isinstance(value, TraitType):
411 if isinstance(value, TraitType):
412 value.instance_init(inst)
412 value.instance_init(inst)
413
413
414 return inst
414 return inst
415
415
416 def __init__(self, **kw):
416 def __init__(self, **kw):
417 # Allow trait values to be set using keyword arguments.
417 # Allow trait values to be set using keyword arguments.
418 # We need to use setattr for this to trigger validation and
418 # We need to use setattr for this to trigger validation and
419 # notifications.
419 # notifications.
420 for key, value in kw.iteritems():
420 for key, value in kw.iteritems():
421 setattr(self, key, value)
421 setattr(self, key, value)
422
422
423 def _notify_trait(self, name, old_value, new_value):
423 def _notify_trait(self, name, old_value, new_value):
424
424
425 # First dynamic ones
425 # First dynamic ones
426 callables = self._trait_notifiers.get(name,[])
426 callables = self._trait_notifiers.get(name,[])
427 more_callables = self._trait_notifiers.get('anytrait',[])
427 more_callables = self._trait_notifiers.get('anytrait',[])
428 callables.extend(more_callables)
428 callables.extend(more_callables)
429
429
430 # Now static ones
430 # Now static ones
431 try:
431 try:
432 cb = getattr(self, '_%s_changed' % name)
432 cb = getattr(self, '_%s_changed' % name)
433 except:
433 except:
434 pass
434 pass
435 else:
435 else:
436 callables.append(cb)
436 callables.append(cb)
437
437
438 # Call them all now
438 # Call them all now
439 for c in callables:
439 for c in callables:
440 # Traits catches and logs errors here. I allow them to raise
440 # Traits catches and logs errors here. I allow them to raise
441 if callable(c):
441 if callable(c):
442 argspec = inspect.getargspec(c)
442 argspec = inspect.getargspec(c)
443 nargs = len(argspec[0])
443 nargs = len(argspec[0])
444 # Bound methods have an additional 'self' argument
444 # Bound methods have an additional 'self' argument
445 # I don't know how to treat unbound methods, but they
445 # I don't know how to treat unbound methods, but they
446 # can't really be used for callbacks.
446 # can't really be used for callbacks.
447 if isinstance(c, types.MethodType):
447 if isinstance(c, types.MethodType):
448 offset = -1
448 offset = -1
449 else:
449 else:
450 offset = 0
450 offset = 0
451 if nargs + offset == 0:
451 if nargs + offset == 0:
452 c()
452 c()
453 elif nargs + offset == 1:
453 elif nargs + offset == 1:
454 c(name)
454 c(name)
455 elif nargs + offset == 2:
455 elif nargs + offset == 2:
456 c(name, new_value)
456 c(name, new_value)
457 elif nargs + offset == 3:
457 elif nargs + offset == 3:
458 c(name, old_value, new_value)
458 c(name, old_value, new_value)
459 else:
459 else:
460 raise TraitError('a trait changed callback '
460 raise TraitError('a trait changed callback '
461 'must have 0-3 arguments.')
461 'must have 0-3 arguments.')
462 else:
462 else:
463 raise TraitError('a trait changed callback '
463 raise TraitError('a trait changed callback '
464 'must be callable.')
464 'must be callable.')
465
465
466
466
467 def _add_notifiers(self, handler, name):
467 def _add_notifiers(self, handler, name):
468 if not self._trait_notifiers.has_key(name):
468 if not self._trait_notifiers.has_key(name):
469 nlist = []
469 nlist = []
470 self._trait_notifiers[name] = nlist
470 self._trait_notifiers[name] = nlist
471 else:
471 else:
472 nlist = self._trait_notifiers[name]
472 nlist = self._trait_notifiers[name]
473 if handler not in nlist:
473 if handler not in nlist:
474 nlist.append(handler)
474 nlist.append(handler)
475
475
476 def _remove_notifiers(self, handler, name):
476 def _remove_notifiers(self, handler, name):
477 if self._trait_notifiers.has_key(name):
477 if self._trait_notifiers.has_key(name):
478 nlist = self._trait_notifiers[name]
478 nlist = self._trait_notifiers[name]
479 try:
479 try:
480 index = nlist.index(handler)
480 index = nlist.index(handler)
481 except ValueError:
481 except ValueError:
482 pass
482 pass
483 else:
483 else:
484 del nlist[index]
484 del nlist[index]
485
485
486 def on_trait_change(self, handler, name=None, remove=False):
486 def on_trait_change(self, handler, name=None, remove=False):
487 """Setup a handler to be called when a trait changes.
487 """Setup a handler to be called when a trait changes.
488
488
489 This is used to setup dynamic notifications of trait changes.
489 This is used to setup dynamic notifications of trait changes.
490
490
491 Static handlers can be created by creating methods on a HasTraits
491 Static handlers can be created by creating methods on a HasTraits
492 subclass with the naming convention '_[traitname]_changed'. Thus,
492 subclass with the naming convention '_[traitname]_changed'. Thus,
493 to create static handler for the trait 'a', create the method
493 to create static handler for the trait 'a', create the method
494 _a_changed(self, name, old, new) (fewer arguments can be used, see
494 _a_changed(self, name, old, new) (fewer arguments can be used, see
495 below).
495 below).
496
496
497 Parameters
497 Parameters
498 ----------
498 ----------
499 handler : callable
499 handler : callable
500 A callable that is called when a trait changes. Its
500 A callable that is called when a trait changes. Its
501 signature can be handler(), handler(name), handler(name, new)
501 signature can be handler(), handler(name), handler(name, new)
502 or handler(name, old, new).
502 or handler(name, old, new).
503 name : list, str, None
503 name : list, str, None
504 If None, the handler will apply to all traits. If a list
504 If None, the handler will apply to all traits. If a list
505 of str, handler will apply to all names in the list. If a
505 of str, handler will apply to all names in the list. If a
506 str, the handler will apply just to that name.
506 str, the handler will apply just to that name.
507 remove : bool
507 remove : bool
508 If False (the default), then install the handler. If True
508 If False (the default), then install the handler. If True
509 then unintall it.
509 then unintall it.
510 """
510 """
511 if remove:
511 if remove:
512 names = parse_notifier_name(name)
512 names = parse_notifier_name(name)
513 for n in names:
513 for n in names:
514 self._remove_notifiers(handler, n)
514 self._remove_notifiers(handler, n)
515 else:
515 else:
516 names = parse_notifier_name(name)
516 names = parse_notifier_name(name)
517 for n in names:
517 for n in names:
518 self._add_notifiers(handler, n)
518 self._add_notifiers(handler, n)
519
519
520 @classmethod
520 @classmethod
521 def class_trait_names(cls, **metadata):
521 def class_trait_names(cls, **metadata):
522 """Get a list of all the names of this classes traits.
522 """Get a list of all the names of this classes traits.
523
523
524 This method is just like the :meth:`trait_names` method, but is unbound.
524 This method is just like the :meth:`trait_names` method, but is unbound.
525 """
525 """
526 return cls.class_traits(**metadata).keys()
526 return cls.class_traits(**metadata).keys()
527
527
528 @classmethod
528 @classmethod
529 def class_traits(cls, **metadata):
529 def class_traits(cls, **metadata):
530 """Get a list of all the traits of this class.
530 """Get a list of all the traits of this class.
531
531
532 This method is just like the :meth:`traits` method, but is unbound.
532 This method is just like the :meth:`traits` method, but is unbound.
533
533
534 The TraitTypes returned don't know anything about the values
534 The TraitTypes returned don't know anything about the values
535 that the various HasTrait's instances are holding.
535 that the various HasTrait's instances are holding.
536
536
537 This follows the same algorithm as traits does and does not allow
537 This follows the same algorithm as traits does and does not allow
538 for any simple way of specifying merely that a metadata name
538 for any simple way of specifying merely that a metadata name
539 exists, but has any value. This is because get_metadata returns
539 exists, but has any value. This is because get_metadata returns
540 None if a metadata key doesn't exist.
540 None if a metadata key doesn't exist.
541 """
541 """
542 traits = dict([memb for memb in getmembers(cls) if \
542 traits = dict([memb for memb in getmembers(cls) if \
543 isinstance(memb[1], TraitType)])
543 isinstance(memb[1], TraitType)])
544
544
545 if len(metadata) == 0:
545 if len(metadata) == 0:
546 return traits
546 return traits
547
547
548 for meta_name, meta_eval in metadata.items():
548 for meta_name, meta_eval in metadata.items():
549 if type(meta_eval) is not FunctionType:
549 if type(meta_eval) is not FunctionType:
550 metadata[meta_name] = _SimpleTest(meta_eval)
550 metadata[meta_name] = _SimpleTest(meta_eval)
551
551
552 result = {}
552 result = {}
553 for name, trait in traits.items():
553 for name, trait in traits.items():
554 for meta_name, meta_eval in metadata.items():
554 for meta_name, meta_eval in metadata.items():
555 if not meta_eval(trait.get_metadata(meta_name)):
555 if not meta_eval(trait.get_metadata(meta_name)):
556 break
556 break
557 else:
557 else:
558 result[name] = trait
558 result[name] = trait
559
559
560 return result
560 return result
561
561
562 def trait_names(self, **metadata):
562 def trait_names(self, **metadata):
563 """Get a list of all the names of this classes traits."""
563 """Get a list of all the names of this classes traits."""
564 return self.traits(**metadata).keys()
564 return self.traits(**metadata).keys()
565
565
566 def traits(self, **metadata):
566 def traits(self, **metadata):
567 """Get a list of all the traits of this class.
567 """Get a list of all the traits of this class.
568
568
569 The TraitTypes returned don't know anything about the values
569 The TraitTypes returned don't know anything about the values
570 that the various HasTrait's instances are holding.
570 that the various HasTrait's instances are holding.
571
571
572 This follows the same algorithm as traits does and does not allow
572 This follows the same algorithm as traits does and does not allow
573 for any simple way of specifying merely that a metadata name
573 for any simple way of specifying merely that a metadata name
574 exists, but has any value. This is because get_metadata returns
574 exists, but has any value. This is because get_metadata returns
575 None if a metadata key doesn't exist.
575 None if a metadata key doesn't exist.
576 """
576 """
577 traits = dict([memb for memb in getmembers(self.__class__) if \
577 traits = dict([memb for memb in getmembers(self.__class__) if \
578 isinstance(memb[1], TraitType)])
578 isinstance(memb[1], TraitType)])
579
579
580 if len(metadata) == 0:
580 if len(metadata) == 0:
581 return traits
581 return traits
582
582
583 for meta_name, meta_eval in metadata.items():
583 for meta_name, meta_eval in metadata.items():
584 if type(meta_eval) is not FunctionType:
584 if type(meta_eval) is not FunctionType:
585 metadata[meta_name] = _SimpleTest(meta_eval)
585 metadata[meta_name] = _SimpleTest(meta_eval)
586
586
587 result = {}
587 result = {}
588 for name, trait in traits.items():
588 for name, trait in traits.items():
589 for meta_name, meta_eval in metadata.items():
589 for meta_name, meta_eval in metadata.items():
590 if not meta_eval(trait.get_metadata(meta_name)):
590 if not meta_eval(trait.get_metadata(meta_name)):
591 break
591 break
592 else:
592 else:
593 result[name] = trait
593 result[name] = trait
594
594
595 return result
595 return result
596
596
597 def trait_metadata(self, traitname, key):
597 def trait_metadata(self, traitname, key):
598 """Get metadata values for trait by key."""
598 """Get metadata values for trait by key."""
599 try:
599 try:
600 trait = getattr(self.__class__, traitname)
600 trait = getattr(self.__class__, traitname)
601 except AttributeError:
601 except AttributeError:
602 raise TraitError("Class %s does not have a trait named %s" %
602 raise TraitError("Class %s does not have a trait named %s" %
603 (self.__class__.__name__, traitname))
603 (self.__class__.__name__, traitname))
604 else:
604 else:
605 return trait.get_metadata(key)
605 return trait.get_metadata(key)
606
606
607 #-----------------------------------------------------------------------------
607 #-----------------------------------------------------------------------------
608 # Actual TraitTypes implementations/subclasses
608 # Actual TraitTypes implementations/subclasses
609 #-----------------------------------------------------------------------------
609 #-----------------------------------------------------------------------------
610
610
611 #-----------------------------------------------------------------------------
611 #-----------------------------------------------------------------------------
612 # TraitTypes subclasses for handling classes and instances of classes
612 # TraitTypes subclasses for handling classes and instances of classes
613 #-----------------------------------------------------------------------------
613 #-----------------------------------------------------------------------------
614
614
615
615
616 class ClassBasedTraitType(TraitType):
616 class ClassBasedTraitType(TraitType):
617 """A trait with error reporting for Type, Instance and This."""
617 """A trait with error reporting for Type, Instance and This."""
618
618
619 def error(self, obj, value):
619 def error(self, obj, value):
620 kind = type(value)
620 kind = type(value)
621 if (not py3compat.PY3) and kind is InstanceType:
621 if (not py3compat.PY3) and kind is InstanceType:
622 msg = 'class %s' % value.__class__.__name__
622 msg = 'class %s' % value.__class__.__name__
623 else:
623 else:
624 msg = '%s (i.e. %s)' % ( str( kind )[1:-1], repr( value ) )
624 msg = '%s (i.e. %s)' % ( str( kind )[1:-1], repr( value ) )
625
625
626 if obj is not None:
626 if obj is not None:
627 e = "The '%s' trait of %s instance must be %s, but a value of %s was specified." \
627 e = "The '%s' trait of %s instance must be %s, but a value of %s was specified." \
628 % (self.name, class_of(obj),
628 % (self.name, class_of(obj),
629 self.info(), msg)
629 self.info(), msg)
630 else:
630 else:
631 e = "The '%s' trait must be %s, but a value of %r was specified." \
631 e = "The '%s' trait must be %s, but a value of %r was specified." \
632 % (self.name, self.info(), msg)
632 % (self.name, self.info(), msg)
633
633
634 raise TraitError(e)
634 raise TraitError(e)
635
635
636
636
637 class Type(ClassBasedTraitType):
637 class Type(ClassBasedTraitType):
638 """A trait whose value must be a subclass of a specified class."""
638 """A trait whose value must be a subclass of a specified class."""
639
639
640 def __init__ (self, default_value=None, klass=None, allow_none=True, **metadata ):
640 def __init__ (self, default_value=None, klass=None, allow_none=True, **metadata ):
641 """Construct a Type trait
641 """Construct a Type trait
642
642
643 A Type trait specifies that its values must be subclasses of
643 A Type trait specifies that its values must be subclasses of
644 a particular class.
644 a particular class.
645
645
646 If only ``default_value`` is given, it is used for the ``klass`` as
646 If only ``default_value`` is given, it is used for the ``klass`` as
647 well.
647 well.
648
648
649 Parameters
649 Parameters
650 ----------
650 ----------
651 default_value : class, str or None
651 default_value : class, str or None
652 The default value must be a subclass of klass. If an str,
652 The default value must be a subclass of klass. If an str,
653 the str must be a fully specified class name, like 'foo.bar.Bah'.
653 the str must be a fully specified class name, like 'foo.bar.Bah'.
654 The string is resolved into real class, when the parent
654 The string is resolved into real class, when the parent
655 :class:`HasTraits` class is instantiated.
655 :class:`HasTraits` class is instantiated.
656 klass : class, str, None
656 klass : class, str, None
657 Values of this trait must be a subclass of klass. The klass
657 Values of this trait must be a subclass of klass. The klass
658 may be specified in a string like: 'foo.bar.MyClass'.
658 may be specified in a string like: 'foo.bar.MyClass'.
659 The string is resolved into real class, when the parent
659 The string is resolved into real class, when the parent
660 :class:`HasTraits` class is instantiated.
660 :class:`HasTraits` class is instantiated.
661 allow_none : boolean
661 allow_none : boolean
662 Indicates whether None is allowed as an assignable value. Even if
662 Indicates whether None is allowed as an assignable value. Even if
663 ``False``, the default value may be ``None``.
663 ``False``, the default value may be ``None``.
664 """
664 """
665 if default_value is None:
665 if default_value is None:
666 if klass is None:
666 if klass is None:
667 klass = object
667 klass = object
668 elif klass is None:
668 elif klass is None:
669 klass = default_value
669 klass = default_value
670
670
671 if not (inspect.isclass(klass) or isinstance(klass, basestring)):
671 if not (inspect.isclass(klass) or isinstance(klass, basestring)):
672 raise TraitError("A Type trait must specify a class.")
672 raise TraitError("A Type trait must specify a class.")
673
673
674 self.klass = klass
674 self.klass = klass
675 self._allow_none = allow_none
675 self._allow_none = allow_none
676
676
677 super(Type, self).__init__(default_value, **metadata)
677 super(Type, self).__init__(default_value, **metadata)
678
678
679 def validate(self, obj, value):
679 def validate(self, obj, value):
680 """Validates that the value is a valid object instance."""
680 """Validates that the value is a valid object instance."""
681 try:
681 try:
682 if issubclass(value, self.klass):
682 if issubclass(value, self.klass):
683 return value
683 return value
684 except:
684 except:
685 if (value is None) and (self._allow_none):
685 if (value is None) and (self._allow_none):
686 return value
686 return value
687
687
688 self.error(obj, value)
688 self.error(obj, value)
689
689
690 def info(self):
690 def info(self):
691 """ Returns a description of the trait."""
691 """ Returns a description of the trait."""
692 if isinstance(self.klass, basestring):
692 if isinstance(self.klass, basestring):
693 klass = self.klass
693 klass = self.klass
694 else:
694 else:
695 klass = self.klass.__name__
695 klass = self.klass.__name__
696 result = 'a subclass of ' + klass
696 result = 'a subclass of ' + klass
697 if self._allow_none:
697 if self._allow_none:
698 return result + ' or None'
698 return result + ' or None'
699 return result
699 return result
700
700
701 def instance_init(self, obj):
701 def instance_init(self, obj):
702 self._resolve_classes()
702 self._resolve_classes()
703 super(Type, self).instance_init(obj)
703 super(Type, self).instance_init(obj)
704
704
705 def _resolve_classes(self):
705 def _resolve_classes(self):
706 if isinstance(self.klass, basestring):
706 if isinstance(self.klass, basestring):
707 self.klass = import_item(self.klass)
707 self.klass = import_item(self.klass)
708 if isinstance(self.default_value, basestring):
708 if isinstance(self.default_value, basestring):
709 self.default_value = import_item(self.default_value)
709 self.default_value = import_item(self.default_value)
710
710
711 def get_default_value(self):
711 def get_default_value(self):
712 return self.default_value
712 return self.default_value
713
713
714
714
715 class DefaultValueGenerator(object):
715 class DefaultValueGenerator(object):
716 """A class for generating new default value instances."""
716 """A class for generating new default value instances."""
717
717
718 def __init__(self, *args, **kw):
718 def __init__(self, *args, **kw):
719 self.args = args
719 self.args = args
720 self.kw = kw
720 self.kw = kw
721
721
722 def generate(self, klass):
722 def generate(self, klass):
723 return klass(*self.args, **self.kw)
723 return klass(*self.args, **self.kw)
724
724
725
725
726 class Instance(ClassBasedTraitType):
726 class Instance(ClassBasedTraitType):
727 """A trait whose value must be an instance of a specified class.
727 """A trait whose value must be an instance of a specified class.
728
728
729 The value can also be an instance of a subclass of the specified class.
729 The value can also be an instance of a subclass of the specified class.
730 """
730 """
731
731
732 def __init__(self, klass=None, args=None, kw=None,
732 def __init__(self, klass=None, args=None, kw=None,
733 allow_none=True, **metadata ):
733 allow_none=True, **metadata ):
734 """Construct an Instance trait.
734 """Construct an Instance trait.
735
735
736 This trait allows values that are instances of a particular
736 This trait allows values that are instances of a particular
737 class or its sublclasses. Our implementation is quite different
737 class or its sublclasses. Our implementation is quite different
738 from that of enthough.traits as we don't allow instances to be used
738 from that of enthough.traits as we don't allow instances to be used
739 for klass and we handle the ``args`` and ``kw`` arguments differently.
739 for klass and we handle the ``args`` and ``kw`` arguments differently.
740
740
741 Parameters
741 Parameters
742 ----------
742 ----------
743 klass : class, str
743 klass : class, str
744 The class that forms the basis for the trait. Class names
744 The class that forms the basis for the trait. Class names
745 can also be specified as strings, like 'foo.bar.Bar'.
745 can also be specified as strings, like 'foo.bar.Bar'.
746 args : tuple
746 args : tuple
747 Positional arguments for generating the default value.
747 Positional arguments for generating the default value.
748 kw : dict
748 kw : dict
749 Keyword arguments for generating the default value.
749 Keyword arguments for generating the default value.
750 allow_none : bool
750 allow_none : bool
751 Indicates whether None is allowed as a value.
751 Indicates whether None is allowed as a value.
752
752
753 Default Value
753 Default Value
754 -------------
754 -------------
755 If both ``args`` and ``kw`` are None, then the default value is None.
755 If both ``args`` and ``kw`` are None, then the default value is None.
756 If ``args`` is a tuple and ``kw`` is a dict, then the default is
756 If ``args`` is a tuple and ``kw`` is a dict, then the default is
757 created as ``klass(*args, **kw)``. If either ``args`` or ``kw`` is
757 created as ``klass(*args, **kw)``. If either ``args`` or ``kw`` is
758 not (but not both), None is replace by ``()`` or ``{}``.
758 not (but not both), None is replace by ``()`` or ``{}``.
759 """
759 """
760
760
761 self._allow_none = allow_none
761 self._allow_none = allow_none
762
762
763 if (klass is None) or (not (inspect.isclass(klass) or isinstance(klass, basestring))):
763 if (klass is None) or (not (inspect.isclass(klass) or isinstance(klass, basestring))):
764 raise TraitError('The klass argument must be a class'
764 raise TraitError('The klass argument must be a class'
765 ' you gave: %r' % klass)
765 ' you gave: %r' % klass)
766 self.klass = klass
766 self.klass = klass
767
767
768 # self.klass is a class, so handle default_value
768 # self.klass is a class, so handle default_value
769 if args is None and kw is None:
769 if args is None and kw is None:
770 default_value = None
770 default_value = None
771 else:
771 else:
772 if args is None:
772 if args is None:
773 # kw is not None
773 # kw is not None
774 args = ()
774 args = ()
775 elif kw is None:
775 elif kw is None:
776 # args is not None
776 # args is not None
777 kw = {}
777 kw = {}
778
778
779 if not isinstance(kw, dict):
779 if not isinstance(kw, dict):
780 raise TraitError("The 'kw' argument must be a dict or None.")
780 raise TraitError("The 'kw' argument must be a dict or None.")
781 if not isinstance(args, tuple):
781 if not isinstance(args, tuple):
782 raise TraitError("The 'args' argument must be a tuple or None.")
782 raise TraitError("The 'args' argument must be a tuple or None.")
783
783
784 default_value = DefaultValueGenerator(*args, **kw)
784 default_value = DefaultValueGenerator(*args, **kw)
785
785
786 super(Instance, self).__init__(default_value, **metadata)
786 super(Instance, self).__init__(default_value, **metadata)
787
787
788 def validate(self, obj, value):
788 def validate(self, obj, value):
789 if value is None:
789 if value is None:
790 if self._allow_none:
790 if self._allow_none:
791 return value
791 return value
792 self.error(obj, value)
792 self.error(obj, value)
793
793
794 if isinstance(value, self.klass):
794 if isinstance(value, self.klass):
795 return value
795 return value
796 else:
796 else:
797 self.error(obj, value)
797 self.error(obj, value)
798
798
799 def info(self):
799 def info(self):
800 if isinstance(self.klass, basestring):
800 if isinstance(self.klass, basestring):
801 klass = self.klass
801 klass = self.klass
802 else:
802 else:
803 klass = self.klass.__name__
803 klass = self.klass.__name__
804 result = class_of(klass)
804 result = class_of(klass)
805 if self._allow_none:
805 if self._allow_none:
806 return result + ' or None'
806 return result + ' or None'
807
807
808 return result
808 return result
809
809
810 def instance_init(self, obj):
810 def instance_init(self, obj):
811 self._resolve_classes()
811 self._resolve_classes()
812 super(Instance, self).instance_init(obj)
812 super(Instance, self).instance_init(obj)
813
813
814 def _resolve_classes(self):
814 def _resolve_classes(self):
815 if isinstance(self.klass, basestring):
815 if isinstance(self.klass, basestring):
816 self.klass = import_item(self.klass)
816 self.klass = import_item(self.klass)
817
817
818 def get_default_value(self):
818 def get_default_value(self):
819 """Instantiate a default value instance.
819 """Instantiate a default value instance.
820
820
821 This is called when the containing HasTraits classes'
821 This is called when the containing HasTraits classes'
822 :meth:`__new__` method is called to ensure that a unique instance
822 :meth:`__new__` method is called to ensure that a unique instance
823 is created for each HasTraits instance.
823 is created for each HasTraits instance.
824 """
824 """
825 dv = self.default_value
825 dv = self.default_value
826 if isinstance(dv, DefaultValueGenerator):
826 if isinstance(dv, DefaultValueGenerator):
827 return dv.generate(self.klass)
827 return dv.generate(self.klass)
828 else:
828 else:
829 return dv
829 return dv
830
830
831
831
832 class This(ClassBasedTraitType):
832 class This(ClassBasedTraitType):
833 """A trait for instances of the class containing this trait.
833 """A trait for instances of the class containing this trait.
834
834
835 Because how how and when class bodies are executed, the ``This``
835 Because how how and when class bodies are executed, the ``This``
836 trait can only have a default value of None. This, and because we
836 trait can only have a default value of None. This, and because we
837 always validate default values, ``allow_none`` is *always* true.
837 always validate default values, ``allow_none`` is *always* true.
838 """
838 """
839
839
840 info_text = 'an instance of the same type as the receiver or None'
840 info_text = 'an instance of the same type as the receiver or None'
841
841
842 def __init__(self, **metadata):
842 def __init__(self, **metadata):
843 super(This, self).__init__(None, **metadata)
843 super(This, self).__init__(None, **metadata)
844
844
845 def validate(self, obj, value):
845 def validate(self, obj, value):
846 # What if value is a superclass of obj.__class__? This is
846 # What if value is a superclass of obj.__class__? This is
847 # complicated if it was the superclass that defined the This
847 # complicated if it was the superclass that defined the This
848 # trait.
848 # trait.
849 if isinstance(value, self.this_class) or (value is None):
849 if isinstance(value, self.this_class) or (value is None):
850 return value
850 return value
851 else:
851 else:
852 self.error(obj, value)
852 self.error(obj, value)
853
853
854
854
855 #-----------------------------------------------------------------------------
855 #-----------------------------------------------------------------------------
856 # Basic TraitTypes implementations/subclasses
856 # Basic TraitTypes implementations/subclasses
857 #-----------------------------------------------------------------------------
857 #-----------------------------------------------------------------------------
858
858
859
859
860 class Any(TraitType):
860 class Any(TraitType):
861 default_value = None
861 default_value = None
862 info_text = 'any value'
862 info_text = 'any value'
863
863
864
864
865 class Int(TraitType):
865 class Int(TraitType):
866 """A integer trait."""
866 """A integer trait."""
867
867
868 default_value = 0
868 default_value = 0
869 info_text = 'an integer'
869 info_text = 'an integer'
870
870
871 def validate(self, obj, value):
871 def validate(self, obj, value):
872 if isinstance(value, int):
872 if isinstance(value, int):
873 return value
873 return value
874 self.error(obj, value)
874 self.error(obj, value)
875
875
876 class CInt(Int):
876 class CInt(Int):
877 """A casting version of the int trait."""
877 """A casting version of the int trait."""
878
878
879 def validate(self, obj, value):
879 def validate(self, obj, value):
880 try:
880 try:
881 return int(value)
881 return int(value)
882 except:
882 except:
883 self.error(obj, value)
883 self.error(obj, value)
884
884
885 if py3compat.PY3:
885 if py3compat.PY3:
886 Long, CLong = Int, CInt
886 Long, CLong = Int, CInt
887 else:
887 else:
888 class Long(TraitType):
888 class Long(TraitType):
889 """A long integer trait."""
889 """A long integer trait."""
890
890
891 default_value = 0L
891 default_value = 0L
892 info_text = 'a long'
892 info_text = 'a long'
893
893
894 def validate(self, obj, value):
894 def validate(self, obj, value):
895 if isinstance(value, long):
895 if isinstance(value, long):
896 return value
896 return value
897 if isinstance(value, int):
897 if isinstance(value, int):
898 return long(value)
898 return long(value)
899 self.error(obj, value)
899 self.error(obj, value)
900
900
901
901
902 class CLong(Long):
902 class CLong(Long):
903 """A casting version of the long integer trait."""
903 """A casting version of the long integer trait."""
904
904
905 def validate(self, obj, value):
905 def validate(self, obj, value):
906 try:
906 try:
907 return long(value)
907 return long(value)
908 except:
908 except:
909 self.error(obj, value)
909 self.error(obj, value)
910
910
911
911
912 class Float(TraitType):
912 class Float(TraitType):
913 """A float trait."""
913 """A float trait."""
914
914
915 default_value = 0.0
915 default_value = 0.0
916 info_text = 'a float'
916 info_text = 'a float'
917
917
918 def validate(self, obj, value):
918 def validate(self, obj, value):
919 if isinstance(value, float):
919 if isinstance(value, float):
920 return value
920 return value
921 if isinstance(value, int):
921 if isinstance(value, int):
922 return float(value)
922 return float(value)
923 self.error(obj, value)
923 self.error(obj, value)
924
924
925
925
926 class CFloat(Float):
926 class CFloat(Float):
927 """A casting version of the float trait."""
927 """A casting version of the float trait."""
928
928
929 def validate(self, obj, value):
929 def validate(self, obj, value):
930 try:
930 try:
931 return float(value)
931 return float(value)
932 except:
932 except:
933 self.error(obj, value)
933 self.error(obj, value)
934
934
935 class Complex(TraitType):
935 class Complex(TraitType):
936 """A trait for complex numbers."""
936 """A trait for complex numbers."""
937
937
938 default_value = 0.0 + 0.0j
938 default_value = 0.0 + 0.0j
939 info_text = 'a complex number'
939 info_text = 'a complex number'
940
940
941 def validate(self, obj, value):
941 def validate(self, obj, value):
942 if isinstance(value, complex):
942 if isinstance(value, complex):
943 return value
943 return value
944 if isinstance(value, (float, int)):
944 if isinstance(value, (float, int)):
945 return complex(value)
945 return complex(value)
946 self.error(obj, value)
946 self.error(obj, value)
947
947
948
948
949 class CComplex(Complex):
949 class CComplex(Complex):
950 """A casting version of the complex number trait."""
950 """A casting version of the complex number trait."""
951
951
952 def validate (self, obj, value):
952 def validate (self, obj, value):
953 try:
953 try:
954 return complex(value)
954 return complex(value)
955 except:
955 except:
956 self.error(obj, value)
956 self.error(obj, value)
957
957
958 # We should always be explicit about whether we're using bytes or unicode, both
958 # We should always be explicit about whether we're using bytes or unicode, both
959 # for Python 3 conversion and for reliable unicode behaviour on Python 2. So
959 # for Python 3 conversion and for reliable unicode behaviour on Python 2. So
960 # we don't have a Str type.
960 # we don't have a Str type.
961 class Bytes(TraitType):
961 class Bytes(TraitType):
962 """A trait for byte strings."""
962 """A trait for byte strings."""
963
963
964 default_value = ''
964 default_value = b''
965 info_text = 'a string'
965 info_text = 'a string'
966
966
967 def validate(self, obj, value):
967 def validate(self, obj, value):
968 if isinstance(value, bytes):
968 if isinstance(value, bytes):
969 return value
969 return value
970 self.error(obj, value)
970 self.error(obj, value)
971
971
972
972
973 class CBytes(Bytes):
973 class CBytes(Bytes):
974 """A casting version of the byte string trait."""
974 """A casting version of the byte string trait."""
975
975
976 def validate(self, obj, value):
976 def validate(self, obj, value):
977 try:
977 try:
978 return bytes(value)
978 return bytes(value)
979 except:
979 except:
980 self.error(obj, value)
980 self.error(obj, value)
981
981
982
982
983 class Unicode(TraitType):
983 class Unicode(TraitType):
984 """A trait for unicode strings."""
984 """A trait for unicode strings."""
985
985
986 default_value = u''
986 default_value = u''
987 info_text = 'a unicode string'
987 info_text = 'a unicode string'
988
988
989 def validate(self, obj, value):
989 def validate(self, obj, value):
990 if isinstance(value, unicode):
990 if isinstance(value, unicode):
991 return value
991 return value
992 if isinstance(value, bytes):
992 if isinstance(value, bytes):
993 return unicode(value)
993 return unicode(value)
994 self.error(obj, value)
994 self.error(obj, value)
995
995
996
996
997 class CUnicode(Unicode):
997 class CUnicode(Unicode):
998 """A casting version of the unicode trait."""
998 """A casting version of the unicode trait."""
999
999
1000 def validate(self, obj, value):
1000 def validate(self, obj, value):
1001 try:
1001 try:
1002 return unicode(value)
1002 return unicode(value)
1003 except:
1003 except:
1004 self.error(obj, value)
1004 self.error(obj, value)
1005
1005
1006
1006
1007 class ObjectName(TraitType):
1007 class ObjectName(TraitType):
1008 """A string holding a valid object name in this version of Python.
1008 """A string holding a valid object name in this version of Python.
1009
1009
1010 This does not check that the name exists in any scope."""
1010 This does not check that the name exists in any scope."""
1011 info_text = "a valid object identifier in Python"
1011 info_text = "a valid object identifier in Python"
1012
1012
1013 if py3compat.PY3:
1013 if py3compat.PY3:
1014 # Python 3:
1014 # Python 3:
1015 coerce_str = staticmethod(lambda _,s: s)
1015 coerce_str = staticmethod(lambda _,s: s)
1016
1016
1017 else:
1017 else:
1018 # Python 2:
1018 # Python 2:
1019 def coerce_str(self, obj, value):
1019 def coerce_str(self, obj, value):
1020 "In Python 2, coerce ascii-only unicode to str"
1020 "In Python 2, coerce ascii-only unicode to str"
1021 if isinstance(value, unicode):
1021 if isinstance(value, unicode):
1022 try:
1022 try:
1023 return str(value)
1023 return str(value)
1024 except UnicodeEncodeError:
1024 except UnicodeEncodeError:
1025 self.error(obj, value)
1025 self.error(obj, value)
1026 return value
1026 return value
1027
1027
1028 def validate(self, obj, value):
1028 def validate(self, obj, value):
1029 value = self.coerce_str(obj, value)
1029 value = self.coerce_str(obj, value)
1030
1030
1031 if isinstance(value, str) and py3compat.isidentifier(value):
1031 if isinstance(value, str) and py3compat.isidentifier(value):
1032 return value
1032 return value
1033 self.error(obj, value)
1033 self.error(obj, value)
1034
1034
1035 class DottedObjectName(ObjectName):
1035 class DottedObjectName(ObjectName):
1036 """A string holding a valid dotted object name in Python, such as A.b3._c"""
1036 """A string holding a valid dotted object name in Python, such as A.b3._c"""
1037 def validate(self, obj, value):
1037 def validate(self, obj, value):
1038 value = self.coerce_str(obj, value)
1038 value = self.coerce_str(obj, value)
1039
1039
1040 if isinstance(value, str) and py3compat.isidentifier(value, dotted=True):
1040 if isinstance(value, str) and py3compat.isidentifier(value, dotted=True):
1041 return value
1041 return value
1042 self.error(obj, value)
1042 self.error(obj, value)
1043
1043
1044
1044
1045 class Bool(TraitType):
1045 class Bool(TraitType):
1046 """A boolean (True, False) trait."""
1046 """A boolean (True, False) trait."""
1047
1047
1048 default_value = False
1048 default_value = False
1049 info_text = 'a boolean'
1049 info_text = 'a boolean'
1050
1050
1051 def validate(self, obj, value):
1051 def validate(self, obj, value):
1052 if isinstance(value, bool):
1052 if isinstance(value, bool):
1053 return value
1053 return value
1054 self.error(obj, value)
1054 self.error(obj, value)
1055
1055
1056
1056
1057 class CBool(Bool):
1057 class CBool(Bool):
1058 """A casting version of the boolean trait."""
1058 """A casting version of the boolean trait."""
1059
1059
1060 def validate(self, obj, value):
1060 def validate(self, obj, value):
1061 try:
1061 try:
1062 return bool(value)
1062 return bool(value)
1063 except:
1063 except:
1064 self.error(obj, value)
1064 self.error(obj, value)
1065
1065
1066
1066
1067 class Enum(TraitType):
1067 class Enum(TraitType):
1068 """An enum that whose value must be in a given sequence."""
1068 """An enum that whose value must be in a given sequence."""
1069
1069
1070 def __init__(self, values, default_value=None, allow_none=True, **metadata):
1070 def __init__(self, values, default_value=None, allow_none=True, **metadata):
1071 self.values = values
1071 self.values = values
1072 self._allow_none = allow_none
1072 self._allow_none = allow_none
1073 super(Enum, self).__init__(default_value, **metadata)
1073 super(Enum, self).__init__(default_value, **metadata)
1074
1074
1075 def validate(self, obj, value):
1075 def validate(self, obj, value):
1076 if value is None:
1076 if value is None:
1077 if self._allow_none:
1077 if self._allow_none:
1078 return value
1078 return value
1079
1079
1080 if value in self.values:
1080 if value in self.values:
1081 return value
1081 return value
1082 self.error(obj, value)
1082 self.error(obj, value)
1083
1083
1084 def info(self):
1084 def info(self):
1085 """ Returns a description of the trait."""
1085 """ Returns a description of the trait."""
1086 result = 'any of ' + repr(self.values)
1086 result = 'any of ' + repr(self.values)
1087 if self._allow_none:
1087 if self._allow_none:
1088 return result + ' or None'
1088 return result + ' or None'
1089 return result
1089 return result
1090
1090
1091 class CaselessStrEnum(Enum):
1091 class CaselessStrEnum(Enum):
1092 """An enum of strings that are caseless in validate."""
1092 """An enum of strings that are caseless in validate."""
1093
1093
1094 def validate(self, obj, value):
1094 def validate(self, obj, value):
1095 if value is None:
1095 if value is None:
1096 if self._allow_none:
1096 if self._allow_none:
1097 return value
1097 return value
1098
1098
1099 if not isinstance(value, basestring):
1099 if not isinstance(value, basestring):
1100 self.error(obj, value)
1100 self.error(obj, value)
1101
1101
1102 for v in self.values:
1102 for v in self.values:
1103 if v.lower() == value.lower():
1103 if v.lower() == value.lower():
1104 return v
1104 return v
1105 self.error(obj, value)
1105 self.error(obj, value)
1106
1106
1107 class Container(Instance):
1107 class Container(Instance):
1108 """An instance of a container (list, set, etc.)
1108 """An instance of a container (list, set, etc.)
1109
1109
1110 To be subclassed by overriding klass.
1110 To be subclassed by overriding klass.
1111 """
1111 """
1112 klass = None
1112 klass = None
1113 _valid_defaults = SequenceTypes
1113 _valid_defaults = SequenceTypes
1114 _trait = None
1114 _trait = None
1115
1115
1116 def __init__(self, trait=None, default_value=None, allow_none=True,
1116 def __init__(self, trait=None, default_value=None, allow_none=True,
1117 **metadata):
1117 **metadata):
1118 """Create a container trait type from a list, set, or tuple.
1118 """Create a container trait type from a list, set, or tuple.
1119
1119
1120 The default value is created by doing ``List(default_value)``,
1120 The default value is created by doing ``List(default_value)``,
1121 which creates a copy of the ``default_value``.
1121 which creates a copy of the ``default_value``.
1122
1122
1123 ``trait`` can be specified, which restricts the type of elements
1123 ``trait`` can be specified, which restricts the type of elements
1124 in the container to that TraitType.
1124 in the container to that TraitType.
1125
1125
1126 If only one arg is given and it is not a Trait, it is taken as
1126 If only one arg is given and it is not a Trait, it is taken as
1127 ``default_value``:
1127 ``default_value``:
1128
1128
1129 ``c = List([1,2,3])``
1129 ``c = List([1,2,3])``
1130
1130
1131 Parameters
1131 Parameters
1132 ----------
1132 ----------
1133
1133
1134 trait : TraitType [ optional ]
1134 trait : TraitType [ optional ]
1135 the type for restricting the contents of the Container. If unspecified,
1135 the type for restricting the contents of the Container. If unspecified,
1136 types are not checked.
1136 types are not checked.
1137
1137
1138 default_value : SequenceType [ optional ]
1138 default_value : SequenceType [ optional ]
1139 The default value for the Trait. Must be list/tuple/set, and
1139 The default value for the Trait. Must be list/tuple/set, and
1140 will be cast to the container type.
1140 will be cast to the container type.
1141
1141
1142 allow_none : Bool [ default True ]
1142 allow_none : Bool [ default True ]
1143 Whether to allow the value to be None
1143 Whether to allow the value to be None
1144
1144
1145 **metadata : any
1145 **metadata : any
1146 further keys for extensions to the Trait (e.g. config)
1146 further keys for extensions to the Trait (e.g. config)
1147
1147
1148 """
1148 """
1149 istrait = lambda t: isinstance(t, type) and issubclass(t, TraitType)
1149 istrait = lambda t: isinstance(t, type) and issubclass(t, TraitType)
1150
1150
1151 # allow List([values]):
1151 # allow List([values]):
1152 if default_value is None and not istrait(trait):
1152 if default_value is None and not istrait(trait):
1153 default_value = trait
1153 default_value = trait
1154 trait = None
1154 trait = None
1155
1155
1156 if default_value is None:
1156 if default_value is None:
1157 args = ()
1157 args = ()
1158 elif isinstance(default_value, self._valid_defaults):
1158 elif isinstance(default_value, self._valid_defaults):
1159 args = (default_value,)
1159 args = (default_value,)
1160 else:
1160 else:
1161 raise TypeError('default value of %s was %s' %(self.__class__.__name__, default_value))
1161 raise TypeError('default value of %s was %s' %(self.__class__.__name__, default_value))
1162
1162
1163 if istrait(trait):
1163 if istrait(trait):
1164 self._trait = trait()
1164 self._trait = trait()
1165 self._trait.name = 'element'
1165 self._trait.name = 'element'
1166 elif trait is not None:
1166 elif trait is not None:
1167 raise TypeError("`trait` must be a Trait or None, got %s"%repr_type(trait))
1167 raise TypeError("`trait` must be a Trait or None, got %s"%repr_type(trait))
1168
1168
1169 super(Container,self).__init__(klass=self.klass, args=args,
1169 super(Container,self).__init__(klass=self.klass, args=args,
1170 allow_none=allow_none, **metadata)
1170 allow_none=allow_none, **metadata)
1171
1171
1172 def element_error(self, obj, element, validator):
1172 def element_error(self, obj, element, validator):
1173 e = "Element of the '%s' trait of %s instance must be %s, but a value of %s was specified." \
1173 e = "Element of the '%s' trait of %s instance must be %s, but a value of %s was specified." \
1174 % (self.name, class_of(obj), validator.info(), repr_type(element))
1174 % (self.name, class_of(obj), validator.info(), repr_type(element))
1175 raise TraitError(e)
1175 raise TraitError(e)
1176
1176
1177 def validate(self, obj, value):
1177 def validate(self, obj, value):
1178 value = super(Container, self).validate(obj, value)
1178 value = super(Container, self).validate(obj, value)
1179 if value is None:
1179 if value is None:
1180 return value
1180 return value
1181
1181
1182 value = self.validate_elements(obj, value)
1182 value = self.validate_elements(obj, value)
1183
1183
1184 return value
1184 return value
1185
1185
1186 def validate_elements(self, obj, value):
1186 def validate_elements(self, obj, value):
1187 validated = []
1187 validated = []
1188 if self._trait is None or isinstance(self._trait, Any):
1188 if self._trait is None or isinstance(self._trait, Any):
1189 return value
1189 return value
1190 for v in value:
1190 for v in value:
1191 try:
1191 try:
1192 v = self._trait.validate(obj, v)
1192 v = self._trait.validate(obj, v)
1193 except TraitError:
1193 except TraitError:
1194 self.element_error(obj, v, self._trait)
1194 self.element_error(obj, v, self._trait)
1195 else:
1195 else:
1196 validated.append(v)
1196 validated.append(v)
1197 return self.klass(validated)
1197 return self.klass(validated)
1198
1198
1199
1199
1200 class List(Container):
1200 class List(Container):
1201 """An instance of a Python list."""
1201 """An instance of a Python list."""
1202 klass = list
1202 klass = list
1203
1203
1204 def __init__(self, trait=None, default_value=None, minlen=0, maxlen=sys.maxint,
1204 def __init__(self, trait=None, default_value=None, minlen=0, maxlen=sys.maxint,
1205 allow_none=True, **metadata):
1205 allow_none=True, **metadata):
1206 """Create a List trait type from a list, set, or tuple.
1206 """Create a List trait type from a list, set, or tuple.
1207
1207
1208 The default value is created by doing ``List(default_value)``,
1208 The default value is created by doing ``List(default_value)``,
1209 which creates a copy of the ``default_value``.
1209 which creates a copy of the ``default_value``.
1210
1210
1211 ``trait`` can be specified, which restricts the type of elements
1211 ``trait`` can be specified, which restricts the type of elements
1212 in the container to that TraitType.
1212 in the container to that TraitType.
1213
1213
1214 If only one arg is given and it is not a Trait, it is taken as
1214 If only one arg is given and it is not a Trait, it is taken as
1215 ``default_value``:
1215 ``default_value``:
1216
1216
1217 ``c = List([1,2,3])``
1217 ``c = List([1,2,3])``
1218
1218
1219 Parameters
1219 Parameters
1220 ----------
1220 ----------
1221
1221
1222 trait : TraitType [ optional ]
1222 trait : TraitType [ optional ]
1223 the type for restricting the contents of the Container. If unspecified,
1223 the type for restricting the contents of the Container. If unspecified,
1224 types are not checked.
1224 types are not checked.
1225
1225
1226 default_value : SequenceType [ optional ]
1226 default_value : SequenceType [ optional ]
1227 The default value for the Trait. Must be list/tuple/set, and
1227 The default value for the Trait. Must be list/tuple/set, and
1228 will be cast to the container type.
1228 will be cast to the container type.
1229
1229
1230 minlen : Int [ default 0 ]
1230 minlen : Int [ default 0 ]
1231 The minimum length of the input list
1231 The minimum length of the input list
1232
1232
1233 maxlen : Int [ default sys.maxint ]
1233 maxlen : Int [ default sys.maxint ]
1234 The maximum length of the input list
1234 The maximum length of the input list
1235
1235
1236 allow_none : Bool [ default True ]
1236 allow_none : Bool [ default True ]
1237 Whether to allow the value to be None
1237 Whether to allow the value to be None
1238
1238
1239 **metadata : any
1239 **metadata : any
1240 further keys for extensions to the Trait (e.g. config)
1240 further keys for extensions to the Trait (e.g. config)
1241
1241
1242 """
1242 """
1243 self._minlen = minlen
1243 self._minlen = minlen
1244 self._maxlen = maxlen
1244 self._maxlen = maxlen
1245 super(List, self).__init__(trait=trait, default_value=default_value,
1245 super(List, self).__init__(trait=trait, default_value=default_value,
1246 allow_none=allow_none, **metadata)
1246 allow_none=allow_none, **metadata)
1247
1247
1248 def length_error(self, obj, value):
1248 def length_error(self, obj, value):
1249 e = "The '%s' trait of %s instance must be of length %i <= L <= %i, but a value of %s was specified." \
1249 e = "The '%s' trait of %s instance must be of length %i <= L <= %i, but a value of %s was specified." \
1250 % (self.name, class_of(obj), self._minlen, self._maxlen, value)
1250 % (self.name, class_of(obj), self._minlen, self._maxlen, value)
1251 raise TraitError(e)
1251 raise TraitError(e)
1252
1252
1253 def validate_elements(self, obj, value):
1253 def validate_elements(self, obj, value):
1254 length = len(value)
1254 length = len(value)
1255 if length < self._minlen or length > self._maxlen:
1255 if length < self._minlen or length > self._maxlen:
1256 self.length_error(obj, value)
1256 self.length_error(obj, value)
1257
1257
1258 return super(List, self).validate_elements(obj, value)
1258 return super(List, self).validate_elements(obj, value)
1259
1259
1260
1260
1261 class Set(Container):
1261 class Set(Container):
1262 """An instance of a Python set."""
1262 """An instance of a Python set."""
1263 klass = set
1263 klass = set
1264
1264
1265 class Tuple(Container):
1265 class Tuple(Container):
1266 """An instance of a Python tuple."""
1266 """An instance of a Python tuple."""
1267 klass = tuple
1267 klass = tuple
1268
1268
1269 def __init__(self, *traits, **metadata):
1269 def __init__(self, *traits, **metadata):
1270 """Tuple(*traits, default_value=None, allow_none=True, **medatata)
1270 """Tuple(*traits, default_value=None, allow_none=True, **medatata)
1271
1271
1272 Create a tuple from a list, set, or tuple.
1272 Create a tuple from a list, set, or tuple.
1273
1273
1274 Create a fixed-type tuple with Traits:
1274 Create a fixed-type tuple with Traits:
1275
1275
1276 ``t = Tuple(Int, Str, CStr)``
1276 ``t = Tuple(Int, Str, CStr)``
1277
1277
1278 would be length 3, with Int,Str,CStr for each element.
1278 would be length 3, with Int,Str,CStr for each element.
1279
1279
1280 If only one arg is given and it is not a Trait, it is taken as
1280 If only one arg is given and it is not a Trait, it is taken as
1281 default_value:
1281 default_value:
1282
1282
1283 ``t = Tuple((1,2,3))``
1283 ``t = Tuple((1,2,3))``
1284
1284
1285 Otherwise, ``default_value`` *must* be specified by keyword.
1285 Otherwise, ``default_value`` *must* be specified by keyword.
1286
1286
1287 Parameters
1287 Parameters
1288 ----------
1288 ----------
1289
1289
1290 *traits : TraitTypes [ optional ]
1290 *traits : TraitTypes [ optional ]
1291 the tsype for restricting the contents of the Tuple. If unspecified,
1291 the tsype for restricting the contents of the Tuple. If unspecified,
1292 types are not checked. If specified, then each positional argument
1292 types are not checked. If specified, then each positional argument
1293 corresponds to an element of the tuple. Tuples defined with traits
1293 corresponds to an element of the tuple. Tuples defined with traits
1294 are of fixed length.
1294 are of fixed length.
1295
1295
1296 default_value : SequenceType [ optional ]
1296 default_value : SequenceType [ optional ]
1297 The default value for the Tuple. Must be list/tuple/set, and
1297 The default value for the Tuple. Must be list/tuple/set, and
1298 will be cast to a tuple. If `traits` are specified, the
1298 will be cast to a tuple. If `traits` are specified, the
1299 `default_value` must conform to the shape and type they specify.
1299 `default_value` must conform to the shape and type they specify.
1300
1300
1301 allow_none : Bool [ default True ]
1301 allow_none : Bool [ default True ]
1302 Whether to allow the value to be None
1302 Whether to allow the value to be None
1303
1303
1304 **metadata : any
1304 **metadata : any
1305 further keys for extensions to the Trait (e.g. config)
1305 further keys for extensions to the Trait (e.g. config)
1306
1306
1307 """
1307 """
1308 default_value = metadata.pop('default_value', None)
1308 default_value = metadata.pop('default_value', None)
1309 allow_none = metadata.pop('allow_none', True)
1309 allow_none = metadata.pop('allow_none', True)
1310
1310
1311 istrait = lambda t: isinstance(t, type) and issubclass(t, TraitType)
1311 istrait = lambda t: isinstance(t, type) and issubclass(t, TraitType)
1312
1312
1313 # allow Tuple((values,)):
1313 # allow Tuple((values,)):
1314 if len(traits) == 1 and default_value is None and not istrait(traits[0]):
1314 if len(traits) == 1 and default_value is None and not istrait(traits[0]):
1315 default_value = traits[0]
1315 default_value = traits[0]
1316 traits = ()
1316 traits = ()
1317
1317
1318 if default_value is None:
1318 if default_value is None:
1319 args = ()
1319 args = ()
1320 elif isinstance(default_value, self._valid_defaults):
1320 elif isinstance(default_value, self._valid_defaults):
1321 args = (default_value,)
1321 args = (default_value,)
1322 else:
1322 else:
1323 raise TypeError('default value of %s was %s' %(self.__class__.__name__, default_value))
1323 raise TypeError('default value of %s was %s' %(self.__class__.__name__, default_value))
1324
1324
1325 self._traits = []
1325 self._traits = []
1326 for trait in traits:
1326 for trait in traits:
1327 t = trait()
1327 t = trait()
1328 t.name = 'element'
1328 t.name = 'element'
1329 self._traits.append(t)
1329 self._traits.append(t)
1330
1330
1331 if self._traits and default_value is None:
1331 if self._traits and default_value is None:
1332 # don't allow default to be an empty container if length is specified
1332 # don't allow default to be an empty container if length is specified
1333 args = None
1333 args = None
1334 super(Container,self).__init__(klass=self.klass, args=args,
1334 super(Container,self).__init__(klass=self.klass, args=args,
1335 allow_none=allow_none, **metadata)
1335 allow_none=allow_none, **metadata)
1336
1336
1337 def validate_elements(self, obj, value):
1337 def validate_elements(self, obj, value):
1338 if not self._traits:
1338 if not self._traits:
1339 # nothing to validate
1339 # nothing to validate
1340 return value
1340 return value
1341 if len(value) != len(self._traits):
1341 if len(value) != len(self._traits):
1342 e = "The '%s' trait of %s instance requires %i elements, but a value of %s was specified." \
1342 e = "The '%s' trait of %s instance requires %i elements, but a value of %s was specified." \
1343 % (self.name, class_of(obj), len(self._traits), repr_type(value))
1343 % (self.name, class_of(obj), len(self._traits), repr_type(value))
1344 raise TraitError(e)
1344 raise TraitError(e)
1345
1345
1346 validated = []
1346 validated = []
1347 for t,v in zip(self._traits, value):
1347 for t,v in zip(self._traits, value):
1348 try:
1348 try:
1349 v = t.validate(obj, v)
1349 v = t.validate(obj, v)
1350 except TraitError:
1350 except TraitError:
1351 self.element_error(obj, v, t)
1351 self.element_error(obj, v, t)
1352 else:
1352 else:
1353 validated.append(v)
1353 validated.append(v)
1354 return tuple(validated)
1354 return tuple(validated)
1355
1355
1356
1356
1357 class Dict(Instance):
1357 class Dict(Instance):
1358 """An instance of a Python dict."""
1358 """An instance of a Python dict."""
1359
1359
1360 def __init__(self, default_value=None, allow_none=True, **metadata):
1360 def __init__(self, default_value=None, allow_none=True, **metadata):
1361 """Create a dict trait type from a dict.
1361 """Create a dict trait type from a dict.
1362
1362
1363 The default value is created by doing ``dict(default_value)``,
1363 The default value is created by doing ``dict(default_value)``,
1364 which creates a copy of the ``default_value``.
1364 which creates a copy of the ``default_value``.
1365 """
1365 """
1366 if default_value is None:
1366 if default_value is None:
1367 args = ((),)
1367 args = ((),)
1368 elif isinstance(default_value, dict):
1368 elif isinstance(default_value, dict):
1369 args = (default_value,)
1369 args = (default_value,)
1370 elif isinstance(default_value, SequenceTypes):
1370 elif isinstance(default_value, SequenceTypes):
1371 args = (default_value,)
1371 args = (default_value,)
1372 else:
1372 else:
1373 raise TypeError('default value of Dict was %s' % default_value)
1373 raise TypeError('default value of Dict was %s' % default_value)
1374
1374
1375 super(Dict,self).__init__(klass=dict, args=args,
1375 super(Dict,self).__init__(klass=dict, args=args,
1376 allow_none=allow_none, **metadata)
1376 allow_none=allow_none, **metadata)
1377
1377
1378 class TCPAddress(TraitType):
1378 class TCPAddress(TraitType):
1379 """A trait for an (ip, port) tuple.
1379 """A trait for an (ip, port) tuple.
1380
1380
1381 This allows for both IPv4 IP addresses as well as hostnames.
1381 This allows for both IPv4 IP addresses as well as hostnames.
1382 """
1382 """
1383
1383
1384 default_value = ('127.0.0.1', 0)
1384 default_value = ('127.0.0.1', 0)
1385 info_text = 'an (ip, port) tuple'
1385 info_text = 'an (ip, port) tuple'
1386
1386
1387 def validate(self, obj, value):
1387 def validate(self, obj, value):
1388 if isinstance(value, tuple):
1388 if isinstance(value, tuple):
1389 if len(value) == 2:
1389 if len(value) == 2:
1390 if isinstance(value[0], basestring) and isinstance(value[1], int):
1390 if isinstance(value[0], basestring) and isinstance(value[1], int):
1391 port = value[1]
1391 port = value[1]
1392 if port >= 0 and port <= 65535:
1392 if port >= 0 and port <= 65535:
1393 return value
1393 return value
1394 self.error(obj, value)
1394 self.error(obj, value)
General Comments 0
You need to be logged in to leave comments. Login now