Getting started
First, install mara with:
pip install mara
See Installation for more options and details.
A Minimal Service
A minimal Mara service looks something like this:
from mara import Service, events
service = Service()
@service.listen(events.Receive)
def receive(event):
event.client.write(event.data)
if __name__ == '__main__':
service.run()
Save it as echo.py
and run it using python
:
$ python echo.py
* Server listening on 127.0.0.1:9000
Now connect to telnet://127.0.0.1:9000
and anything you enter will be sent back to you - you have built a simple echo server.
Lets look at the code in more detail:
-
First we import
mara.Service
and create an instance of it.This
service
will be at the core of everything we do with Mara; it manages settings, the server, keeps track of clients, and handles events. - Next we listen to one of those events using the listen decorator on a function definition.
-
When an event of the type
mara.events.Receive
is triggered, this function will be called with the event object as the only argument.The event object contains all the relevant information about that event - in this case the
event.client
andevent.data
. - The
client
attribute is an instance ofmara.Client
, which provides thewrite()
method to send data. We just send back the raw data we received. - Lastly we call the run method on the service to collect any settings from the command line and start the server.
Event handlers (or sub-handlers, like command
) are the primary way you'll interact with your service. Client event handlers can also prompt the client for input by using yield
- see Event Handlers for more details.
Although we ran it here with python
, you will normally want to run it using the angel - see Using the mara angel for details.
More examples
This echo server is in the examples
directory of the Mara source, along with several more examples which will help get a feel for what Mara can do, and how you can develop with it:
- echo.py: The echo server shown above
- chat.py: A simple IRC-like chat server
- talker.py: A talker with support for commands and rooms
Overriding settings
Settings are collected in the following order, with last-defined being the setting that wins:
- Default settings in
mara.settings.defaults
-
Settings sources passed to
service.run
as non-keyword arguments- In addition to normal settings sources in strings, you can also provide a reference to an imported python module
- Settings passed to
service.run
as keyword arguments - Settings sources passed as non-keyword arguments on the command line
-
Settings in keyword arguments on command line options
- To set a string or integer value, use
--value=X
- To set a boolean True value, use
--setting
- To set a boolean False value, use
--no-setting
- To set a string or integer value, use
Settings sources can be:
module:python.module
: Name of python module to import/path/to/conf.json
: Path to JSON file
If a setting source isn't found, an error will be raised.
Once loaded, settings will be available in a mara.Settings
instance on service.settings
.
Example of coded settings passed to service.run
, to override default settings:
from mymud import settings
service.run(settings, 'settings.json', host='0.0.0.0', port='7000')
This will use the default settings, then the mymud.settings
module, then values in settings.json
, then set the host and port as specified.
Command line example to override default and coded settings:
$ python run_mymud.py module:mymud.dv dev.json --host=10.0.0.11 --port=8000
This will use the default settings and coded settings, then load them from mymud.dev
module, then dev.json
, then set the host and port as specified.
Bear in mind that there is no way to target command line settings at a specific service definition, so if your script defines multiple services, the command line settings will be used by all of them.
Logging
Rather than using python's standard logging, Mara provides its own logger for each service instance, with more customisability for what you want to log.
The built-in logging levels are:
all
: select all logging levelsangel
: when the angel starts and stops processes, passes services etcservice
: when the service starts, stops, reloads etcserver
: when the server listens to a socket, suspends etcclient
: when a client connects or disconnectsevent
: when events are triggeredstore
: when stores are useddebug
: debug notes
Your logging level will be controlled by the setting log_level
Your code can log to the default levels by calling the built-in logging methods for each level on service.log
(eg service.log.event(*lines)
), or it can specify its own logging levels by passing a different level string to write(level, *lines)
.
By default only the levels angel
and service
are logged, although the angel
level is only available when you're using the angel.
Using the mara angel
Mara provides an angel to look after your process daemon - it starts your process, restarts it if it fails, and allows your process to restart itself without losing connections or state.
To run your process through an angel, run it with mara
instead of python
:
$ mara echo.py
[7510] angel> Starting process 7511
[7510] angel> Established connection to process 7511
[7511] server> Server listening on 127.0.0.1:9000
You can pass command line settings to your service in exactly the same way, eg:
$ mara run_mymud.py module:mymud.dv dev.json --host=10.0.0.11 --port=8000
Mara starts your processes using the same python interpreter it uses, so it works from within a virtual environment.
You can now make use of service.restart()
in your code - this will serialise your sockets and stores, pass them to the angel, and start a new process which will deserialise them again, seamlessly moving clients to the new process without them knowing. For more information, see restart()
.
If your process dies unexpectedly, the angel will keep trying to restart it. If the angel dies (or is terminated), the process will terminate itself.