Skip to main content

recurrence

A zero-dependency, modern Python replacement for schedule — with async support, thread safety, monthly scheduling, a pluggable Clock protocol, structured error handling, and concurrent job execution.

Installation

pip install recurrence

Quickstart

import recurrence

def greet():
    print("Hello!")

# Run every 10 seconds
recurrence.every(10).seconds.do(greet)

# Run every Monday at 9am
recurrence.every().monday.at("09:00").do(greet)

# Run every 2 hours
recurrence.every(2).hours.do(greet)

# Main loop
import time
while True:
    recurrence.run_pending()
    time.sleep(1)

Key Features

Async Support

Schedule coroutines directly — no boilerplate needed:

import asyncio
import recurrence

async def fetch_data():
    await asyncio.sleep(0)
    print("fetched")

recurrence.every(30).seconds.do(fetch_data)

# In an async context, use run_pending_async:
async def main():
    while True:
        await recurrence.default_scheduler.run_pending_async()
        await asyncio.sleep(1)

asyncio.run(main())

Thread-Safe

Scheduler uses an internal threading.RLock so jobs can be added or cancelled from any thread without data races.

Monthly Scheduling

# Run on the 1st of every month at midnight (approximate — uses calendar arithmetic)
recurrence.every().month.at("00:00").do(my_monthly_report)

Clock Protocol

Inject a custom clock for deterministic testing:

import datetime
from recurrence import Scheduler

class FakeClock:
    def __init__(self, dt):
        self._dt = dt
    def now(self, tz=None):
        return self._dt

clock = FakeClock(datetime.datetime(2024, 6, 1, 12, 0, 0))
scheduler = Scheduler(clock=clock)
scheduler.every(10).seconds.do(lambda: print("ran"))

Error Handling

Provide a per-scheduler or per-job error handler instead of crashing the loop:

def handle_error(job, exc):
    print(f"Job {job} failed: {exc}")

scheduler = recurrence.Scheduler(on_error=handle_error)
scheduler.every(5).seconds.do(risky_function)

# Or per-job:
job = recurrence.Job(on_error=handle_error)
scheduler.every(5).seconds.do(risky_function)

Concurrent Execution

Run jobs in a thread pool so slow jobs don't block each other:

scheduler = recurrence.Scheduler(max_workers=4)
scheduler.every(1).seconds.do(slow_job)
scheduler.every(1).seconds.do(another_slow_job)
scheduler.run_pending()  # both run concurrently

Migration from schedule

One-line import change:

# Before
import schedule

# After
import recurrence as schedule

Everything else stays the same. recurrence is a strict superset of the schedule API.

API Reference

Module-level functions (default scheduler)

Function Description
every(interval=1) Create a new job on the default scheduler
run_pending() Run all jobs that are due
run_all(delay_seconds=0) Run all jobs immediately
get_jobs(tag=None) Return all jobs, optionally filtered by tag
clear(tag=None) Remove all jobs, or only those with the given tag
cancel_job(job) Remove a specific job
next_run() Datetime of the next scheduled run
idle_seconds() Seconds until the next job runs
repeat(job, *args, **kwargs) Decorator to schedule a function

Job fluent API

every(10).seconds.do(fn)
every(5).minutes.do(fn)
every(2).hours.at(":30").do(fn)
every().day.at("10:30").do(fn)
every().monday.at("09:00").do(fn)
every().month.at("00:00").do(fn)

# Random interval
every(5).to(10).seconds.do(fn)

# Deadline
every(1).hours.until("18:00").do(fn)

# Tags
every(10).seconds.do(fn).tag("my-tag")

# Cancel from within the job
def my_job():
    return recurrence.CancelJob  # removes itself after running

Scheduler class

s = recurrence.Scheduler(
    timezone="America/New_York",  # str, zoneinfo.ZoneInfo, or pytz tz
    clock=my_clock,               # any object with .now(tz) -> datetime
    on_error=my_handler,          # callable(job, exc)
    max_workers=4,                # enable concurrent execution
)
s.every(10).seconds.do(fn)
s.run_pending()
await s.run_pending_async()
s.shutdown()  # clean up thread pool

License

MIT

Metadata

Release files for recurrence 0.1.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 recurrence 0.1.0
File Size Uploaded
recurrence-0.1.0.tar.gz 21.6 kB Details

Built distribution (wheel)

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

Total release size: 33.6 kB

Release files / recurrence-0.1.0.tar.gz

Download URL recurrence-0.1.0.tar.gz
Size 21.6 kB
Tags Source
SHA-256 checksum
How to use checksums
6e2fa7b9817aa0fb0de0082af3dbdecc373208fa6dc89d9824b8030dfb0694a4
BLAKE2b-256 checksum
How to use checksums
2468cfb0101bf1052dffaa985e553d4916f938d768edcb8dcda0de7815a0e28f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Mar 13, 2026.

Transparency log

Release files / recurrence-0.1.0-py3-none-any.whl

Download URL recurrence-0.1.0-py3-none-any.whl
Size 11.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
695f90104227b29977d1feef5bb381f852829ae666c67ddf3d419a0ae1c558bf
BLAKE2b-256 checksum
How to use checksums
b0fb9cb1cd651f8ba7610bc40cb1deec51bcf6acbf9ccde1924bac2f023ac8ff
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Mar 13, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.0 This release

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