service manager for asyncio
Project description
Facet
Service manager for asyncio.
Reason
mode
tries to do too much job:
- Messy callbacks (
on_start
,on_started
,on_crashed
, etc.). - Inheritance restrict naming and forces
super()
calls. - Forced logging module and logging configuration.
Features
- Simple (
start
,stop
,dependencies
andadd_task
). - Configurable via inheritance (graceful shutdown timeout).
- Mixin (no
super()
required). - Requires no runner engine (
Worker
,Runner
, etc.) just plainawait
orasync with
.
License
facet
is offered under MIT license.
Requirements
- python 3.6+
Usage
import asyncio
import logging
from facet import ServiceMixin
class B(ServiceMixin):
def __init__(self):
self.value = 0
async def start(self):
self.value += 1
logging.info("b started")
async def stop(self):
self.value -= 1
logging.info("b stopped")
class A(ServiceMixin):
def __init__(self):
self.b = B()
@property
def dependencies(self):
return [self.b]
async def start(self):
logging.info("a started")
async def stop(self):
logging.info("a stopped")
logging.basicConfig(level=logging.DEBUG)
asyncio.run(A().run())
This will produce:
INFO:root:b started
INFO:root:a started
Start and stop order determined by strict rule: dependencies must be started first and stopped last. That is why B
starts before A
. Since A
may use B
in start
routine.
Hit ctrl-c
and you will see:
INFO:root:a stopped
INFO:root:b stopped
Traceback (most recent call last):
...
KeyboardInterrupt
Stop order is reversed, since A
may use B
in stop
routine. Any raised exception propagates to upper context. facet
do not trying to be too smart.
Service can be used as a context manager. Instead of
asyncio.run(A().run())
Code can look like:
async def main():
async with A() as a:
assert a.b.value == 1
await a.wait()
asyncio.run(main())
Another service feature is add_task
method:
class A(ServiceMixin):
async def task(self):
await asyncio.sleep(1)
logging.info("task done")
async def start(self):
self.add_task(self.task())
logging.info("start done")
logging.basicConfig(level=logging.DEBUG)
asyncio.run(A().run())
This will lead to background task creation and handling:
INFO:root:start done
INFO:root:task done
Any non-handled exception on background task will lead the whole service stack crashed. This is also a key feature to fall down fast and loud.
All background tasks will be cancelled and awaited on service stop.
You can manage dependencies start/stop to start sequently, parallel or mixed. Like this:
class A(ServiceMixin):
def __init__(self):
self.b = B()
self.c = C()
self.d = D()
@property
def dependencies(self):
return [
[self.b, self.c],
self.d,
]
This leads to first b
and c
starts parallel, after they successfully started d
will try to start, and then a
itself start will be called. And on stop routine a
stop called first, then d
stop, then both b
and c
stops parallel.
The rule here is first nesting level is sequential, second nesting level is parallel
API
Here is public methods you get on inheritance/mixin:
wait
async def wait(self):
Wait for service stop. Service must be started. This is useful when you use service as a context manager.
run
async def run(self):
Run service and wait until it stop.
graceful_shutdown_timeout
@property
def graceful_shutdown_timeout(self):
return 10
How much total time in seconds wait for stop routines. This property can be overriden with subclass:
class CustomServiceMixin(ServiceMixin):
@property
def graceful_shutdown_timeout(self):
return 60
dependencies
@property
def dependencies(self):
return []
Should return iterable of current service dependencies instances.
running
@property
def running(self) -> bool:
Check if service is running
add_task
def add_task(self, coro) -> asyncio.Task:
Add background task.
start
async def start(self):
pass
Start routine.
stop
async def stop(self):
pass
Stop routine.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.