Skip to content
This repository has been archived by the owner on Aug 13, 2020. It is now read-only.
persquare edited this page Jun 26, 2015 · 11 revisions

Writing Actors in Python

A new actor can be developed easily by inheriting from the Actor class, which holds similarity to CAL actors, if one is familiar with them.

An actor

The following is an example of an actor. In this case, the only function is to pass any token recived on the inport to the outport, optionally printing it to standard out.

    from calvin.actor.actor import Actor, ActionResult, manage, condition

    class Identity(Actor):
        """
        forward a token unchanged
        Inputs:
          token : a token
        Outputs:
          token : the same token
        """
        @manage(['dump'])
        def init(self, dump = False):
            self.dump = dump

        def log(self, data) :
            print "%s<%s>: %s" % (self.__class__.__name__, self.id, data)

        @condition(['token'], ['token'])
        def donothing(self, input):
            if self.dump : self.log(input)
            return ActionResult(tokens_consumed=1, tokens_produced=1, production=( input, ))

        action_priority = ( donothing, )

Actor breakdown

The first lines import the necessary parts of the Actor module.

    from calvin.actor.actor import Actor, ActionResult, manage, condition

Here, Actor is the base class of all actors and should always be included. ActionResult is how an action reports its result to the runtime, including how many tokens where consumed, many where produced, and of course what those produced were.

Actor decorators

To simplify development of actors, there are three decorators that can be imported from the calvin.actor.actormodule: @manage, @condition, and @guard. Use of @manage and @condition is in fact mandatory, while use of @guard is optional.

The @manage decorator is used to inform the runtime which attributes of the actor should be automatically managed when migrating, and the @condition decorator specifies how many tokens are needed on in-ports and how much space is required on out-ports before an action can fire. The condition decorator also serves the dual purpose of specifying that the decorated function actually is an action. Finally, the optional @guard decorator, which may only appear after the @condition decorator, can inspect the data available for the action and decide wether or not to allow the action to run.

@manage

This decorator should be used with the init method of the actor to inform the system of which actor attributes (i.e. actor state) should be serialized on migration.

Usage:
    @manage()                     # Manage every instance variable known upon completion of init
    @manage(include = [])         # Manage nothing
    @manage(include = [foo, bar]) # Manage self.foo and self.bar only. Equivalent to @manage([foo, bar])
    @manage(exclude = [foo, bar]) # Manage everything except self.foo and self.bar
    @manage(exclude = [])         # Same as @manage()
    @manage(<list>)               # Same as @manage(include = <list>)

    N.B. If include and exclude are both present, exclude will be disregarded.

@condition

The @condition decorator specifies the required input data and necessary output space. Unless both input and output conditions are fulfilled the action cannot be fired.

Usage:
    @condition(action_input=[], action_output=[])

Both action_input and action_output parameters are lists of tuples: ("portname", #tokens) Optionally, the port specification can be a "portname" only instead of a tuple, meaning #tokens is 1.

@guard

The decorator guard refines the criteria for picking an action to run by stating a function with the same signature as the guarded action, returning a boolean (True if action allowed). If the specified function is unbound or is a lambda expression, you must account for 'self', e.g.

    @condition(['select', 'false'], ['data'])
    @guard(lambda self, select, data : select == 0)
    def false_action(self, select, data):
        ...
An example actor

An actor should always inherit from the Actor base class.

    class Identity(Actor):

The docstring of the actor defines the ports and their names. Note: This is required as it is the only way of defining ports. In this example there is one in-port, named token, and one out-port, also named token. The Inputs: and Outputs: headings are optional if no ports listed under heading. For aesthetical reasons the plural 's' in the headings is optional, as is the capitalization of the words. Also optionally document the port after the port string. At least a whitespace is required after the portname, but the convention is to separate port name and port documentation by " : ", i.e. space colon space. It is a convention to use lowercase letters for ports, unless there is good reason not to, e.g. a port taking a URL as input.

        """
        forward a token unchanged
        Inputs:
          token : a token
        Outputs:
          token : the same token
        """

The manage decorator, here used in its simplest form - giving a list of attributes to manage. Optionally, it could be written @manage( include = ['dump'] ). If there are a multitude of attributes, all of which should be included, an empty argument list, i.e. @manage() will ensure all of them are included. It is also possible to only specify which attributes should be excluded. See the documentation for Actor.actor.manage for further details.

        @manage(['dump'])

The init method serves the same purpose for actors as __init__ does for python classes, to initialize attributes. This is the first method called after an actor is created and has access to all actor specific methods. Any attribute which exists after init returns can be managed (see above)

        def init(self, dump = False):
            self.dump = dump

It is of course possible to define and call any number of methods in an actor.

        def log(self, data) :
            print "%s<%s>: %s" % (self.__class__.__name__, self.id, data)

An action is defined by the @condition decorator. In this case, it expects one token on the in-port token, and one available slot on the out-port, also named token. The argument list here is actually a shortened version of @condition( action_input = [ ('token', 1) ], action_output = [ ('token', 1) ] meaning the action has as input one token from in-port 'token' and will produce one token on out-port 'token'. See documentation for Actor.actor.condition.

        @condition(['token'], ['token'])

Omitted in this action is the guard decorator. Whereas condition only does a superficial examination of the ports (number of tokens available on in-ports and space left on out-ports, mainly) the guard has full access to the tokens and can examine them more thoroughly. For example, it would be possible to handle input differently based on type by including a guard

    @guard(lambda self, a : isinstance(a, str))

on an action, and it would only be applied when the token on the in-port is a string. The guard will always have the same signature as the action.

        def donothing(self, input):
            if self.dump : self.log(input)

The ActionResult returns the result of the action to the runtime. Here, one token was consumed, one was produced, and the tokens produced is given as a sequence.

            return ActionResult(tokens_consumed=1, tokens_produced=1, production=( input, ))

Finally, action_priority determines the priority of the actions. This is the order in which conditions (and guards) will be evaluated to see which actions fire. Whenever an action fires, this can cause actions of higher priority to be ready to fire, and thus the sequence will be iterated, restarting whenever an action fires, until no action has fired for a full iteration.

        action_priority = ( donothing, )
Clone this wiki locally