Skip to main content

A periodic task scheduler for asyncio processes.

Project description

atevery

A periodic task scheduler for asyncio processes.

Installation options

pip install atevery
poetry add atevery

Usage

Register entrypoint functions to be run periodically by using the @every decorator:

from datetime import timedelta, datetime

from atevery import every

@every(timedelta(seconds=2), 'fast')
def print_time(name):
    print(name, datetime.now().strftime("%Y-%m-%d %H:%M:%S"))

Then start the scheduler in the main entrypoint:

import asyncio

from atevery import start_background_tasks, stop_background_tasks

if __name__ == '__main__':
    loop = asyncio.new_event_loop()
    try:
        loop.run_until_complete(start_background_tasks())
        loop.run_until_complete(...) # or loop.run_forever()
    finally:
        loop.run_until_complete(stop_background_tasks())

The registered functions are ran accordingly to their specified interval. Arguments and keyword arguments passed to the @every decorator are forwarded to the target function.

The target function can also be a coroutine/async function, like:

from datetime import timedelta, datetime

from atevery import every

@every(timedelta(seconds=2), 'fast')
async def print_time(name):
    print(name, datetime.now().strftime("%Y-%m-%d %H:%M:%S"))

Stopping and resuming tasks

Tasks can be stopped and restarted at any time by using the stop_background_tasks and start_background_tasks functions. While stopping, tasks that are still running or pending to run are cancelled, and by default waited for. If by any reason you don't want to cancel or wait for them, specify the wait_for=False or wait_for=True, timeout=timedelta(...) arguments to the stop_background_tasks function.

Concurrency

You can expect only a single instance of the same task to be running at the same time. In other words, the tasks are only started once their last execution was finished.

import asyncio
import random
from datetime import datetime, timedelta

from atevery import every, start_background_tasks, stop_background_tasks


@every(timedelta(seconds=2), 'slow')
async def print_time(name):
    print(name, datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
    await asyncio.sleep(4)


async def run():
    while True:
        await asyncio.sleep(random.random())


if __name__ == '__main__':
    loop = asyncio.new_event_loop()
    try:
        loop.run_until_complete(start_background_tasks())
        loop.run_until_complete(run())
    finally:
        loop.run_until_complete(stop_background_tasks())
        loop.stop()

Outputs something similar to:

slow 2024-03-03 14:24:26
slow 2024-03-03 14:24:30
slow 2024-03-03 14:24:34
slow 2024-03-03 14:24:38

Commitment

The scheduler mechanism makes its best to keep committed to the requested interval, but some delay might happen, specially under heavier loads. It comes from the fact the scheduler competes with the tasks and all other processes for the CPU time.

In order to reduce the event loop and CPU pressure, the scheduler has a minium resolution of 50ms. That means that on every iteration, the scheduler sleeps for 50ms. Attempting to schedule a task for intervals smaller than 50ms result in ValueError being raised.

Contributing

Feel free to create issues or merge requests with any improvement or fix you might find useful.

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

atevery-0.1.1.tar.gz (3.7 kB view hashes)

Uploaded Source

Built Distribution

atevery-0.1.1-py3-none-any.whl (4.1 kB view hashes)

Uploaded Python 3

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