Skip to main content

service manager for asyncio

Project description

Facet

Travis status for master branch Codecov coverage for master branch Pypi version Pypi downloads count

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 and add_task).
  • Configurable via inheritance (graceful shutdown timeout).
  • Mixin (no super() required).
  • Requires no runner engine (Worker, Runner, etc.) just plain await or async 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

facet-0.7.0.tar.gz (4.4 kB view details)

Uploaded Source

Built Distribution

facet-0.7.0-py3-none-any.whl (4.9 kB view details)

Uploaded Python 3

File details

Details for the file facet-0.7.0.tar.gz.

File metadata

  • Download URL: facet-0.7.0.tar.gz
  • Upload date:
  • Size: 4.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.4.1 importlib_metadata/4.0.1 pkginfo/1.7.0 requests/2.25.1 requests-toolbelt/0.9.1 tqdm/4.60.0 CPython/3.7.1

File hashes

Hashes for facet-0.7.0.tar.gz
Algorithm Hash digest
SHA256 ac724c7660d071bda0c487fa1f6de508ca9ba4f09cd436cde0fb91ddf2bf3066
MD5 b349915617c65e7096c0c5a43867015e
BLAKE2b-256 b6f11eb8788a5ae0e024d0bb7c5dee00cd7339d87ade469fe7f2a89bf4f5ec1b

See more details on using hashes here.

File details

Details for the file facet-0.7.0-py3-none-any.whl.

File metadata

  • Download URL: facet-0.7.0-py3-none-any.whl
  • Upload date:
  • Size: 4.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.4.1 importlib_metadata/4.0.1 pkginfo/1.7.0 requests/2.25.1 requests-toolbelt/0.9.1 tqdm/4.60.0 CPython/3.7.1

File hashes

Hashes for facet-0.7.0-py3-none-any.whl
Algorithm Hash digest
SHA256 93f87082e36b93f94877ea0fd3e077b928406f54f90d296cffd48e0c3419d204
MD5 7bab8fcec59865ad22c413bb33b34f09
BLAKE2b-256 53130c842ffbb1812baa0ada5a287486e479eadc0e5b4b383dbcf405d9b5cad8

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page