Skip to main content

A simple, file-system-backed library to handle rate-limited job execution via cron.

Project description

🏃‍♂️ Pace Runner

Pace Runner is a lightweight, file-system-based job scheduler designed to efficiently handle rate-limited or resource-constrained service calls by deferring them to a recurring cron job. Never worry about hitting an API rate limit again; simply queue your jobs, and let Pace Runner manage the execution, retries, and notifications.

✨ Features

  • Rate-Limit Management: Automatically defers job execution, preventing immediate rate limit errors.
  • Decoupled Execution: Submit jobs immediately in your main application and process them later via a scheduled cron job.
  • Customizable Executors: Define your own logic for processing the job payload and handling rate limits within the executor.
  • Pluggable Messengers: Get real-time updates and alerts for job completions or errors via Slack, Email, File Log, or any other custom messenger service.
  • Local Persistence: Uses the local file system for robust job data storage and file locks to ensure single-instance cron execution.
  • Job Inspection: Easily inspect the status, error messages, and retry count of all pending jobs.

🛠️ Installation

pip install pace-runner

🚀 Two-Step Quick Start

Using Pace Runner is a simple two-step process: Submitting Jobs and Running the Executor.

Step 1: Submit Jobs

Use the add_job function in your main script whenever you need to execute a rate-limited operation.

In this step, you define the job's payload (the data needed for the task) and the output path where the result will be saved.

# main_application_script.py

from pace_runner import BaseJobPayload, add_job
from dataclasses import dataclass, field

# 1. Define the custom job payload
@dataclass
class TempJobPayload(BaseJobPayload):
    """Payload for a custom processing job."""
    text: str = field(init=True)

temp_path = "/tmp/job_outputs"

# 2. Add jobs to the pending queue
for idx in range(5):
    add_job(
        payload=TempJobPayload(text=f"Process this text item {idx}"), 
        # The executor will save its JSON output to this path
        output_path=f"{temp_path}/item_{idx}.json"
    )

print(f"Submitted 5 jobs to the queue. They will be processed by the cron job.")

Step 2: Run the Executor via Cron

Create a separate Python script, cron_job.py, which defines the execution logic and notification method. Schedule this script to run at regular intervals (e.g., every 5 minutes) using cron.

A. Define the Executor Script (cron_job.py)

# cron_job.py

from pace_runner import JobExecutorBase, MessengerBase, SystemJob

# 1. Define the Job Executor
# This class contains the core logic for running your time-limited service.
class TempJobExecutor(JobExecutorBase):
    """
    Executes the actual rate-limited task.
    The returned python dict will be saved as a JSON file at job.output_path.
    """
    def _execute_handler_logic(self, job: SystemJob) -> dict:
        print(f"Executing job: {job.id} with payload: {job.payload}")
        
        # --- PLACE YOUR RATE-LIMITED SERVICE CALL HERE ---
        # e.g., result = tts_service.synthesize(job.payload.text)
        
        # For this example, we'll just return the payload's data
        return {"input_text": job.payload.text, "status": "completed"}

# 2. Define the Messenger
# This class sends alerts on errors and batch completion status.
class PrintMessenger(MessengerBase):
    """Sends notifications to the console."""
    # NOTE: The base JobExecutorBase's run_cron_job method controls *when* to call 
    # this method (e.g., upon error or completion of a batch).
    def _send_notification(self, message: str):
        # The message includes error alerts and cron completion messages.
        # You would implement your Slack/Email/etc. API call here.
        print(message)


# 3. Run the cron job
executor = TempJobExecutor(messenger=PrintMessenger())
# The run_cron_job method handles file locks, job retrieval, execution, 
# and determines when to send notifications via the messenger.
executor.run_cron_job()

B. Schedule with Cron

Add the following entry to your crontab to run the script every five minutes:

# To edit your crontab: crontab -e
# PACE_RUNNER_SETTINGS_TOML is defined in configuration section below

*/5 * * * * PACE_RUNNER_SETTINGS_TOML=/path/to/custom_settings.toml /usr/bin/env python3 /path/to/your/cron_job.py

🔍 Job Management and Inspection

You can manage the pending job queue outside of the execution script using simple utility functions.

# job_inspector.py

from pace_runner import get_pending_jobs, remove_all_jobs, remove_job_by_id

# 1. Inspect all pending jobs
all_jobs = get_pending_jobs()

if all_jobs:
    print(f"Found {len(all_jobs)} pending jobs.")
    
    # Analyze jobs, for example, checking for failed ones
    for job in all_jobs:
        if job.error_message:
            print(f"Job ID: {job.id} failed with error: {job.error_message}")
            bad_job_id = job.id 

    # 2. Remove a specific job by its ID (e.g., a problematic one)
    if 'bad_job_id' in locals():
        was_job_removal_succesful = remove_job_by_id(bad_job_id)
        print(f"Removal of job {bad_job_id} successful: {was_job_removal_succesful}")


# 3. Or, remove all pending jobs at once
# num_removed_jobs = remove_all_jobs()
# print(f"Removed {num_removed_jobs} jobs.")

⚙️ Configuration

Pace Runner is configured using a TOML file, which must be located at a path exposed via the environment variable PACE_RUNNER_SETTINGS_TOML.

Example pace_runner_settings.toml

The configuration controls job metadata, output paths, and logging behavior.

[default]
# Controls the timezone used for all timestamps (e.g., job submission time,
# last execution time) for consistency and accurate logging.
tz = "Asia/Kolkata"

[default.output]
# The base directory where all job-specific output JSON files will be saved.
# The final path is: base/job_name/job_id.json.
base = "/home/limited_user/Data/cron_job_outputs"

# A subdirectory name used to group jobs. Useful for distinguishing between 
# different types of services (e.g., "tts_jobs" vs "image_gen_jobs").
job_name = "default_job"

[default.logs]
# The base directory for Pace Runner's internal log files.
base = "/tmp/pace_runner_logs"

# If true, log messages will also be streamed to the console (stdout/stderr).
# Recommended to set to 'false' when running as a scheduled cron job.
enable_stream = false

# The minimum severity level for logs to be recorded (e.g., DEBUG, INFO, WARNING, ERROR).
level = "INFO"

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

pace_runner-0.1.0.tar.gz (15.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pace_runner-0.1.0-py3-none-any.whl (17.5 kB view details)

Uploaded Python 3

File details

Details for the file pace_runner-0.1.0.tar.gz.

File metadata

  • Download URL: pace_runner-0.1.0.tar.gz
  • Upload date:
  • Size: 15.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.1.3 CPython/3.10.12 Linux/6.8.0-85-generic

File hashes

Hashes for pace_runner-0.1.0.tar.gz
Algorithm Hash digest
SHA256 4269db430a7fa4a6a9da31a41fea5f7014dd90c60cbd8a2d7b0996a0bb7341c5
MD5 3035e38f702d71d578df01827973c41e
BLAKE2b-256 8d7f277b7fdd2b7dce9b324a11f47ed55ed58801bc891052c259c54b0da35856

See more details on using hashes here.

File details

Details for the file pace_runner-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: pace_runner-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 17.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.1.3 CPython/3.10.12 Linux/6.8.0-85-generic

File hashes

Hashes for pace_runner-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 fad4be5b62d57f711151e989492dee56ec985b1d539383e83f0fe87de77a9c4c
MD5 d69f28337951cb295a1641e8511afa30
BLAKE2b-256 14f72c89d06380ac14b6d7060642811b18f0dfcba414cc66f023de9b3d09726e

See more details on using hashes here.

Supported by

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