API

Also in this section:

Core classes

mara.Service

Central control of the Mara service.

A service is responsible for managing its server and clients. All events, timers and storage are tied to a specific service instance.

The Service class is a subclass of mara.connection.ClientContainer, but it disables the add_client and remove_client methods - clients are added automatically when they connect to the server, and removed when they disconnect. Unlike the standard ClientContainer, a service also has a global filter callback to allow you to filter it to clients who have logged in, for example.

run(*args, **kwargs)

Build settings and run the service.

Arguments:

*args

List of settings sources - see Overriding settings for options

**kwargs

Settings

listen(event_class, handler)

Bind a function to a class of event. It can be used as a direct call, or as a decorator:

# Direct call to bind defined handler function
service.listen(Event, handler)

# Decorator to bind while defining handler function
@service.listen(Event):
def handler(event):
    pass

For information about writing an event handler, see Event Handlers.

The order of binding is important - the first handler to listen to an event sees it first, and any handler can stop the event.

It will also be bound to any subclasses of the specified event, allowing you to listen to a category of events - see Event inheritance for details.

Arguments:

Event

An mara.events.Event subclass (not an instance)

handler

A reference to a handler. Omit this argument if you are using listen as a decorator. See Event Handlers for details.

trigger(event)

Trigger an event. The event will be passed to all event handlers bound to this class of event, in the order they were bound, until either a handler calls event.stop() or there are no more handlers to call.

Arguments:

event

An instance of an mara.events.Event subclass

timer(cls=PeriodTimer, **kwargs)

Function decorator to simplify defining a timer class instance and register it with the service.

For example:

@service.timer(period=60)
def every_minute(timer):
    service.write_all('Another minute has passed')

Here the decorator is shorthand for:

from mara import timers
timer = timers.PeriodTimer(period=60)
timer.fn = every_minute
service.timers.add(timer)

You can use a different timer class by passing it as the first argument, cls:

from mara.timers import PeriodTimer
@service.timer(PeriodTimer, period=60)
def every_minute(timer):
    service.write_all('Time passes')

See Timers for more details of how timers work, including how to write timer handlers, and a list of the built-in timer classes for you to use.

Arguments:

cls

The class of timer to instantiate.

**kwargs

Keyword arguments used to instantiate the timer class

write(clients, *data, newline=True)

Send the lines of data to the specified clients. See ClientContainer.write for details.

write_all(*data, filter_fn=function, exclude=list)

Send the data to all connected clients, filtered by the service's global filter.

See ClientContainer.write_all for details.

filter_all = callable

Set a filter for all write_all calls. This can be supplemented by the filter keyword argument - both can use the same callables.

The callable that you assign should expect the following arguments:

service

The service that is in the process of writing the data

clients

A list of mara.Client instances

It should then return a filtered list of clients.

If the callable is set to None, the filter will be reset and no filtering will be performed.

For example:

# Only write to every other client
service.filter_all = lambda service, clients: clients[::2]

or slightly more complex:

def room_filter(service, clients, room=None):
    if not room:
        return []
    return [c for c in clients if c in room.clients]

# We could set this as a global filter with:
#   service.filter_all = room_filter
# But this would stop us from broadcasting global events

@service.listen(mara.events.Receieve):
def chat(event):
    client.write('You say %s' % event.data)
    service.write_all(
        '%s says: %s' % (event.client.username, event.data),
        except=event.client,
        # So we'll pass it in the write_all call
        filter=room_filter,
        room=event.client.room,
    )

store(cls, name)

Retrieve the store instance of the given class and name.

See Storage for more details of how storage works.

restart()

Restarts the process while maintaining the service state and client sockets. Only available when the process is run using the angel.

When called, the current service will do the following:

  1. service.restart() is called
  2. If we don't have an angel, raise a ValueError
  3. Trigger PreRestart event
  4. Flush all client output buffers (so they can see a restart notification)
  5. Suspend the server (do no further socket processing)
  6. Serialise the service state and client sockets
  7. Pass the serialised data to the angel
  8. Wait for a response from the angel

The angel will then:

  1. Receive serialised data from the current process
  2. Start a new process

The new process will then:

  1. Connect to the angel
  2. Trigger PreStart event
  3. Request serialised data
  4. Deserialise the data into the new service
  5. Notify the angel that it has started
  6. Trigger PostStart event
  7. Trigger PostRestart event

When the angel receives notification that the new process has started, it will tell the old process that everything is ok. The old process will then terminate immediately.

mara.Settings

A container for service settings.

Additional custom settings can be stored on the Settings class, but do not start them with an underscore, and make sure they do not start with an underscore, and that they do not clash with methods on the Settings class. You should use a prefix to ensure they do not collide with any other settings; eg: myproject_mysetting=20.

load(source)

Load a settings source and override existing settings

If called from code rather than the command line you can also pass a reference to an imported module:

import myproject.settings
settings.load(myproject.settings)
# Equivalent to:
settings.load('module:myproject.settings')

mara.Client

The client object is the telnet socket manager.

write(*lines, newline=True)

Send the data to the client

Arguments:

lines

One or more lines of data to send to the client. Should not contain newline sequences.

newline=True

A boolean to determine if the newline character should be added to the end of each line. Defaults to True, can be set to False for use in prompts etc.

mara.events

See Events for details

mara.settings.defaults

These are the default settings for any Mara service. Look at this file for details of all settings; the important ones are:

host

Host IP to bind to

Default: 127.0.0.1

port

Port to bind to

Default: 9000

root_path

This is an optional root path for all non-absolute path settings. If it is set to None, the directory containing the service script will be used. Individual path settings will ignore this if they are absolute themselves.

Default: None (use script directory)

socket_raw

Raw socket mode

Mara is primarily designed to be a telnet server for talkers and MUDs, so it normally treats inbound and outbound data as telnet content - performing telnet negotiation, breaking and joining raw socket data with newlines. However, this can be disabled using this setting, so you can read and write the raw data.

If True, disable telnet negotiation, do not buffer or strip inbound data, and do not modify outbound data.

If False, assume this is a telnet connection using rn for line feeds. This will enable telnet negotiation, buffer inbound data until the newline sequence is received (which will be stripped), and use the newline sequence to suffix all lines of outbound data.

Default: False

store_path

Path to store directory. If it does not exist, it will be created.

If it is a relative path, Mara will use the root_path setting to determine the absolute path.

Default: store

mara.Logger

write(level, *lines)

Write the lines at the specified level

mara.connection.ClientContainer

Clients are grouped together in ClientContainer instances to make it easier to write to them in bulk.

The class mara.Service is a subclass of ClientContainer, so that you can easily write to and filter all clients connected to the service.

Rather than using a container directly, you should normally create a subclass which also inherits from mara.storage.Store (for containers with persistent state, eg talker or mud rooms which have flags or items) or mara.storage.SessionStore (for containers without persistent state, eg chat channels), changing the clients attribute into a list field:

class Room(storage.Store):
    service = service
    clients = storage.Field(default=[])

This will allow connected clients to remain associated with their container instances across restarts.

clients

A read-only list of clients in this container.

add_client(client)

Add the specified client to this container.

remove_client(client)

Remove the specified client from this container.

write(clients, *data, newline=True)

Send the lines of data to the specified clients.

Arguments:

clients

A mara.Client instance, or list of Client instances.

*data

One or more lines of data to send to the client. Should not contain newline sequences.

newline=True

A boolean to determine if the newline character should be added to the end of each line. Defaults to True, can be set to False for use in prompts etc.

write_all(*data, filter_fn=function, exclude=clients)

Send the data to all clients in this container.

Arguments:

*data

One or more lines of data to send to the client. Should not contain newline sequences.

The optional keyword arguments are passed to filter_clients(filter_fn=function, exclude=clients) to filter the client list.

filter_clients(filter_fn=function, exclude=clients)

Returns a filtered list of clients in this container.

Optional keyword arguments:

filter

A callable which will be used to filter the clients. It should expect the following arguments:

service

The service that is in the process of writing the data

clients

A list of mara.Client instances

It should then return a filtered list of clients.

Default: None

exclude

A mara.Client instance, or list of Client instances.

mara.styles

The styles module contains a set of classes for rendering strings with ANSI styles and other decorations; they all subclass styles.String which is instantiated with strings and other String instances, which can also be concatenated.

Most of the time rendering will be handled for you by the Client; behind the scenes each String has a render(client, state) method, where state is an instance of styles.State (or styles.StatePlain if ANSI is not supported by the client's terminal).

Classes available for you to use are:

normal

Reset all styles and colours

bold, faint, italic, underline, negative, strike

Font styles

red, green, yellow, blue, magenta, cyan, white

Colours

hr

Horizontal rule; if it has any content, the content will be centered.

Takes its style state and sequence from the settings hr_state and hr_sequence

Example usage:

client.write(
    # Horizontal rule with centered message
    styles.hr('Welcome'),

    # Multi-coloured concatenated style objects
    styles.red('Red') + ' and ' + styles.blue('blue'),

    # Multiple nested style objects
    styles.bold(
        'This', styles.cyan('is'), ' ', styles.red(
            styles.strike('dumb'), 'amazing',
        ), '.',
    ),

    # Horizontal rule without message, does not need to be instantiated
    styles.hr,
)