score.serve

This module is responsible for starting long-running processes like a web-server, celery-workers or other services.

Quickstart

Let’s write a small service that sends spam to stdout periodically. We will first need a subclass of Worker that will perform the sending. Let’s use the very simple SimpleWorker subclass:

import time
from score.serve import SimpleWorker

class Spammer(SimpleWorker):

    def loop(self):
        while self.running:
            print('spam!')
            time.sleep(1)

We now need our module to expose a function called score_serve_workers that returns an instance of this class:

from score.init import ConfiguredModule


class ConfiguredSpamModule(ConfiguredModule):

    def __init__(self):
        import demo.spam
        super().__init__(demo.spam)

    def score_serve_workers(self):
        return [Spammer()]

The only thing left to do is to configure the serve module to start this worker. Add the following to your configuration:

[score.serve]
    modules = demo.spam

You can now start your glorious spam server with score serve.

Configuration

score.serve.init(confdict)[source]

Initializes this module acoording to our module initialization guidelines with the following configuration keys:

autoreload False
When set to true, the server will automatically reload whenever it detects a change in one of the python files, that are in use.
modules
The list of modules to serve. This need to be a list of module aliases, i.e. the same name, with which you configured the module with (“score.http” becomes “http” if not specified otherwise.)

Details

Although we have tried to render the usage of this module as simple as possible, it has very components inside. The reason for this complexity is the many constraints, that were imposed upon the architecture:

  • It must be possible to pause a worker. The worker is expected to cease handling new requests, but should also be able to continue operations immediately when instructed to do so.
  • It should be possible to track the state of every worker at all times, even when a worker is transitioning from one state to another.
  • Reloading the application when a python file changes should be as fast as possible, since this will be done very often during application development.
  • The main loop should not rely on the existence of a console. Future versions of this module will provide a maintenance port, where workers can be probed and controlled by externals applications.

Worker States

The above constraints have lead us to the following states for workers:

  • STOPPED: This is the initial as well as the final state of workers. Workers are not expected to consume any significant resources in this state.
  • PAUSED: The worker has all required resources to start perform its duty at any point. An HTTP server will have an open socket at port 80, for example, but will not accept any connections.
  • RUNNING: This is the state where the worker actually does whatever it is meant to do.

If the worker states are not tweaked manually (via @transitions annotations), the state machine looks like the following:

.       STOPPED
       /      ^
   PAUSING    |
      |     STOPPING
      V     /
      PAUSED
     /     ^
STARTING   |
    |     PAUSING
    V     /
    RUNNING

So every transition passes through an intermediate state which represents the state the worker ist transitioning to. This implies that the PAUSING state occurs twice in the diagram, as you might have noticed.

It is possible to add furhter transition capabilities to your worker using @transitions. Have a look at the function documentation for details.

Worker API

To make these state transitions as transparent as possible, the Worker API follows a simple design principle: Every state transition is represented by a function. The transition STOPPED -> PAUSED is handled by the function prepare, for example. As soon as the method is entered, the worker is assumed to be in the PAUSING state. When the function terminates, the worker is assumed to be in the PAUSED state.

The layers above the Worker will make sure, that these functions are always called in the correct order. Workers have the guarantee, that they are in the STOPPED state, when their prepare method is called, for example.

Due to the working of Service API (the next higher layer), every transition function is called inside a different thread. This is the only inconvenience imposed upon this layer. The provided SimpleWorker class implements an abstraction around this limitation, so if you don’t want to dirty your code with threading, you can just go ahead and use that class instead of the more powerful and more complex Worker base class.

Service API

Every worker is wrapped in a Service object that handles the state transitions of a worker. Service objects expose the same API as workers, but they have a slightly different meaning: When you call start on a service object, it will make sure that the worker transitions to the RUNNING state eventually, no matter what state it currently is in.

API

Configuration

score.serve.init(confdict)[source]

Initializes this module acoording to our module initialization guidelines with the following configuration keys:

