├── cronjobs ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ └── cron.py └── __init__.py ├── .gitignore ├── MANIFEST.in ├── CONTRIBUTORS ├── setup.py ├── LICENSE └── README.rst /cronjobs/management/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cronjobs/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | dist 3 | *.egg-info 4 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include README.rst 3 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | Original implementation by: 2 | Jeff Balogh 3 | 4 | Contributors: 5 | James Socol 6 | Andy McKay 7 | Dave Dash 8 | -------------------------------------------------------------------------------- /cronjobs/__init__.py: -------------------------------------------------------------------------------- 1 | registered = {} 2 | registered_lock = {} 3 | 4 | def register(f=None, lock=True): 5 | """Decorator to add the function to the cronjob library. 6 | 7 | @cronjobs.register 8 | def my_task(): 9 | print('I can be run once/machine at a time.') 10 | 11 | @cronjobs.register(lock=False) 12 | def my_task(): 13 | print('I am concurrent friendly!') 14 | 15 | """ 16 | 17 | def decorator(f, lock=lock): 18 | registered[f.__name__] = f 19 | if lock: 20 | registered_lock[f.__name__] = f 21 | return f 22 | 23 | if callable(f): 24 | return decorator(f, lock) 25 | return decorator 26 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | setup( 4 | name='django-cronjobs', 5 | version='0.2.3', 6 | description='A simple Django app for running cron jobs.', 7 | long_description=open('README.rst').read(), 8 | author='Mozilla', 9 | author_email='james@mozilla.com', 10 | url='http://github.com/jsocol/django-cronjobs', 11 | license='BSD', 12 | packages=find_packages(), 13 | include_package_data=True, 14 | classifiers=[ 15 | 'Development Status :: 4 - Beta', 16 | 'Environment :: Web Environment', 17 | 'Environment :: Web Environment :: Mozilla', 18 | 'Intended Audience :: Developers', 19 | 'Framework :: Django', 20 | 'License :: OSI Approved :: BSD License', 21 | 'Operating System :: OS Independent', 22 | 'Programming Language :: Python', 23 | 'Topic :: Software Development :: Libraries :: Python Modules', 24 | ] 25 | ) 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010, Mozilla Foundation 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of django-cronjobs nor the names of its contributors 15 | may be used to endorse or promote products derived from this software 16 | without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | =============== 2 | Django Cronjobs 3 | =============== 4 | 5 | django-cronjobs is a simple Django app that runs registered cron jobs via a 6 | management command. 7 | 8 | 9 | DEPRECATION 10 | =========== 11 | 12 | **django-cronjobs** should be considered **deprecated and unmaintained.** It 13 | was effectively a shortcut for adding management commands, but that has 14 | gotten easier and should be considered the best path forward. 15 | 16 | 17 | Installing 18 | ========== 19 | 20 | To install django-cronjobs, first install via pip or easy_install, then just 21 | add ``cronjobs`` to your ``INSTALLED_APPS``. 22 | 23 | 24 | Registering a cron job 25 | ====================== 26 | 27 | django-cronjobs includes a decorator to register a cronjob, and discovers 28 | registered jobs in the module ``.cron``. 29 | 30 | For example:: 31 | 32 | # myapp/cron.py 33 | import cronjobs 34 | 35 | @cronjobs.register 36 | def periodic_task(): 37 | pass 38 | 39 | django-cronjobs will then recognize ``periodic_task`` as a valid job. 40 | 41 | 42 | Running a cron job 43 | ================== 44 | 45 | To run a registered cron job, use the ``cron`` management command:: 46 | 47 | $ ./manage.py cron 48 | 49 | So to run ``periodic_task`` from above, you could use:: 50 | 51 | $ ./manage.py cron periodic_task 52 | 53 | Additional arguments can be passed after the name of the task. 54 | 55 | 56 | Locks 57 | ===== 58 | 59 | By default, cron jobs are locked so that only one copy of a given job can be 60 | running at a time. If you need to override this behavior, you can pass the 61 | ``lock`` kwarg to ``register``:: 62 | 63 | from cronjobs import register 64 | @register(lock=False) 65 | def my_cron_job(): 66 | # Multiple instances of me can run simultaneously. 67 | 68 | If you run multiple sets of cronjobs on the same file system and need the locks 69 | to not collide, set ``CRONJOB_LOCK_PREFIX`` to something unique in your Django 70 | settings. 71 | -------------------------------------------------------------------------------- /cronjobs/management/commands/cron.py: -------------------------------------------------------------------------------- 1 | import atexit 2 | import logging 3 | import os 4 | import sys 5 | import imp 6 | import tempfile 7 | 8 | from django.conf import settings 9 | from django.utils.importlib import import_module 10 | from django.core.management.base import BaseCommand 11 | 12 | import cronjobs 13 | 14 | 15 | log = logging.getLogger('cron') 16 | 17 | LOCK = getattr(settings, 'CRONJOB_LOCK_PREFIX', 'lock') 18 | 19 | 20 | class Command(BaseCommand): 21 | help = 'Run a script, often a cronjob' 22 | args = '[name args...]' 23 | 24 | def handle(self, *args, **opts): 25 | # Load up all the cron scripts. 26 | for app in settings.INSTALLED_APPS: 27 | try: 28 | app_path = import_module(app).__path__ 29 | except AttributeError: 30 | continue 31 | 32 | try: 33 | imp.find_module('cron', app_path) 34 | except ImportError as e: 35 | continue 36 | 37 | import_module('%s.cron' % app) 38 | 39 | registered = cronjobs.registered 40 | 41 | if not args: 42 | log.error("Cron called but doesn't know what to do.") 43 | print 'Try one of these:\n%s' % '\n'.join(sorted(registered)) 44 | sys.exit(1) 45 | 46 | script, args = args[0], args[1:] 47 | if script not in registered: 48 | log.error("Cron called with unrecognized command: %s %s" % 49 | (script, args)) 50 | print 'Unrecognized name: %s' % script 51 | sys.exit(1) 52 | 53 | # Acquire lock if needed. 54 | if script in cronjobs.registered_lock: 55 | filename = os.path.join(tempfile.gettempdir(), 56 | 'django_cron.%s.%s' % (LOCK, script)) 57 | try: 58 | fd = os.open(filename, os.O_CREAT|os.O_EXCL) 59 | 60 | def register(): 61 | os.close(fd) 62 | os.remove(filename) 63 | 64 | atexit.register(register) 65 | except OSError: 66 | msg = ("Script run multiple times. If this isn't true, delete " 67 | "`%s`." % filename) 68 | log.error(msg) 69 | sys.stderr.write(msg + "\n") 70 | sys.exit(1) 71 | 72 | log.info("Beginning job: %s %s" % (script, args)) 73 | registered[script](*args) 74 | log.info("Ending job: %s %s" % (script, args)) 75 | --------------------------------------------------------------------------------