A zero-dependency, modern Python replacement for schedule — with async, thread safety, and monthly scheduling
Project description
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
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.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file recurrence-0.1.0.tar.gz.
File metadata
- Download URL: recurrence-0.1.0.tar.gz
- Upload date:
- Size: 21.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6e2fa7b9817aa0fb0de0082af3dbdecc373208fa6dc89d9824b8030dfb0694a4
|
|
| MD5 |
277fdc5b37f1cbac55e2823528e31f6e
|
|
| BLAKE2b-256 |
2468cfb0101bf1052dffaa985e553d4916f938d768edcb8dcda0de7815a0e28f
|
Provenance
The following attestation bundles were made for recurrence-0.1.0.tar.gz:
Publisher:
publish.yml on agentine/recurrence
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
recurrence-0.1.0.tar.gz -
Subject digest:
6e2fa7b9817aa0fb0de0082af3dbdecc373208fa6dc89d9824b8030dfb0694a4 - Sigstore transparency entry: 1095177177
- Sigstore integration time:
-
Permalink:
agentine/recurrence@b10b7d47b7ed181a95266aa89e0a2ddf13e7d551 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/agentine
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@b10b7d47b7ed181a95266aa89e0a2ddf13e7d551 -
Trigger Event:
release
-
Statement type:
File details
Details for the file recurrence-0.1.0-py3-none-any.whl.
File metadata
- Download URL: recurrence-0.1.0-py3-none-any.whl
- Upload date:
- Size: 11.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
695f90104227b29977d1feef5bb381f852829ae666c67ddf3d419a0ae1c558bf
|
|
| MD5 |
e040b14abbd8f417ea5cf8e331fcea9a
|
|
| BLAKE2b-256 |
b0fb9cb1cd651f8ba7610bc40cb1deec51bcf6acbf9ccde1924bac2f023ac8ff
|
Provenance
The following attestation bundles were made for recurrence-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on agentine/recurrence
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
recurrence-0.1.0-py3-none-any.whl -
Subject digest:
695f90104227b29977d1feef5bb381f852829ae666c67ddf3d419a0ae1c558bf - Sigstore transparency entry: 1095177254
- Sigstore integration time:
-
Permalink:
agentine/recurrence@b10b7d47b7ed181a95266aa89e0a2ddf13e7d551 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/agentine
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@b10b7d47b7ed181a95266aa89e0a2ddf13e7d551 -
Trigger Event:
release
-
Statement type: