3.9. Attributes and Callbacks¶
Intro to SAGA attribute interface.
3.9.1. Callbacks –
Callback base class.
All objects using the Attribute Interface allow to register a callback for any changes of its attributes, such as ‘state’ and ‘state_detail’. Those callbacks can be python call’ables, or derivates of this callback base class. Instances which inherit this base class MUST implement (overload) the cb() method.
The callable, or the callback’s cb() method is what is invoked whenever the SAGA implementation is notified of an change on the monitored object’s attribute.
The cb instance receives three parameters upon invocation:
- obj: the watched object instance
- key: the watched attribute (e.g. ‘state’ or ‘state_detail’)
- val: the new value of the watched attribute
If the callback returns ‘True’, it will remain registered after invocation, to monitor the attribute for the next subsequent state change. On returning ‘False’ (or nothing), the callback will not be called again.
To register a callback on a object instance, use:
class MyCallback (saga.Callback): def __init__ (self): pass def cb (self, obj, key, val) : print " %s\n %s (%s) : %s" % self._msg, obj, key, val jd = saga.job.Description () jd.executable = "/bin/date" js = saga.job.Service ("fork://localhost/") job = js.create_job(jd) cb = MyCallback() job.add_callback(saga.STATE, cb) job.run()
See documentation of the
saga.Attributeinterface for further details and examples.
cb(obj, key, val)¶
This is the method that needs to be implemented by the application
obj: the watched object instance key: the watched attribute val: the new value of the watched attribute
keep: bool, signals to keep (True) or remove (False) the callback after invocation
Callback invocation MAY (and in general will) happen in a separate thread – so the application need to make sure that the callback code is thread-safe.
The boolean return value is used to signal if the callback should continue to listen for events (return True) , or if it rather should get unregistered after this invocation (return False).
3.9.2. Attribute –
Attribute Interface Class
The Attributes interface implements the attribute semantics of the SAGA Core API specification (http://ogf.org/documents/GFD.90.pdf). Additionally, this implementation provides that semantics the python property interface. Note that a single set of attributes is internally managed, no matter what interface is used for access.
A class which uses this interface can internally specify which attributes can be set, and what type they have. Also, default values can be specified, and the class provides a rudimentary support for converting scalar attributes into vector attributes and back.
Also, the consumer of this API can register callbacks, which get triggered on changes to specific attribute values.
Example use case:
# -------------------------------------------------------------------------------- class Transliterator ( saga.Attributes ) : def __init__ (self, *args, **kwargs) : # setting attribs to non-extensible will cause the cal to init below to # complain if attributes are specified. Default is extensible. # self._attributes_extensible (False) # pass args to base class init (requires 'extensible') super (Transliterator, self).__init__ (*args, **kwargs) # setup class attribs self._attributes_register ('apple', 'Appel', URL, SCALAR, WRITEABLE) self._attributes_register ('plum', 'Pruim', STRING, SCALAR, READONLY) # setting attribs to non-extensible at *this* point will have allowed # custom user attribs on __init__ time (via args), but will then forbid # any additional custom attributes. # self._attributes_extensible (False) # -------------------------------------------------------------------------------- if __name__ == "__main__": # define a callback method. This callback can get registered for # attribute changes later. # ---------------------------------------------------------------------------- def cb (key, val, obj) : # the callback gets information about what attribute was changed # on what object: print "called: %s - %s - %s" % (key, str(val), type (obj)) # returning True will keep the callback registered for further # attribute changes. return True # ---------------------------------------------------------------------------- # create a class instance and add a 'cherry' attribute/value on # creation. trans = Transliterator (cherry='Kersche') # use the property interface to mess with the pre-defined # 'apple' attribute print "\n -- apple" print trans.apple trans.apple = 'Abbel' print trans.apple # add our callback to the apple attribute, and trigger some changes. # Note that the callback is also triggered when the attribute's # value changes w/o user control, e.g. by some internal state # changes. trans.add_callback ('apple', cb) trans.apple = 'Apfel' # Setting an attribute final is actually an internal method, used by # the implementation to signal that no further changes on that # attribute are expected. We use that here for demonstrating the # concept though. Callback is invoked on set_final(). trans._attributes_set_final ('apple') trans.apple = 'Abbel' print trans.apple # mess around with the 'plum' attribute, which was marked as # ReadOnly on registration time. print "\n -- plum" print trans.plum # trans.plum = 'Pflaume' # raises readonly exception # trans['plum'] = 'Pflaume' # raises readonly exception print trans.plum # check if the 'cherry' attribute exists, which got created on # instantiation time. print "\n -- cherry" print trans.cherry # as we have 'extensible' set, we can add a attribute on the fly, # via either the property interface, or via the GFD.90 API of # course. print "\n -- peach" print trans.peach trans.peach = 'Birne' print trans.peach
This example will result in:
-- apple Appel Appel Abbel called: apple - Abbel Appel - <class '__main__.Transliterator'> called: apple - Apfel - <class '__main__.Transliterator'> called: apple - Apfel - <class '__main__.Transliterator'> Apfel -- plum Pruim Pruim -- cherry Kersche -- peach Berne Birne
This method sets the value of the specified attribute. If that attribute does not exist, DoesNotExist is raised – unless the attribute set is marked ‘extensible’ or ‘private’. In that case, the attribute is created and set on the fly (defaulting to mode=writeable, flavor=Scalar, type=ANY, default=None). A value of ‘None’ may reset the attribute to its default value, if such one exists (see documentation).
Note that this method is performing a number of checks and conversions, to match the value type to the attribute properties (type, mode, flavor). Those conversions are not guaranteed to yield the expected result – for example, the conversion from ‘scalar’ to ‘vector’ is, for complex types, ambiguous at best, and somewhat stupid. The consumer of the API SHOULD ensure correct attribute values. The conversions are intended to support the most trivial and simple use cases (int to string etc). Failed conversions will result in an BadParameter exception.
Attempts to set a ‘final’ attribute are silently ignored. Attempts to set a ‘readonly’ attribute will result in an IncorrectState exception being raised.
Note that set_attribute() will trigger callbacks, if a new value (different from the old value) is given.
This method returns the value of the specified attribute. If that attribute does not exist, an DoesNotExist is raised. It is not an error to query an existing, but unset attribute though – that will result in ‘None’ to be returned (or the default value, if available).
As python can handle scalar and vector types transparently, this method is in fact not very useful. For that reason, it maps internally to the set_attribute method.
As python can handle scalar and vector types transparently, this method is in fact not very useful. For that reason, it maps internally to the get_attribute method.
Removing an attribute is actually different from unsetting it, or from setting it to ‘None’. On remove, all traces of the attribute are purged, and the key will not be listed on
List all attributes which have been explicitly set.
Similar to list(), but also grep for a given attribute pattern. That pattern is of the form ‘key=val’, where both ‘key’ and ‘val’ can contain POSIX shell wildcards. For non-string typed attributes, the pattern is applied to a string serialization of the typed value, if that exists.
This method will check if the given key is known and was set explicitly. The call will also return ‘True’ if the value for that key is ‘None’.
This method will check if the given key is readonly, i.e. cannot be ‘set’. The call will also return ‘True’ if the attribute is final
This method will check if the given key is writeable - i.e. not readonly.
This method will check if the given key can be removed.
This method will check if the given attribute has a vector value type.
For any attribute change, the API will check if any callbacks are registered for that attribute. If so, those callbacks will be called in order of registration. This registration function will return an id (cookie) identifying the callback – that id can be used to remove the callback.
A callback is any callable python construct, and MUST accept three arguments:
- STRING key: the name of the attribute which changed - ANY val: the new value of the attribute - ANY obj: the object on which this attribute interface was called
The ‘obj’ can be any python object type, but is guaranteed to expose this attribute interface.
The callback SHOULD return ‘True’ or ‘False’ – on ‘True’, the callback will remain registered, and will thus be called again on the next attribute change. On returning ‘False’, the callback will be unregistered, and will thus not be called again. Returning nothing is interpreted as ‘False’, other return values lead to undefined behavior.
Note that callbacks will not be called on ‘Final’ attributes (they will be called once as that attribute enters finality).
This method allows to unregister a previously registered callback, by providing its id. It is not an error to remove a non-existing cb, but a valid ID MUST be provided – otherwise, a BadParameter is raised.
If no ID is provided (id == None), all callbacks are removed for this attribute.
set attributes from dict
return a dict representation of all set attributes