Automatic script recording¶
This package provides a very handy and powerful Python script recording facility. This can be used to:
record all actions performed on a traits based UI into a human readable, Python script that should be able to recreate your UI actions.
easily learn the scripting API of an application.
This package is not just a toy framework and is powerful enough to provide full script recording to the Mayavi application. Mayavi is a powerful 3D visualization tool that is part of ETS.
The scripting API¶
The scripting API primarily allows you to record UI actions for objects
that have Traits. Technically the framework listens to all trait
changes so will work outside a UI. We do not document the full API
here, the best place to look for that is the
apptools.scripting.recorder
module which is reasonably well
documented. We provide a high level overview of the library.
The quickest way to get started is to look at a small example.
A tour by example¶
The following example is taken from the test suite. Consider a set of simple objects organized in a hierarchy:
from traits.api import (HasTraits, Float, Instance,
Str, List, Bool, HasStrictTraits, Tuple, PrefixMap, Range,
Trait)
from apptools.scripting.api import (Recorder, recordable,
set_recorder)
class Property(HasStrictTraits):
color = Tuple(Range(0.0, 1.0), Range(0.0, 1.0), Range(0.0, 1.0))
opacity = Range(0.0, 1.0, 1.0)
representation = PrefixMap(
{"surface": 2, "wireframe": 1, "points": 0},
default_value="surface"
)
class Toy(HasTraits):
color = Str
type = Str
# Note the use of the trait metadata to ignore this trait.
ignore = Bool(False, record=False)
class Child(HasTraits):
name = Str('child')
age = Float(10.0)
# The recorder walks through sub-instances if they are marked
# with record=True
property = Instance(Property, (), record=True)
toy = Instance(Toy, record=True)
friends = List(Str)
# The decorator records the method.
@recordable
def grow(self, x):
"""Increase age by x years."""
self.age += x
class Parent(HasTraits):
children = List(Child, record=True)
recorder = Instance(Recorder, record=False)
Using these simple classes we first create a simple object hierarchy as follows:
p = Parent()
c = Child()
t = Toy()
c.toy = t
p.children.append(c)
Given this hierarchy, we’d like to be able to record a script. To do this we setup the recording infrastructure:
from mayavi.core.recorder import Recorder, set_recorder
# Create a recorder.
r = Recorder()
# Set the global recorder so the decorator works.
set_recorder(r)
r.register(p)
r.recording = True
The key method here is the r.register(p)
call above. It looks at
the traits of p
and finds all traits and nested objects that specify
a record=True
in their trait metadata (all methods starting and
ending with _
are ignored). All sub-objects are in turn registered
with the recorder and so on. Callbacks are attached to traits changes
and these are wired up to produce readable and executable code. The
set_recorder(r)
call is also very important and sets the global
recorder so the framework listens to any functions that are decorated
with the recordable
decorator.
Now lets test this out like so:
# The following will be recorded.
c.name = 'Shiva'
c.property.representation = 'w'
c.property.opacity = 0.4
c.grow(1)
To see what’s been recorded do this:
print(r.script)
This prints:
child = parent.children[0]
child.name = 'Shiva'
child.property.representation = 'wireframe'
child.property.opacity = 0.40000000000000002
child.grow(1)
The recorder internally maintains a mapping between objects and unique
names for each object. It also stores the information about the
location of a particular object in the object hierarchy. For example,
the path to the Toy
instance in the hierarchy above is
parent.children[0].toy
. Since scripting with lists this way can be
tedious, the recorder first instantiates the child
:
child = parent.children[0]
Subsequent lines use the child
attribute. The recorder always tries
to instantiate the object referred to using its path information in this
manner.
To record a function or method call one must simply decorate the
function/method with the recordable
decorator. Nested recordable
functions are not recorded and trait changes are also not recorded if
done inside a recordable function.
Note
It is very important to note that the global recorder must be set via the
set_recorder
method. Therecordable
decorator relies on this being set to work.The
recordable
decorator will work with plain Python classes and with functions too.
To stop recording do this:
r.unregister(p)
r.recording = False
The r.unregister(p)
reverses the r.register(p)
call and
unregisters all nested objects as well.
Advanced use cases¶
Here are a few advanced use cases.
The API also provides a
RecorderWithUI
class that provides a simple user interface that prints the recorded script and allows the user to save the script.Sometimes it is not enough to just record trait changes, one may want to pass an arbitrary string or command when recording is occurring. To allow for this, if one defines a
recorder
trait on the object, it is set to the current recorder. One can then use this recorder to do whatever one wants. This is very convenient.To ignore specific traits one must specify either a
record=False
metadata to the trait definition or specify a list of strings to theregister
method in theignore
keyword argument.If you want to use a specific name for an object on the script you can pass the
script_id
parameter to the register function.
For more details on the recorder itself we suggest reading the module source code. It is fairly well documented and with the above background should be enough to get you going.