Events

Event Handlers

An event handler is a function, generator or mara.events.Handler, which is registered with the service using listen(event_class, handler) for certain classes of events. It is passed the instance of the event, and handlers are able to modify it for future handlers if they wish.

Multiple handlers can listen to a single event; they will be called in the order they are defined. If a handler does not want later handlers to receive the event, it can call event.stop().

Events also are bubbled up to superclass handlers - see Event inheritance for more details.

The handler can be a function or a generator. A function can return at any point; any return value is ignored.

If the handler is a generator, and the event has a client attribute, the handler can yield to capture the next line of input from the client (or in raw socket mode the next chunk of data). It can continue to yield to capture further lines. For example:

@service.listen(mara.events.Connect)
def connect(event):
    event.client.write('Welcome. Please enter your name: ', newline=False)
    username = yield
    event.client.username = username
    event.client.write('Welcome, %s' % username)
    service.write_all('%s has connected' % username, exclude=event.client)

This handler is from the chat.py example. Note that the first call to write sets newline=False; this stops Mara from adding a newline to the end of the line when it's sent to the client, so they will type their name on the same line.

mara.events.Handler

Event handler base class.

Event handlers can be stand-alone functions or generators, but you can also use the Handler class to create compound handlers which can be designed to be inherited from and overridden.

Any methods on the class with a name starting handler_ will be run in alphanumeric name order - so if order is important to you, use a number in your method name to ensure clear ordering, eg handler_10_get_name.

Each handler method can be a functions or a generator. Because calling a sub-generator in python 2.7 is a pain, this makes the Handler class the easiest way to write re-usable event handlers which rely on user input.

Each handler method is passed two arguments:

self

Reference to the handler class

event

The event which triggered this handler class. This is also available on self.event.

There are two ways to manage flow between handler methods:

  • Calls to event.stop() are respected; no further handler methods will be called once an event is stopped
  • Modify self.handlers - this is the queue of remaining handlers to run. It is a simple list of method references; add or remove items as needed. You can reset it to the full list of handlers by using get_handlers():

    def handler_infinite_loop(self):
        "This is very silly and will lock up your service"
        self.handlers = self.get_handlers()

For an example of a more complex event handler, see the ConnectHandler classes in mara.contrib.users and mara.contrib.users.password.

Event handlers are compatible with the mara.contrib.commands module; see Using event handlers as command functions for more details.

Event inheritance

It is often desirable to bind a handler to listen to a category of events; for example, when you want to extend all client events by adding a user attribute to them, as is done with mara.contrib.users.

To make this easy, Mara lets you bind a handler to an event base class. For example, a handler bound to events.Client will also be called for Receive, Connect and Disconnect events.

Behind the scenes manages this in two ways:

  • when binding a handler, the service adds finds subclasses of the specified event and binds the handler to those too
  • when a service sees a new event class (when binding or triggering) it looks at the bound handlers for its base class, and binds those to the new event class. If a class has multiple base classes, only the first one is used.

This means that the order that handlers are bound is still respected.

Writing custom events

Create a subclass of mara.events.Event and ensure it sets a docstring or __str__ for logging.

Handlers are matched by comparing classes, so you can have two classes with the same name (as long as they are in separate modules).

Event classes

mara.events.Event

Base class for event classes.

Events are containers for event data; event attributes are passed as keyword arguments to the constructor. For example:

event = mara.events.Receive(client=client_obj, data=raw_data)

Events can render to strings; this is used for logging.

Methods:

stop()

Stop the event from being passed to any more handlers

Service events

These are subclasses of the mara.events.Service event.

When the service starts running:

mara.events.PreStart

The service is about to start its server (service.server is not yet defined). Settings have been collected, a connection to the angel (if present) has been established, and the logger has been initialised.

mara.events.PostStart

The server has been initialised and is about to enter its main listen loop. If the process is restarting, the clients and stores have now been deserialised.

When the service stops:

mara.events.PreStop

The service is about to stop its server by telling it to terminate its main listen loop. This is the last opportunity to write to clients - but flush them to make sure the data gets to them.

mara.events.PostStop

The server has left its main listen loop and has closed its socket and those of its clients. Main program execution is about to resume from where it called service.run()

When the service restarts:

mara.events.PreRestart

The service is about to restart the process. It has confirmed that it is connected to the angel and can proceed; it is about to flush client sockets, suspend the server, serialise all client sockets and store data and send it to the angel, before terminating this process.

mara.events.PostRestart

The service has restarted. This is called immediately after PostStart, so everything has been deserialised now. This is a new process to the one which triggered the PreRestart.

For more information about events when restarting, see restart().

Server events

These are subclasses of the mara.events.Server event.

mara.events.ListenStart

The server is listening.

Called between the service events PreStart and PostStart, once the server has opened its socket and started listening.

mara.events.ListenStop

The server is no longer listening

Client events

These are subclasses of the mara.events.Client event.

mara.events.Connect

Client has connected (client)

Attributes:

client

Instance of mara.Client

mara.events.Receive

Client has sent data (client).

When in raw mode this will be triggered as soon as data arrives on the socket, but when raw mode is disabled (by default), incoming data will be buffered until one or more newline sequences are found; at that point a new event will be created for each complete line.

Attributes:

client

Instance of mara.Client

data

Input data. When in raw mode this will be the unmodified data exactly as it arrives, but when raw mode is disabled (by default), this is a single full line of input, with newline stripped and any telnet negotiation sequences removed.

mara.events.Disconnect

Client has disconnected (client)

Attributes:

client

Instance of mara.Client