|
Tutorial
circuits Tutorial
Featured Tutorial and Introduction to circuitsOverviewThis 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 Programmingcircuits 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 Architecturecircuits 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 LibraryOkay, 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:
ManagerA 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:
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()BaseComponentThis 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. ComponentThis 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:
Any method defined in a Component that:
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()EventEvent 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:
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 AttributesThe 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 AttributesEvent 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 EqualityThe 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:
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:
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 HandlersAn 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:
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:
Some clarification:
a given channel or set of channels. 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 filterListeners... 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 TargetsEvery 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:
The Manager's push and send interfaces (''which all Component(s) also have'') accept the following arguments:
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 ArchitectureThe 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.'' QuestionsIf 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 |