: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:
.. code-block:: console
$ 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 :kbd:`Control-c`:
.. code-block:: console
[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:
.. code-block:: console
$ 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.
Starting other services using
add_depenency
::class MyService(Service):
def __post_init__(self) -> None: self.add_dependency(OtherService(loop=self.loop))
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), ]
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')
Start a background task::
class MyService(Service):
@Service.task async def _my_coro(self) -> None: print('Executing coroutine')
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')