Skip to main content

Downloads License Python Versions Build status

schedex

Distributed persistent extendable job scheduling library

Installation

pip3 install schedex

Optional dependencies:

  • cron: crontab based schedules
  • sqlalchemy: sqlalchemy backed scheduler
  • psycopg: psycopg backed scheduler and event broker (through LISTEN/NOTIFY mechanism)
  • asyncmy: mysql(asyncmy) backed scheduler
  • pymongo: mongodb(pymongo) backed scheduler
  • opentelemetry: telemetry compatible with opentelemetry standard
  • dishka: dependency injection with dishka library

Overview

Schedex is a modern strictly-typed distributed persistent backend agnostic job scheduling library.

Features

  • Database agnostic nature allows to use it with any relation database (PostgreSQL, MySql, MsSql, MariaDB, Percona etc.) or NoSql one (MongoDB, Redis, etc.) which let you pick up the best solution suitable for your requirements.

  • The library is aimed to real-world projects so that it supports essential features required in production like transactional updates - may be necessary if your application logic requires scheduling multiple jobs or cancel one job and schedule another or modify your domain data and schedule a job atomically.

  • For data serialization schedex uses msgspec library which make it serialization format agnostic (JSON, MsgPack etc.)

  • Strongly typed codebase helps typecheckers like mypy or pyright to validate your code for type correctness and fix more bugs on development stage.

  • The library supports locking mechanism customization so that you can choose the one that is more suitable in your environment. It supports select-for-update locks (with relational databases), leasing locks; but you can implement your own one if it is necessary.

Why yet another scheduler?

There are some libraries that occupy the same niche. But they have some disadvantages which motivated to write schedex instead.

procrastinate

Mature and well tested library with great documentation and support. But it is tied to PostgreSQL which doesn't allow to use it with different databases. No transaction support make it useless when the business logic requires strong consistency.

apscheduler

Mature and well tested database agnostic library, but has some disadvantages: it is not strictly typed which leads to many bugs in runtime; only one locking mechanism supported (leasing locks), transactional modifications are not supported which makes it problematic to use the library in strong consistency required applications.

Quickstart

import datetime as dt
from typing import Any, MutableMapping

import psycopg_pool as ppg_pool
import sqlalchemy.ext.asyncio as aiosa

import schedex as sx
import schedex.contrib.psycopg as sxpg
import schedex.contrib.sqlalchemy as sxsa

AppState = MutableMapping[str, Any]
TaskLock = sxsa.SqlAlchemySfuTaskLock
Context = sx.Context[AppState, TaskLock]

tasks = sx.TaskRegistry[AppState, TaskLock]()


@tasks.task(name="print")
async def print_message(context: Context, message: str, /, *, end: str = "\n") -> None:
    print(message, end=end)


async def start_scheduler() -> None:
    db_url = "postgresql+psycopg://user:password@localhost:5432/myapp"
    db_engine = aiosa.create_async_engine(db_url)

    async with db_engine.begin() as conn:
        await conn.run_sync(sxsa.BaseModel.metadata.create_all)

    async with ppg_pool.AsyncConnectionPool(db_url) as pg_pool:
        event_sender = sxpg.PsycopgEventSender(pg_pool)

        async with sxsa.SqlAlchemyTransactionalScheduler(
            aiosa.async_sessionmaker(db_engine),
            schedule_type=sx.PeriodicSchedule,
            event_sender=event_sender,
            graceful_timeout=0.1,
        ) as scheduler:
            await scheduler.add_job(
                sx.Job(
                    sx.PeriodicSchedule.after(dt.timedelta(seconds=0)),
                    print_message.with_params("let's get started"),
                )
            )

            async with scheduler.transactional() as scheduler_tx:
                await scheduler_tx.add_job(
                    sx.Job(
                        sx.PeriodicSchedule.periodically(
                            start_at=dt.datetime.now(dt.timezone.utc) + dt.timedelta(seconds=0),
                            delay=dt.timedelta(seconds=2),
                        ).with_max_count(5),
                        print_message.with_params("hello", end=" "),
                    )
                )
                await scheduler_tx.add_job(
                    sx.Job(
                        sx.PeriodicSchedule.periodically(
                            start_at=dt.datetime.now(dt.timezone.utc) + dt.timedelta(seconds=1),
                            delay=dt.timedelta(seconds=2),
                        ).with_max_count(5),
                        print_message.with_params("world!!!"),
                    )
                )


async def start_executor() -> None:
    db_url = "postgresql+psycopg://user:password@localhost:5432/myapp"
    db_engine = aiosa.create_async_engine(db_url)
    async with db_engine.begin() as conn:
        await conn.run_sync(sxsa.BaseModel.metadata.create_all)

    app_state: AppState = {}

    async with ppg_pool.AsyncConnectionPool(db_url) as pg_pool:
        executor = sx.WorkerPoolExecutor(
            max_workers=3,
            state=app_state,
            polling_interval=dt.timedelta(seconds=15),
            schedule_type=sx.PeriodicSchedule,
            task_registry=tasks,
            lock_manager=sxsa.SqlAlchemySfuLockManager(db_engine, sxpg.PsycopgEventSender(pg_pool)),
            event_receiver=sxpg.PsycopgEventReceiver(pg_pool),
        )

        await executor.run()

Metadata

Release files for schedex 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for schedex 0.2.0
File Size Uploaded
schedex-0.2.0.tar.gz 35.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for schedex 0.2.0
File Interpreter ABI Platform
schedex-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 84.6 kB

Release files / schedex-0.2.0.tar.gz

Download URL schedex-0.2.0.tar.gz
Size 35.4 kB
Tags Source
SHA-256 checksum
How to use checksums
033ab1544f93c8137065a190bc0960e034e58e6a9391f892ac08a7a7f77efda2
BLAKE2b-256 checksum
How to use checksums
3f1a7750d6b5d787c08306d34d910f28be2a6560e23273b3dc02845cc9949f71
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via poetry/2.5.1 CPython/3.14.7 Linux/6.17.0-1022-azure

Release files / schedex-0.2.0-py3-none-any.whl

Download URL schedex-0.2.0-py3-none-any.whl
Size 49.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
751bd341e692903f6ff22abf520eb5c4b7669ffdb9f3c7f487bb13e627862c2f
BLAKE2b-256 checksum
How to use checksums
ee9ca98b45da57f10ecefd073053568d8d3f50d43a0d9e7b241cb20db5c069f8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via poetry/2.5.1 CPython/3.14.7 Linux/6.17.0-1022-azure

Release history Release notifications | RSS feed

0.2.1

2 release files

This release

0.2.0 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page