⚡ 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.
Metadata
Release files for zenpulse-scheduler 0.1.6
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| zenpulse_scheduler-0.1.6.tar.gz | 20.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| zenpulse_scheduler-0.1.6-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 39.7 kB
Release files / zenpulse_scheduler-0.1.6.tar.gz
| Download URL | zenpulse_scheduler-0.1.6.tar.gz |
|---|---|
| Size | 20.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
7fd6d67fb74a8d1427dd80f0bd1282e12301ce0e127a73abf660dcec23c9e743
|
|
BLAKE2b-256 checksum How to use checksums |
a84967e6279126b35cab3793de11a3f9818016b063170018aade6fe97de2b57f
|
| 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 3, 2026.
Transparency logRelease files / zenpulse_scheduler-0.1.6-py3-none-any.whl
| Download URL | zenpulse_scheduler-0.1.6-py3-none-any.whl |
|---|---|
| Size | 18.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
81db75e42924747ad4d297046c0ef22ca09978fe8eea03c04168c37673db3592
|
|
BLAKE2b-256 checksum How to use checksums |
00fb15760869e3ac8124167257f862af682e5474f8c57920b8e78e809a251510
|
| 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 3, 2026.
Transparency log