1
#------------------------------------------------------------------------------
2
# Copyright (c) 2008, Riverbank Computing Limited
5
# This software is provided without warranty under the terms of the BSD
6
# license included in enthought/LICENSE.txt and may be redistributed only
7
# under the conditions described in the aforementioned license. The license
8
# is also available online at http://www.enthought.com/licenses/BSD.txt
9
# Thanks for using Enthought open source!
11
# Author: Riverbank Computing Limited
12
# Description: <Enthought application scripting package component>
13
#------------------------------------------------------------------------------
16
# Standard library imports.
21
# Enthought library imports.
22
from enthought.traits.api import Any, Bool, Callable, Dict, Event, HasTraits, \
23
implements, Instance, Int, List, Property, Str, Unicode
26
from bind_event import BindEvent
27
from i_bind_event import IBindEvent
28
from i_script_manager import IScriptManager
29
from lazy_namespace import add_to_namespace, FactoryWrapper, LazyNamespace
30
from scriptable_type import make_object_scriptable
33
class _BoundObject(HasTraits):
34
"""The base class for any object that can be bound to a name."""
36
#### '_BoundObject' interface #############################################
38
# Set if the object was explicitly bound.
39
explicitly_bound = Bool(True)
41
# The name the object is bound to.
44
# The object being bound.
48
class _ScriptObject(_BoundObject):
49
"""The _ScriptObject class encapsulates a scriptable object."""
51
#### '_BoundObject' interface #############################################
53
# The object being bound.
56
#### '_ScriptObject' interface ############################################
58
# The positional arguments passed to __init__ after being converted to
59
# strings. A particular argument may be an exception if it couldn't be
63
# The keyword arguments passed to __init__ after being converted to
64
# strings. A particular argument may be an exception if it couldn't be
68
# The id of the object.
71
# A weak reference to the object.
74
# The type of the scriptable object.
77
###########################################################################
79
###########################################################################
82
"""The property getter."""
87
class _ScriptCall(HasTraits):
88
""" The _ScriptCall class is the base class for all script calls. """
90
#### '_ScriptCall' interface ##############################################
92
# The name of the call.
95
# The scriptable object.
98
###########################################################################
99
# '_ScriptCall' interface.
100
###########################################################################
102
def as_str(self, sm, so_needed):
103
""" Return the string equivalent of the call, updated the list of
104
needed scriptable objects if required.
107
raise NotImplementedError
110
class _ScriptTraitGet(_ScriptCall):
111
""" The _ScriptTraitGet class encapsulates a single call to the get of a
115
#### '_ScriptTraitGet' interface ##########################################
117
# Set if the getter has side effects.
118
has_side_effects = Bool(False)
120
# The result of the get. Keeping a reference to it means that the memory
124
###########################################################################
125
# '_ScriptCall' interface.
126
###########################################################################
128
def as_str(self, sm, so_needed):
129
""" Return the string equivalent of the call, updated the list of
130
needed scriptable objects if required.
133
# Ignore if it is no longer bound.
137
if self.result is None:
140
nr, _ = sm._results[id(self.result)]
147
# Unless getter has side effects, if the result is not needed then
148
# don't bother including it in the script.
149
if not self.has_side_effects and rstr == "":
152
so = sm.arg_as_string(self.so, so_needed)
154
return "%s%s.%s" % (rstr, so, self.name)
157
class _ScriptTraitSet(_ScriptCall):
158
""" The _ScriptTraitSet class encapsulates a single call to the set of a
162
#### '_ScriptTraitSet' interface ##########################################
164
# The value the trait is set to.
167
###########################################################################
168
# '_ScriptCall' interface.
169
###########################################################################
171
def as_str(self, sm, so_needed):
172
""" Return the string equivalent of the call, updated the list of
173
needed scriptable objects if required.
176
# Ignore if it is no longer bound.
180
so = sm.arg_as_string(self.so, so_needed)
181
value = sm.arg_as_string(self.value, so_needed)
183
return "%s.%s = %s" % (so, self.name, value)
186
class _ScriptMethod(_ScriptCall):
187
""" The _ScriptMethod class encapsulates a single call to a scriptable
191
#### '_ScriptMethod' interface ############################################
193
# The positional arguments passed to the method after being converted to
194
# strings. A particular argument may be an exception if it couldn't be
198
# The keyword arguments passed to the method after being converted to
199
# strings. A particular argument may be an exception if it couldn't be
203
# The result of the method call. Keeping a reference to it means that the
204
# memory can't get reused.
207
###########################################################################
208
# '_ScriptCall' interface.
209
###########################################################################
211
def as_str(self, sm, so_needed):
212
""" Return the string equivalent of the call, updated the list of
213
needed scriptable objects if required.
216
# Ignore if it is no longer bound.
217
if self.so and not self.so.name:
220
if self.result is None:
222
elif type(self.result) is type(()):
226
for r in self.result:
227
nr, _ = sm._results[id(r)]
230
rlist.append("r%d" % nr)
236
rstr = ", ".join(rlist) + " = "
240
nr, _ = sm._results[id(self.result)]
248
so = sm.arg_as_string(self.so, so_needed) + '.'
252
args = sm.args_as_string_list(self.args, self.kwargs, so_needed)
254
return "%s%s%s(%s)" % (rstr, so, self.name, ", ".join(args))
257
class _FactoryObject(_BoundObject):
258
""" The _FactoryObject class wraps a factory that lazily creates
262
#### '_BoundObject' interface #############################################
264
# The object being bound.
267
#### '_FactoryObject' interface ###########################################
269
# The optional object that defines the scripting API.
272
# The scriptable object factory.
275
# The optional attribute include list.
278
# The optional attribute exclude list.
281
###########################################################################
283
###########################################################################
286
"""The property getter."""
288
return FactoryWrapper(factory=self.factory, api=self.api,
289
includes=self.includes, excludes=self.excludes)
292
class ScriptManager(HasTraits):
293
""" The ScriptManager class is the default implementation of
297
implements(IScriptManager)
299
#### 'IScriptManager' interface ###########################################
301
# This event is fired whenever a scriptable object is bound or unbound. It
302
# is intended to be used by an interactive Python shell to give the
303
# advanced user access to the scriptable objects. If an object is created
304
# via a factory then the event is fired when the factory is called, and not
305
# when the factory is bound.
306
bind_event = Event(IBindEvent)
308
# This is set if user actions are being recorded as a script. It is
309
# maintained by the script manager.
310
recording = Bool(False)
312
# This is the text of the script currently being recorded (or the last
313
# recorded script if none is currently being recorded). It is updated
314
# automatically as the user performs actions.
315
script = Property(Unicode)
317
# This event is fired when the recorded script changes. The value of the
318
# event will be the ScriptManager instance.
319
script_updated = Event(IScriptManager)
321
#### Private interface ####################################################
323
# The list of calls to scriptable calls.
324
_calls = List(Instance(_ScriptCall))
326
# The dictionary of bound names. The value is the next numerical suffix
327
# to use when the binding policy is 'auto'.
330
# The dictionary of _BoundObject instances keyed by the name the object is
334
# The next sequential result number.
335
_next_result_nr = Int
337
# The results returned by previous scriptable calls. The key is the id()
338
# of the result object. The value is a two element tuple of the sequential
339
# result number (easier for the user to use than the id()) and the result
343
# The dictionary of _ScriptObject instances keyed by the object's id().
346
# The dictionary of _ScriptObject instances keyed by the a weak reference
350
# The date and time when the script was recorded.
353
###########################################################################
354
# 'IScriptManager' interface.
355
###########################################################################
357
def bind(self, obj, name=None, bind_policy='unique', api=None,
358
includes=None, excludes=None):
359
""" Bind obj to name and make (by default) its public methods and
360
traits (ie. those not beginning with an underscore) scriptable. The
361
default value of name is the type of obj with the first character
362
forced to lower case. name may be a dotted name (eg. 'abc.def.xyz').
364
bind_policy determines what happens if the name is already bound. If
365
the policy is 'auto' then a numerical suffix will be added to the name,
366
if necessary, to make it unique. If the policy is 'unique' then an
367
exception is raised. If the policy is 'rebind' then the previous
368
binding is discarded. The default is 'unique'
370
If api is given then it is a class, or a list of classes, that define
371
the attributes that will be made scriptable.
373
Otherwise if includes is given it is a list of names of attributes that
374
will be made scriptable.
376
Otherwise all the public attributes of scripted_type will be made
377
scriptable except those in the excludes list.
380
# Register the object.
381
self.new_object(obj, obj.__class__, name=name, bind_policy=bind_policy)
383
# Make it scriptable.
384
make_object_scriptable(obj, api=api, includes=includes,
387
def bind_factory(self, factory, name, bind_policy='unique', api=None,
388
includes=None, excludes=None):
389
""" Bind factory to name. This does the same as the bind() method
390
except that it uses a factory that will be called later on to create
391
the object only if the object is needed.
393
See the documentation for bind() for a description of the remaining
397
name = self._unique_name(name, bind_policy)
398
self._namespace[name] = _FactoryObject(name=name, factory=factory,
399
api=api, includes=includes, excludes=excludes)
401
def run(self, script):
402
""" Run the given script, either a string or a file-like object.
405
# Initialise the namespace with all explicitly bound objects.
406
nspace = LazyNamespace()
407
for name, bo in self._namespace.iteritems():
408
if bo.explicitly_bound:
409
add_to_namespace(bo.obj, name, nspace)
411
exec script in nspace
413
def run_file(self, file_name):
414
""" Run the given script file.
421
def start_recording(self):
422
""" Start the recording of user actions. The 'script' trait is cleared
423
and all subsequent actions are added to 'script'. The 'recording'
424
trait is updated appropriately.
428
self._next_result_nr = 0
431
self.recording = True
432
self.script_updated = self
434
def stop_recording(self):
435
""" Stop the recording of user actions. The 'recording' trait is
436
updated appropriately.
439
self.recording = False
441
###########################################################################
442
# 'ScriptManager' interface.
443
###########################################################################
445
def record_method(self, func, args, kwargs):
446
""" Record the call of a method of a ScriptableObject instance and
447
return the result. This is intended to be used only by the scriptable
451
# Record the arguments before the function has a chance to modify
453
srec = self._new_method(func, args, kwargs)
454
result = func(*args, **kwargs)
455
self._add_method(srec, result)
457
self.script_updated = self
459
result = func(*args, **kwargs)
463
def record_trait_get(self, obj, name, result):
464
""" Record the get of a trait of a scriptable object. This is intended
465
to be used only by the Scriptable trait getter.
469
side_effects = self._add_trait_get(obj, name, result)
471
# Don't needlessly fire the event if there are no side effects.
473
self.script_updated = self
475
def record_trait_set(self, obj, name, value):
476
""" Record the set of a trait of a scriptable object. This is intended
477
to be used only by the Scriptable trait getter.
481
self._add_trait_set(obj, name, value)
483
self.script_updated = self
485
def new_object(self, obj, scripted_type, args=None, kwargs=None, name=None,
487
""" Register a scriptable object and the arguments used to create it.
488
If no arguments were provided then assume the object is being
492
# The name defaults to the type name.
494
name = scripted_type.__name__
495
name = name[0].lower() + name[1:]
497
name = self._unique_name(name, bind_policy)
500
obj_ref = weakref.ref(obj, self._gc_script_obj)
502
so = _ScriptObject(name=name, obj_id=obj_id, obj_ref=obj_ref,
503
scripted_type=scripted_type)
505
# If we are told how to create the object then it must be implicitly
508
# Convert each argument to its string representation if possible.
509
# Doing this now avoids problems with mutable arguments.
510
so.args = [self._scriptable_object_as_string(a) for a in args]
512
for n, value in kwargs.iteritems():
513
so.kwargs[n] = self._scriptable_object_as_string(value)
515
so.explicitly_bound = False
517
# Remember the scriptable object via the different access methods.
518
self._so_by_id[obj_id] = so
519
self._so_by_ref[obj_ref] = so
520
self._namespace[name] = so
522
# Note that if anything listening to this event doesn't use weak
523
# references then the object will be kept alive.
524
self.bind_event = BindEvent(name=name, obj=obj)
527
def args_as_string_list(args, kwargs, so_needed=None):
528
""" Return a complete argument list from sets of positional and keyword
529
arguments. Update the optional so_needed list for those arguments that
530
refer to a scriptable object.
533
if so_needed is None:
539
s = ScriptManager.arg_as_string(arg, so_needed)
542
for name, value in kwargs.iteritems():
543
s = ScriptManager.arg_as_string(value, so_needed)
544
all_args.append('%s=%s' % (name, s))
549
def arg_as_string(arg, so_needed):
550
""" Return the string representation of an argument. Update the
551
so_needed list if the argument refers to a scriptable object. Any
552
delayed conversion exception is handled here.
555
if isinstance(arg, Exception):
558
if isinstance(arg, _ScriptObject):
559
# Check it hasn't been unbound.
561
raise NameError("%s has been unbound but is needed by the script" % arg.obj_ref())
563
# Add it to the needed list if it isn't already there.
564
if arg not in so_needed:
565
so_needed.append(arg)
571
###########################################################################
573
###########################################################################
575
def _new_method(self, func, args, kwargs):
576
""" Return an object that encapsulates a call to a scriptable method.
577
_add_method() must be called to add it to the current script.
580
# Convert each argument to its string representation if possible.
581
# Doing this now avoids problems with mutable arguments.
582
nargs = [self._object_as_string(arg) for arg in args]
584
if type(func) is types.FunctionType:
591
for name, value in kwargs.iteritems():
592
nkwargs[name] = self._object_as_string(value)
594
return _ScriptMethod(name=func.func_name, so=so, args=nargs,
597
def _add_method(self, entry, result):
598
""" Add a method call (returned by _new_method()), with it's associated
599
result and ID, to the current script.
604
if result is not None:
605
# Assume that a tuple represents multiple returned values - not
606
# necessarily a valid assumption unless we make it a rule for
607
# scriptable functions.
608
if type(result) is type(()):
612
self._save_result(result)
614
entry.result = result
616
self._calls.append(entry)
618
def _add_trait_get(self, obj, name, result):
619
""" Add a call to a trait getter, with it's associated result and ID,
620
to the current script. Return True if the get had side effects.
625
side_effects = obj.trait(name).has_side_effects
627
if side_effects is None:
630
so = self._object_as_string(obj)
632
if result is not None:
633
self._save_result(result)
635
self._calls.append(_ScriptTraitGet(so=so, name=name, result=result,
636
has_side_effects=side_effects))
640
def _add_trait_set(self, obj, name, value):
641
""" Add a call to a trait setter, with it's associated value and ID,
642
to the current script.
647
so = self._object_as_string(obj)
648
value = self._object_as_string(value)
650
self._calls.append(_ScriptTraitSet(so=so, name=name, value=value))
652
def _unique_name(self, name, bind_policy):
653
""" Return a name that is guaranteed to be unique according to the bind
657
# See if the name is already is use.
658
bo = self._namespace.get(name)
661
self._names[name] = 1
662
elif bind_policy == 'auto':
663
suff = self._names[name]
664
self._names[name] = suff + 1
666
name = '%s%d' % (name, suff)
667
elif bind_policy == 'rebind':
670
raise NameError("\"%s\" is already bound to a scriptable object" % name)
674
def _unbind(self, bo):
675
"""Unbind the given bound object."""
677
# Tell everybody it is no longer bound. Don't bother if it is a
678
# factory because the corresponding bound event wouldn't have been
680
if not isinstance(bo, _FactoryObject):
681
self.bind_event = BindEvent(name=bo.name, obj=None)
684
del self._namespace[bo.name]
688
def _gc_script_obj(obj_ref):
689
""" The callback invoked when a scriptable object is garbage collected.
692
# Avoid recursive imports.
693
from package_globals import get_script_manager
695
sm = get_script_manager()
696
so = sm._so_by_ref[obj_ref]
701
del sm._so_by_id[so.obj_id]
702
del sm._so_by_ref[so.obj_ref]
704
def _start_script(self):
705
""" Save when a script recording is started. """
707
if len(self._calls) == 0:
708
self._when_started = datetime.datetime.now().strftime('%c')
710
def _object_as_string(self, obj):
711
""" Convert an object to a string as it will appear in a script. An
712
exception may be returned (not raised) if there was an error in the
718
# See if the argument is the result of a previous call.
719
nr, _ = self._results.get(obj_id, (None, None))
723
nr = self._next_result_nr
724
self._next_result_nr += 1
726
# Key on the ID of the argument (which is hashable) rather than
727
# the argument itself (which might not be).
728
self._results[obj_id] = (nr, obj)
732
return self._scriptable_object_as_string(obj)
734
def _scriptable_object_as_string(self, obj):
735
""" Convert an object to a string as it will appear in a script. An
736
exception may be returned (not raised) if there was an error in the
742
# If it is a scriptable object we return the object and convert it to a
743
# string later when we know it is really needed.
744
so = self._so_by_id.get(obj_id)
749
# Use the repr result if it doesn't appear to be the generic response,
750
# ie. it doesn't contain its own address as a hex string.
753
if hex(obj_id) not in s:
756
# We don't know how to represent the argument as a string. This is
757
# most likely because an appropriate __init__ hasn't been made
758
# scriptable. We don't raise an exception until the user decides to
759
# convert the calls to a script.
760
return ValueError("unable to create a script representation of %s" % obj)
762
def _save_result(self, result):
763
""" Save the result of a call to a scriptable method so that it can be
767
if id(result) not in self._results:
768
self._results[id(result)] = (-1, result)
770
def _get_script(self):
771
""" Convert the current list of calls to a script. """
773
# Handle the trivial case.
774
if len(self._calls) == 0:
777
# Generate the header.
778
header = "# Script generated %s" % self._when_started
780
# Generate the calls.
784
for call in self._calls:
785
s = call.as_str(self, so_needed)
790
calls = "\n".join(calls)
792
# Generate the scriptable object constructors.
797
if so.explicitly_bound:
800
so_type = so.scripted_type
801
args = self.args_as_string_list(so.args, so.kwargs)
803
ctors.append("%s = %s(%s)" % (so.name, so_type.__name__, ", ".join(args)))
805
# See if a new import is needed.
806
if so_type not in types_needed:
807
types_needed.append(so_type)
809
ctors = "\n".join(ctors)
811
# Generate the import statements.
814
for so_type in types_needed:
815
imports.append("from %s import %s" % (so_type.__module__, so_type.__name__))
817
imports = "\n".join(imports)
819
return "\n\n".join([header, imports, ctors, calls]) + "\n"