Introduction to Mode — mode-ng 0.4.0 documentation

# Introduction to Mode

Version:

  0.3.0

Web:

  http://mode-ng.readthedocs.org/

Download:

  http://pypi.org/project/mode-ng

Source:

  http://github.com/lqhuang/mode-ng

Keywords:

  async, service, framework, actors, bootsteps, graph

## What is Mode?

Mode is a very minimal Python library built-on top of AsyncIO that makes it much easier to use.

In Mode your program is built out of services that you can start, stop, restart and supervise.

A service is just a class:

    class PageViewCache(Service):
        redis: Redis = None

        async def on_start(self) -> None:
            self.redis = connect_to_redis()

        async def update(self, url: str, n: int = 1) -> int:
            return await self.redis.incr(url, n)

        async def get(self, url: str) -> int:
            return await self.redis.get(url)

Services are started, stopped and restarted and have callbacks for those actions.

It can start another service:

    class App(Service):
        page_view_cache: PageViewCache = None

        async def on_start(self) -> None:
            await self.add_runtime_dependency(self.page_view_cache)

        @cached_property
        def page_view_cache(self) -> PageViewCache:
            return PageViewCache()

It can include background tasks:

    class PageViewCache(Service):

        @Service.timer(1.0)
        async def _update_cache(self) -> None:
            self.data = await cache.get('key')

Services that depends on other services actually form a graph that you can visualize.

Worker

  Mode optionally provides a worker that you can use to start the program, with support for logging, blocking detection, remote debugging and more.

  To start a worker add this to your program:

      if __name__ == '__main__':
          from mode import Worker
          Worker(Service(), loglevel="info").execute_from_commandline()

  Then execute your program to start the worker:

      $ python examples/tutorial.py
      [2018-03-27 15:47:12,159: INFO]: [^Worker]: Starting...
      [2018-03-27 15:47:12,160: INFO]: [^-AppService]: Starting...
      [2018-03-27 15:47:12,160: INFO]: [^--Websockets]: Starting...
      STARTING WEBSOCKET SERVER
      [2018-03-27 15:47:12,161: INFO]: [^--UserCache]: Starting...
      [2018-03-27 15:47:12,161: INFO]: [^--Webserver]: Starting...
      [2018-03-27 15:47:12,164: INFO]: [^--Webserver]: Serving on port 8000
      REMOVING EXPIRED USERS
      REMOVING EXPIRED USERS

  To stop it hit ``Control`-`c``:

      [2018-03-27 15:55:08,084: INFO]: [^Worker]: Stopping on signal received...
      [2018-03-27 15:55:08,084: INFO]: [^Worker]: Stopping...
      [2018-03-27 15:55:08,084: INFO]: [^-AppService]: Stopping...
      [2018-03-27 15:55:08,084: INFO]: [^--UserCache]: Stopping...
      REMOVING EXPIRED USERS
      [2018-03-27 15:55:08,085: INFO]: [^Worker]: Gathering service tasks...
      [2018-03-27 15:55:08,085: INFO]: [^--UserCache]: -Stopped!
      [2018-03-27 15:55:08,085: INFO]: [^--Webserver]: Stopping...
      [2018-03-27 15:55:08,085: INFO]: [^Worker]: Gathering all futures...
      [2018-03-27 15:55:08,085: INFO]: [^--Webserver]: Closing server
      [2018-03-27 15:55:08,086: INFO]: [^--Webserver]: Waiting for server to close handle
      [2018-03-27 15:55:08,086: INFO]: [^--Webserver]: Shutting down web application
      [2018-03-27 15:55:08,086: INFO]: [^--Webserver]: Waiting for handler to shut down
      [2018-03-27 15:55:08,086: INFO]: [^--Webserver]: Cleanup
      [2018-03-27 15:55:08,086: INFO]: [^--Webserver]: -Stopped!
      [2018-03-27 15:55:08,086: INFO]: [^--Websockets]: Stopping...
      [2018-03-27 15:55:08,086: INFO]: [^--Websockets]: -Stopped!
      [2018-03-27 15:55:08,087: INFO]: [^-AppService]: -Stopped!
      [2018-03-27 15:55:08,087: INFO]: [^Worker]: -Stopped!

Beacons

  The `beacon` object that we pass to services keeps track of the services in a graph.

  They are not stricly required, but can be used to visualize a running system, for example we can render it as a pretty graph.

  This requires you to have the `pydot` library and GraphViz installed:

      $ pip install pydot

  Let’s change the app service class to dump the graph to an image at startup:

      class AppService(Service):

          async def on_start(self) -> None:
              print('APP STARTING')
              import pydot
              import io
              o = io.StringIO()
              beacon = self.app.beacon.root or self.app.beacon
              beacon.as_graph().to_dot(o)
              graph, = pydot.graph_from_dot_data(o.getvalue())
              print('WRITING GRAPH TO image.png')
              with open('image.png', 'wb') as fh:
                  fh.write(graph.create_png())

## Creating a Service

To define a service, simply subclass and fill in the methods to do stuff as the service is started/stopped etc.:

    class MyService(Service):

        async def on_start(self) -> None:
            print('Im starting now')

        async def on_started(self) -> None:
            print('Im ready')

        async def on_stop(self) -> None:
            print('Im stopping now')

To start the service, call `await service.start()`:

    await service.start()

Or you can use `mode.Worker` (or a subclass of this) to start your services-based asyncio program from the console:

    if __name__ == '__main__':
        import mode
        worker = mode.Worker(
            MyService(),
            loglevel='INFO',
            logfile=None,
            daemon=False,
        )
        worker.execute_from_commandline()

## It’s a Graph!

Services can start other services, coroutines, and background tasks.

1. Starting other services using `add_depenency`:

       class MyService(Service):

           def __post_init__(self) -> None:
              self.add_dependency(OtherService(loop=self.loop))

2. Start a list of services using `on_init_dependencies`:

       class MyService(Service):

           def on_init_dependencies(self) -> None:
               return [
                   ServiceA(loop=self.loop),
                   ServiceB(loop=self.loop),
                   ServiceC(loop=self.loop),
               ]

3. Start a future/coroutine (that will be waited on to complete on stop):

       class MyService(Service):

           async def on_start(self) -> None:
               self.add_future(self.my_coro())

           async def my_coro(self) -> None:
               print('Executing coroutine')

4. Start a background task:

       class MyService(Service):

           @Service.task
           async def _my_coro(self) -> None:
               print('Executing coroutine')

5. Start a background task that keeps running:

       class MyService(Service):

           @Service.task
           async def _my_coro(self) -> None:
               while not self.should_stop:
                   # NOTE: self.sleep will wait for one second, or
                   #       until service stopped/crashed.
                   await self.sleep(1.0)
                   print('Background thread waking up')

## What do I need?

Mode requires Python 3.6.2 or later.

There’s currently no plan to port Mode to earlier Python versions, please get in touch if this is something that you want to work on.