A DB-driven APScheduler for Django
Project description
โก ZenPulse Scheduler for Django
Modern, Hybrid, and Zero-Latency Job Scheduler
ZenPulse Scheduler is a Developer-First background job scheduler for Django. Unlike traditional schedulers that either rely on heavy external infrastructure (Redis/Celery) or spam your database with polling queries, ZenPulse uses a Smart Hybrid Architecture.
๐ Features
- โ No Redis/Celery Required - Pure Django solution
- โ Zero DB Impact - Smart file-based signaling system
- โ Hybrid Execution - Config in DB, Runtime in RAM
- โ Selective Logging - Log everything, nothing, or only failures
- โ Live Updates - Change schedules without restarting
- โ Multi-Process Safe - Built-in locking mechanisms
- โ Interval & Cron Support - Flexible scheduling options
๐ฆ Installation
Step 1: Install the Package
pip install zenpulse-scheduler
Or if installing from source:
cd /path/to/package
pip install -e .
Step 2: Add to Django Settings
# settings.py
INSTALLED_APPS = [
# ... other apps
'zenpulse_scheduler',
]
Step 3: Run Migrations
python manage.py migrate
๐ Quick Start Guide
1. Define Your Job
Create a file (e.g., yourapp/jobs.py) and register your job:
from zenpulse_scheduler.registry import zenpulse_job
# Basic: auto-creates with default schedule (every 5 minutes, enabled)
@zenpulse_job("send_daily_report")
def send_daily_report():
"""Send daily sales report via email"""
print("๐ง Sending daily report...")
# Your business logic here
# Custom interval: runs every 30 seconds
@zenpulse_job("health_check", trigger="interval", interval_value=30, interval_unit="seconds")
def health_check():
print("๐ Health check...")
# Cron schedule: runs daily at 8:30 AM
@zenpulse_job("morning_report", trigger="cron", cron_hour="8", cron_minute="30")
def morning_report():
print("๐ Generating morning report...")
๐ Auto-Sync: When the scheduler starts, it automatically creates a
ScheduleConfigentry in the database for any new registered job. No need to manually add it in Django Admin!
Important: Make sure this file is imported when Django starts. You can do this by:
# yourapp/apps.py
from django.apps import AppConfig
class YourAppConfig(AppConfig):
name = 'yourapp'
def ready(self):
import yourapp.jobs # Import to register jobs
Or import it in your urls.py:
# yourapp/urls.py
import yourapp.jobs # Ensure jobs are registered
urlpatterns = [
# your urls
]
2. Start the Scheduler
Open a separate terminal and run:
python manage.py run_zenpulse_scheduler
You should see:
Starting ZenPulse Scheduler (Sync: 10s, Lock: False)...
Auto-created ScheduleConfig for job 'send_daily_report' [interval (5 minutes), enabled=True]
3. (Optional) Fine-tune via Admin
Jobs are auto-configured from the decorator defaults. But if you want to change settings later:
- Go to Django Admin โ ZenPulse Scheduler โ Schedule Configs
- Edit the auto-created entry
- Change schedule, enable/disable, adjust log policy, etc.
- Save โ changes apply within the next sync interval (no restart needed!)
๐ Complete Configuration Guide
All job scheduling is controlled through the ScheduleConfig model in Django Admin.
Core Fields
Job Key (Required)
- Type: Text (Unique)
- Description: Must match the name you used in
@zenpulse_job("name") - Example:
send_daily_report
Enabled (Required)
- Type: Checkbox
- Description: Controls whether the job runs
- Default: โ Checked (Enabled)
- Behavior:
- โ Checked โ Job runs according to schedule
- โ Unchecked โ Job stops immediately (within next sync interval)
Trigger Configuration
Trigger Type (Required)
Choose how your job should be scheduled:
| Choice | When to Use | Example |
|---|---|---|
| Interval | Repeating tasks that run in a loop | "Check for new emails every 5 minutes" |
| Cron | Tasks that run at specific calendar times | "Send report every Monday at 9 AM" |
Interval Configuration
Use when Trigger Type = Interval
Interval Value (Required for Interval)
- Type: Number
- Description: How many units to wait between runs
- Example:
30(combined with unit below)
Interval Unit (Required for Interval)
| Unit | Best For | Example Usage |
|---|---|---|
| Seconds | High-frequency monitoring, heartbeats | 30 seconds = runs every 30 seconds |
| Minutes | Standard periodic tasks | 15 minutes = runs every 15 minutes |
| Hours | Regular updates, data sync | 4 hours = runs every 4 hours |
| Days | Daily routines | 1 day = runs once per day |
| Weeks | Weekly maintenance | 2 weeks = runs every 2 weeks |
Example Configurations:
Interval Value: 10, Unit: Minutes โ Runs every 10 minutes
Interval Value: 1, Unit: Hours โ Runs every hour
Interval Value: 30, Unit: Seconds โ Runs every 30 seconds
Cron Configuration
Use when Trigger Type = Cron
All cron fields support:
- Specific values (e.g.,
8for 8 AM) - Wildcards (
*means "every") - Lists (e.g.,
mon,wed,fri) - Ranges (e.g.,
1-5)
Cron Minute
- Valid Values:
0-59or* - Default:
*(every minute) - Examples:
0= At the top of the hour30= At 30 minutes past the hour*/15= Every 15 minutes
Cron Hour
- Valid Values:
0-23or* - Default:
*(every hour) - Examples:
9= 9 AM14= 2 PM22= 10 PM
Cron Day (of Month)
- Valid Values:
1-31or* - Default:
*(every day) - Examples:
1= 1st of the month15= 15th of the month
Cron Month
- Valid Values:
1-12or* - Default:
*(every month) - Examples:
1= January12= December
Cron Day of Week
- Valid Values:
0-6(0=Sunday) ormon,tue,wed,thu,fri,sat,sunor* - Default:
*(every day) - Examples:
mon= Every Monday0= Every Sundaymon,wed,fri= Monday, Wednesday, Friday
Example Cron Configurations:
Daily at 8:30 AM:
Minute: 30, Hour: 8, Day: *, Month: *, Day of Week: *
Every Monday at 9 AM:
Minute: 0, Hour: 9, Day: *, Month: *, Day of Week: mon
First day of every month at midnight:
Minute: 0, Hour: 0, Day: 1, Month: *, Day of Week: *
Every weekday at 6 PM:
Minute: 0, Hour: 18, Day: *, Month: *, Day of Week: mon,tue,wed,thu,fri
Advanced Options
Max Instances
- Type: Number
- Default:
1 - Description: Maximum number of this job that can run simultaneously
- Use Case: Set to
3if you want to allow up to 3 parallel executions
Coalesce
- Type: Checkbox
- Default: โ Checked
- Description: If multiple runs were missed (e.g., scheduler was down), combine them into one
- Behavior:
- โ Checked โ Run once to catch up
- โ Unchecked โ Run all missed executions
Misfire Grace Time
- Type: Number (seconds)
- Default:
60 - Description: How long after the scheduled time the job can still run
- Example: If job was supposed to run at 10:00 but scheduler was busy, it can still run until 10:01 (60 seconds grace)
Logging Configuration
Log Policy
Control how much execution history is saved to the database:
| Policy | What Gets Logged | Database Impact | Best For |
|---|---|---|---|
| None | Nothing | Zero writes | High-frequency jobs (every few seconds) |
| Failures Only | Only when job crashes | Minimal writes | Production critical jobs (recommended) |
| All Executions | Every success and failure | High writes | Debugging, auditing |
Recommendation: Use Failures Only for production. This way:
- โ You get full error traces when something breaks
- โ Database stays clean
- โ You can still monitor job health
๐ฅ๏ธ Admin Panel Examples
Example 1: Send Email Every 30 Minutes
Job Key: send_email_digest
Enabled: โ
Trigger Type: Interval
Interval Value: 30
Interval Unit: Minutes
Log Policy: Failures Only
Example 2: Daily Report at 8:30 AM
Job Key: generate_daily_report
Enabled: โ
Trigger Type: Cron
Cron Minute: 30
Cron Hour: 8
Cron Day: *
Cron Month: *
Cron Day of Week: *
Log Policy: All Executions
Example 3: Weekly Cleanup Every Sunday at Midnight
Job Key: weekly_cleanup
Enabled: โ
Trigger Type: Cron
Cron Minute: 0
Cron Hour: 0
Cron Day: *
Cron Month: *
Cron Day of Week: sun
Log Policy: Failures Only
โ๏ธ Running the Scheduler
Development
python manage.py run_zenpulse_scheduler
Production (with Safety Lock)
python manage.py run_zenpulse_scheduler --lock
The --lock flag prevents multiple scheduler instances from running simultaneously:
- PostgreSQL: Uses advisory locks (recommended)
- MySQL: Uses
GET_LOCK - SQLite/Others: Uses file-based locking
Custom Sync Interval
By default, the scheduler checks for config changes every 10 seconds. To reduce database queries:
python manage.py run_zenpulse_scheduler --sync-every 60
This checks only once per minute. Trade-off: Changes take up to 60 seconds to apply.
๐๏ธ Architecture
โโโโโโโโโโโโโโโโโโโ
โ Django Admin โ โ You configure jobs here
โโโโโโโโโโฌโโโโโโโโโ
โ Save Config
โ
โโโโโโโโโโโโโโโโโโโ
โ Database โ โ Stores ScheduleConfig
โโโโโโโโโโฌโโโโโโโโโ
โ Sync (every 10s)
โ
โโโโโโโโโโโโโโโโโโโ
โ Scheduler โ โ Runs in separate process
โ (Memory) โ
โโโโโโโโโโฌโโโโโโโโโ
โ Execute
โ
โโโโโโโโโโโโโโโโโโโ
โ Your Job โ โ @zenpulse_job decorated function
โโโโโโโโโโโโโโโโโโโ
Key Points:
- Configuration lives in Database (persistent)
- Execution happens in Memory (fast)
- Scheduler syncs config changes automatically
- No Redis or external dependencies needed
๐ Monitoring Job Execution
View Execution Logs
Go to Django Admin โ ZenPulse Scheduler โ Job Execution Logs
You'll see:
- Job Key: Which job ran
- Status: Success or Fail
- Run Time: When it executed (with seconds precision)
- Duration: How long it took (in milliseconds)
- Exception Details: Full traceback if it failed
Understanding Log Entries
Job Key: send_email_digest
Status: Success
Run Time: 2026-02-01 14:30:45
Duration: 234.5 ms
If a job fails:
Job Key: send_email_digest
Status: Fail
Run Time: 2026-02-01 14:30:45
Exception Type: SMTPException
Exception Message: Connection refused
Traceback: [Full Python traceback here]
๐ง Troubleshooting
Job Not Running?
-
Check if job is registered:
# In Django shell from zenpulse_scheduler.registry import JobRegistry print(JobRegistry.get_all_jobs())
Your job should appear in the list.
-
Check if enabled in Admin:
- Go to Schedule Configs
- Verify
Enabledis checked
-
Check scheduler is running:
- Look for
Starting ZenPulse Scheduler...in terminal - Check for any error messages
- Look for
Duplicate Executions?
- Make sure only one
run_zenpulse_schedulerprocess is running - Use
--lockflag in production - Check if you accidentally started it in multiple terminals
Jobs Running at Wrong Time?
- Verify your
TIME_ZONEsetting in Django settings - Check cron configuration carefully
- Use
Intervalfor simple repeating tasks
Database Performance Issues?
- Set
Log PolicytoNoneorFailures Only - Increase
--sync-everyto reduce config checks - Consider archiving old logs periodically
๐ Production Deployment
Using Systemd (Linux)
Create /etc/systemd/system/zenpulse-scheduler.service:
[Unit]
Description=ZenPulse Scheduler
After=network.target
[Service]
Type=simple
User=www-data
WorkingDirectory=/path/to/your/project
ExecStart=/path/to/venv/bin/python manage.py run_zenpulse_scheduler --lock --sync-every 30
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
Enable and start:
sudo systemctl enable zenpulse-scheduler
sudo systemctl start zenpulse-scheduler
sudo systemctl status zenpulse-scheduler
Using Supervisor
[program:zenpulse-scheduler]
command=/path/to/venv/bin/python manage.py run_zenpulse_scheduler --lock
directory=/path/to/your/project
user=www-data
autostart=true
autorestart=true
redirect_stderr=true
stdout_logfile=/var/log/zenpulse-scheduler.log
Using Docker
# In your Dockerfile
CMD ["python", "manage.py", "run_zenpulse_scheduler", "--lock"]
Or in docker-compose.yml:
services:
scheduler:
build: .
command: python manage.py run_zenpulse_scheduler --lock
depends_on:
- db
โ FAQ
Q: Can I run this with Gunicorn/Uvicorn?
A: Yes, but run the scheduler in a separate process/container. Never run it inside web workers.
Q: What happens if I restart the server?
A: Configuration is in the database, so it persists. Just start the scheduler command again.
Q: Can jobs accept parameters?
A: Currently, jobs should be parameter-less functions. For different parameters, create separate job functions.
Q: How do I delete old logs?
A: You can manually delete from Admin or create a cleanup job:
@zenpulse_job("cleanup_old_logs")
def cleanup_old_logs():
from datetime import timedelta
from django.utils import timezone
from zenpulse_scheduler.models import JobExecutionLog
cutoff = timezone.now() - timedelta(days=30)
JobExecutionLog.objects.filter(run_time__lt=cutoff).delete()
Q: Is this production-ready?
A: Yes! It's built on APScheduler (battle-tested library) with Django best practices.
๐ License
[Your License Here]
๐ค Contributing
Contributions welcome! Please open an issue or PR.
Made with โค๏ธ for Django developers who want simple, powerful scheduling.
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
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 zenpulse_scheduler-0.1.6.tar.gz.
File metadata
- Download URL: zenpulse_scheduler-0.1.6.tar.gz
- Upload date:
- Size: 20.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7fd6d67fb74a8d1427dd80f0bd1282e12301ce0e127a73abf660dcec23c9e743
|
|
| MD5 |
9f122b50ab53ce99a10f13487ee33e4f
|
|
| BLAKE2b-256 |
a84967e6279126b35cab3793de11a3f9818016b063170018aade6fe97de2b57f
|
Provenance
The following attestation bundles were made for zenpulse_scheduler-0.1.6.tar.gz:
Publisher:
workflow.yml on masudranaxpert/zenpulse-scheduler
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
zenpulse_scheduler-0.1.6.tar.gz -
Subject digest:
7fd6d67fb74a8d1427dd80f0bd1282e12301ce0e127a73abf660dcec23c9e743 - Sigstore transparency entry: 1016622257
- Sigstore integration time:
-
Permalink:
masudranaxpert/zenpulse-scheduler@0a377308cdf0db63b78361611956b90732b7a3df -
Branch / Tag:
refs/tags/v0.1.6 - Owner: https://github.com/masudranaxpert
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
workflow.yml@0a377308cdf0db63b78361611956b90732b7a3df -
Trigger Event:
release
-
Statement type:
File details
Details for the file zenpulse_scheduler-0.1.6-py3-none-any.whl.
File metadata
- Download URL: zenpulse_scheduler-0.1.6-py3-none-any.whl
- Upload date:
- Size: 18.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 |
81db75e42924747ad4d297046c0ef22ca09978fe8eea03c04168c37673db3592
|
|
| MD5 |
dd1e42d42d2566c9643f44550e0fb37d
|
|
| BLAKE2b-256 |
00fb15760869e3ac8124167257f862af682e5474f8c57920b8e78e809a251510
|
Provenance
The following attestation bundles were made for zenpulse_scheduler-0.1.6-py3-none-any.whl:
Publisher:
workflow.yml on masudranaxpert/zenpulse-scheduler
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
zenpulse_scheduler-0.1.6-py3-none-any.whl -
Subject digest:
81db75e42924747ad4d297046c0ef22ca09978fe8eea03c04168c37673db3592 - Sigstore transparency entry: 1016622314
- Sigstore integration time:
-
Permalink:
masudranaxpert/zenpulse-scheduler@0a377308cdf0db63b78361611956b90732b7a3df -
Branch / Tag:
refs/tags/v0.1.6 - Owner: https://github.com/masudranaxpert
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
workflow.yml@0a377308cdf0db63b78361611956b90732b7a3df -
Trigger Event:
release
-
Statement type: