My favorites | Sign in
Project Home
Search
for
Tutorial  
circuits Tutorial
Featured
Updated Jan 13, 2010 by prolo...@shortcircuit.net.au

Tutorial and Introduction to circuits

Overview

This document will serve as a tutorial and introduction to the circuits library. Basic concepts will be described and code samples shown to illustrate the kinds of things you can do with circuits. For the impatient read [wiki:docs/CircuitsNutShell circuits in a nut shell].

Event Driven Programming

circuits is an Event Framework, and implements a lot of Event Driven Programming features. What is Event Driven Programming ? It's a technique of programming whereby events are scheduled into some kind of a queue and handled by event handlers. Some really good references and reading material can be found here:

Component Architecture

circuits also employs a strong Component Architecture in it's design. What is a Component Architecture ? In the context of circuits it's best described as a way of completely decoupling every part of the system or application. Each Component knows nothing of any of the other components in the system. This kind of architecture can be a very powerful tool if used properly.

Other articles on the subject describe the Component Architecture differently, but nonetheless, here are some good reference and reading material:

circuits - The Library

Okay, so let's go through all the basic concepts of circuits and what it has to offer. circuits consists of a core set of BuildingBlocks which are used to build up more complex components. They are:

Manager

A Manager is responsible for managing the events of components that are registered to it. See ComponentRegistrations for more details on how this works. A Manager has the following public interface:

Not only is a Manager capable of managing events, but it is also capable of being a self-contained running unit. This is called RunnableComponents in circuits. A Component/Manager is capable of being started in one of three ways:

  • [wiki:docs/RunnableComponents#Thread_Mode Thread Mode]
  • [wiki:docs/RunnableComponents#Process_Mode Process Mode]
  • [wiki:docs/RunnableComponents#Main Main]

Example(s):

from circuits import handler, Event, Component, Manager

# Create a new Manager
m = Manager()

# Start the Manager in ThreadMode
m.start()

# To Start the Manager in ProcessMode
#m.start(process=True)

# Push an Event to a Channel called "foo"
m.push(Event(), "foo")

# Send an Event to a Channel called "bar"
x = m.send(Event(), "bar")
print x # x is the result returned from "bar"'s Event Handler

# Stop the Manager
m.stop()

# Push a thousand events to a Channel called "foobar"
for i in xrange(1000):
    m.push(Event(), "foobar")

# Flush all evvents
m.flush()

# Run the Manager
m.run()

BaseComponent

This is the next building block used in circuits. The Component is comprised of the BaseComponent. The BaseComponent has all of the features and capabilities of the Manager, however a BaseComponent (''and all Component(s)'') can be Registered and Unregistered to other Manager(s) or Component(s).

'''NB:''' A Manager cannot be registered to anything.

The BaseComponent has the following two public interfaces:

Example(s): ''No examples for BaseComponent''.

'''NB:''' BaseComponent should not be used normally. Use Component.

Component

This is the last and final building block of circuits and all components, The Component. This is built on top of the BaseComponent (which is built on top of the Manager). It has no new public interfaces, however, it does one thing for you:

  • Automatically creates Event Handlers.

Any method defined in a Component that:

  • does '''NOT''' start with a single
  • or is '''NOT''' already an Event Handler
is turned into an Event Handler with the following properties:
  • channel: The name of the method
  • filter: False

Example(s):

from circuits import handler, Event, Component, Manager

# Create a new Component
class Foo(Component):

   def a(self):
      print "Foo's Event Handler for 'a'"

   def b(self):
      return "Foo's Event Handler for 'b'"

# Create a new Manager
m = Manager()

# Start the Manager in ThreadMode
m.start()

# To Start the Manager in ProcessMode
#m.start(process=True)

# Create a new instance of the Component Foo
foo = Foo()

# Register foo with the Manager
m += foo # Equivalent to: foo.register(m)

# Push an Event to a Channel called "a"
m.push(Event(), "a")

# Send an Event to a Channel called "b"
x = m.send(Event(), "b")
print x # x contains: "Foo's Event Handler for 'b'"

# Unregister foo
foo.unregister() # Or: m -= foo

# Stop the Manager
m.stop()

Event

Event objects, or events are simply a container to hold information about the event that occurred. Event objects can hold any arbitrary information and accept a list of arguments and keyword arguments. It is usually recommended though to subclass the Event object to create your own unique Event objects.

For example:

class Foo(Event):
    """Foo Event

    This event occurs when the system wants to send a
    message to another part of the system.

    @param message: The message
    @type  message: str
    """

    def __init__(self, message):
        super(Foo, self).__init__(message)

Two important things to note here are:

  • The Event object is documented (''using epydoc'').
  • The data that can be passed is restricted.

All Event objects have two instance attributes and one class attribute. Event objects can also be compared with other Event objects for equality and each Event object implements the {{{getitem}} for accessing both arguments and keyword arguments (''data'') of the Event object.

Class Attributes

The Event object has one class attribute: channel

This is used to define the '''channel''' this Event is bound for in a system. An example will demonstrate this:

>>> class System(Component):
...     def foo(self):
...             print "I am foo"
... 
>>> class Foo(Event):
...     channel = "foo"
... 
>>> system = System()
>>> system.start()
>>> system.push(Foo())
>>> I am foo

'''Note:''' If the Event object doesn't contain a channel class attribute, it defaults to None. Also note, that if an event is pushed or sent with the Manager.push or Manager.send calls without a channel, then the channel name is taken from the Event object name lower-cased.

Instance Attributes

Event objects contain two instance attributes (''one really''), a channel attribute and a name property.

The name property is a read-only property that identifies the name of the Event object (''taken from it's class name'').

The channel attribute is dynamically set as an Event is sent to a destination channel.

Event Equality

The equality of event event can be verified by using the == operator against two Event objects.

For example:

>>> class Foo(Event): pass
... 
>>> class Bar(Event): pass
... 
>>> foo = Foo()
>>> bar = Bar()
>>> foo == bar
False
>>> foo2 = Foo()
>>> foo == foo2
True
>>> foo3 = Foo(1,2,3)
>>> foo == foo3
False

It's important to note that two events are only considered equal if and only if:

  • They are the same type
  • They have the same channel
  • They have the same data

Feedback Channels

[[NoteBox(note, This is a new feature for the 1.3 (dev) branch)]] An Event Object can define channels to be used as "feedback" during the life-cycle of an event. These are:

  • ('''success''') The successful execution of an Event Handler for an Event.
  • ('''failure''') The failure of an Event Handler for a Event.
  • ('''before''') Before an Event Handler is executed for an Event.
  • ('''filter''') After an Event has been filtered.
  • ('''start''') Before an Event has started.
  • ('''end''') After an Event has finished.

These attributes are a tuple containing the the target and channel to be defined for the type of "feedback". The format is:

(channel, target)

Event Handlers

An Event Handler is basically a Python callable (''function or method'') that handles a specific event or set of events.

In circuits, there are two types of Event Handlers:

  • Listeners
  • Filters

Event Handlers can either be defined explicitly or implicitly. The Component building block automatically implicitly creates Event Handlers based on the Python methods it finds in that Component, with the exception os '' prefixed methods or methods already defined as Event Handlers with the @handler decorator.

Event Handlers otherwise are created with the @handler decorator and this gives more flexibility and control over how the Event Handler is created. Whereas the Component creates Event Handlers that are Listeners that listen on a channel of the same name as the method, the @handler decorator can do much more:

  • Create Event Handlers that listen to more than one channel.
  • Create Filters
  • Create prioritized Event Handlers
  • Override inherited Event Handlers.
  • Set a different target to be listening to.

Some clarification:

  • A '''Listener''' is an Event Handler that listens on \
  • a given channel or set of channels.
  • A '''Filter''' is an Event Handler that also listens on \
  • a given channel or set of channels, but, can also '''filter''' the event from other Event Handlers by returning a non-None and True value (''one that evaluates to True'').

Examples:

>>> class System(Component):
...     def foo(self):
...             print "I am foo"
...     @handler("bar")
...     def bar(self):  
...             print "I am bar"
...     @handler()
...     def all(self):
...             print "I am all"
...     @handler("foo", filter=True)
...     def filter(self, stop=False):
...             if stop:
...                     print "I am a filter"
... 
>>> system = System()
>>> system.start()
>>> system.push(Event(), "foo")
>>> I am all
I am foo
>>> system.push(Event(), "bar")
>>> I am all
I am bar
>>> system.push(Event(stop=True), "bar")
>>> system.push(Event(stop=True), "foo")
>>> I am a filter

Listeners

...

Filters

...

from circuits import handler, Event, Component, Manager, Debugger

class Foo(Event): pass
class Bar(Event): pass

class A(Component):

    def foo(self):
        print "A.foo"

    @handler("bar", filter=True)
    def bar(self, *args, **kwargs):
        print "A.bar"
        return kwargs.get("stop", False)

class B(Component):

    def foo(self):
        print "B.foo"

    @handler("bar", filter=True)
    def bar(self, *args, **kwargs):
        print "B.bar"
        return kwargs.get("stop", False)

m = Manager() + A() + B()
m.start()

Example session:

>>> m.push(Foo())
>>> A.foo
B.foo

>>> m.push(Bar())
>>> A.bar
B.bar

>>> m.push(Bar(stop=True))
>>> A.bar

Setting priory

...

Inheritance

...

Channels and Targets

Every Event Handler of a Component listens on a specific channel or set of channels. In the previous section Event Handlers can also listen to a different target.

Components can also have a channel that they are associated with. So to be clear:

  • Event Handlers listen to channel(s).
  • Component(s) have a channel.

The Manager's push and send interfaces (''which all Component(s) also have'') accept the following arguments:

  • event ('''required''').
  • channel (''optional'')
  • target (''optional'')

When '''target''' is specified, this causes that event to be destined for the given channel on the given target Component. The '''target''' can in fact be a reference to another Component.

circuits - The Architecture

The architecture of circuits is such that components can be registered and unregistered from the system event at run-time (''plugged in and plugged out'').

The best way to understand this is with a visual dirgram of an example system. Here is circuits.web (''the tool''):

This same diagram is represented textually as:

* <Manager (q: 7 c: 15 h: 15) [S]>
 * <Debugger/* (q: 0 c: 1 h: 1) [S]>
 * <Server/web (q: 0 c: 14 h: 14) [S]>
  * <TCPServer/server (q: 0 c: 10 h: 10) [S]>
   * <Select/select (q: 0 c: 0 h: 0) [S]>
   * <HTTP/http (q: 0 c: 5 h: 5) [S]>
  * <Dispatcher/web (q: 0 c: 3 h: 3) [S]>
   * <Root// (q: 0 c: 1 h: 1) [S]>

This information is generated by the circuits.tools package.

What all this means is that (''for example'') the circuits.web Server is comprised of at least 5 components with Root being a Controller (''See [wiki:/docs/CircuitsWeb CircuitsWeb]''). It's also important to mention that each of these components need not be running in the same process, or event the same machine.

This kind of architecture (''Component/Event''), allows you to (''for example''), replace the Select component with any other equivalent component that reacts to and exposes the same kind of events that the TCPServer component needs.

''This architecture is very powerful and scalable.''

Questions

If event Foo has an attribute channel = "bar" but i use self.push(Foo(), "baz") Where would it go ? > It will go to the "baz" channel.


If you have any further questions please read the [wiki:docs/FrequentlyAskedQuestions FAQ] or send an email to the circuits Google Group or simply come see us at the FreeNode IRC Network on #circuits.


See also: Other Tutorials


Sign in to add a comment
Powered by Google Project Hosting