autoreload False
When set to true, the server will automatically reload whenever it detects a change in one of the python files, that are in use.
modules
The list of modules to serve. This need to be a list of module aliases, i.e. the same name, with which you configured the module with (“score.http” becomes “http” if not specified otherwise.)
class score.serve.ConfiguredServeModule[source]

This module’s configuration class

autoreload

The configured autoreload value as a bool.

start()[source]

Starts all configured workers and runs until the workers stop or <CTRL-C> ist pressed. Will optionally reload the server, if it was configured to do so via autoreload.

Workers

class score.serve.Worker[source]

The implementation of a single service.

The worker will be wrapped in Service objects before being started.

prepare()[source]

Implements the transition from STOPPED to PAUSED.

start()[source]

Implements the transition from PAUSED to RUNNING.

stop()[source]

Implements the transition from PAUSED to STOPPED.

pause()[source]

Implements the transition from RUNNING to PAUSED.

cleanup(exception)[source]

Called when an exception occured. Due to the nature of threading, it is not entirely clear, in which state the worker was, when this specific exception occurred.

register_state_change_listener(callback)[source]

Registers a callable that will be invoked whenever the state of this worker changes. The callback will receive three arguments:

  • a Service wrapping this worker,
  • the old state and
  • the new (current) state.

Note, that due to the nature of threading, it is possible that the Service is already in another state than the one provided as the third argument.

unregister_state_change_listener(callback)[source]

Removes a previously registered listener.

score.serve.transitions(state1, state2=None)[source]

This annotation can add additional transitions to a class.

If your worker is capable of going from RUNNING to STOPPED, for example, you can add the additional transition to a new class method:

class MyWorker(Worker):

    @transitions(Service.State.RUNNING, Service.State.STOPPED)
    def kill():
        # ...
class score.serve.SimpleWorker[source]

A simplified worker base class, that hides all the ugly threading logic.

You can subclass this and implement a loop function that periodically checks this.running:

class Spammer(SimpleWorker):

    def loop():
        while self.running:
            print('spam!')
            time.sleep(1)
class score.serve.SocketServerWorker[source]

A specialized worker for handling socketserver objects.

You only need to implement the function _mkserver in subclasses. That function must return a socketserver.BaseServer instance. The Worker will then perform the equivalent of calling its serve_forever method.

class score.serve.AsyncioWorker[source]

A specialized worker for asyncio servers.

This base class will add a layer of abstraction to eliminate threading. Subclasses can override the functions _prepare(), _start(), _pause(), _stop() and _cleanup(). These functions will be called inside a running event loop (which can be accessed as self.loop) and can be regular functions or coroutines.

Example implementation:

class EchoServer(AsyncioWorker):

    async def _start(self):
        self.server = yield from self.loop.create_server(myserver)

    def _pause(self):
        self.server.close()
prepare()[source]

Implements the transition from STOPPED to PAUSED.

start()[source]

Implements the transition from PAUSED to RUNNING.

pause()[source]

Implements the transition from RUNNING to PAUSED.

stop()[source]

Implements the transition from PAUSED to STOPPED.

cleanup(exception)[source]

Called when an exception occured. Due to the nature of threading, it is not entirely clear, in which state the worker was, when this specific exception occurred.

Service

class score.serve.Service(name, worker)[source]

A wrapper around workers, that you can use to control your workers without worrying about threading.

State

alias of ServiceState

start()[source]

Makes sure the worker ends up in the RUNNING state eventually.

pause()[source]

Makes sure the worker ends up in the PAUSED state eventually.

prepare()[source]

An alias for pause() for ensuring compaitibility with the Worker API.

stop()[source]

Makes sure the worker ends up in the STOPPED state eventually.

register_state_change_listener(callback)[source]

Registers a callable that will be invoked whenever the state of the worker changes. The callback will receive three arguments:

  • this service,
  • the old state and
  • the new (current) state.

Note, that due to the nature of threading, it is possible that the Service is already in another state than the one provided as the third argument.

unregister_state_change_listener(callback)[source]

Removes a previously registered listener.