diff --git a/Procfile b/Procfile index eb17e00f4..9c71d6726 100644 --- a/Procfile +++ b/Procfile @@ -1 +1,2 @@ web: ./bin/run-prod.sh +clock: ./manage.py runscript cron diff --git a/requirements.txt b/requirements.txt index 7634e2e69..9a4e98bd4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -182,3 +182,16 @@ boto==2.39.0 \ --hash=sha256:950c5bf36691df916b94ebc5679fed07f642030d39132454ec178800d5b6c58a django-clear-cache==0.3 \ --hash=sha256:fc9d27d6d7dd143917393484236bdf66a109d99bc64a4418cdf5a1127356d766 +django-extensions==1.6.1 \ + --hash=sha256:4799534f35eba1c07cb6f9859aa5bb719886769f5d35d2a38e7490ce90c0ce69 \ + --hash=sha256:f98f991d2b039033ac5faa638c0d1afb2720abf4d9d781573c3592d6899480a1 +requests==2.8.1 \ + --hash=sha256:89f1b1f25dcd7b68f514e8d341a5b2eb466f960ae756822eaab480a3c1a81c28 \ + --hash=sha256:84fe8d5bf4dcdcc49002446c47a146d17ac10facf00d9086659064ac43b6c25b +apscheduler==3.0.4 \ + --hash=sha256:b0b395c557dfad8415b0848bb8a50d0627575f44bd6b577c8fe96db3ad30768d \ + --hash=sha256:8b9b39e102ad2a003fb8299f306adc225e7f1062af8ea819bb61a1cd8c8552d2 +tzlocal==1.2 --hash=sha256:438f122d684c951b22cb98aecc80fdb961b84e69542bf878c6b9a9419de09a8b +futures==3.0.3 \ + --hash=sha256:04afa2a06ab7dcca9d81717b420a7a14826061e9b2614a5c77dd24c75ccf97e4 \ + --hash=sha256:2fe2342bb4fe8b8e217f0d21b5921cbe5408bf966d9f92025e707e881b198bed diff --git a/scripts/__init__.py b/scripts/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/scripts/cron.py b/scripts/cron.py new file mode 100755 index 000000000..60bdfdde5 --- /dev/null +++ b/scripts/cron.py @@ -0,0 +1,69 @@ +from __future__ import print_function +import datetime +import os +import sys + +from django.core.management import call_command +from django.conf import settings + +import requests +from apscheduler.schedulers.blocking import BlockingScheduler + + +schedule = BlockingScheduler() + + +class scheduled_job(object): + """Decorator for scheduled jobs. Takes same args as apscheduler.schedule_job.""" + def __init__(self, *args, **kwargs): + self.args = args + self.kwargs = kwargs + + def __call__(self, fn): + job_name = fn.__name__ + self.name = job_name + self.callback = fn + schedule.add_job(self.run, id=job_name, *self.args, **self.kwargs) + self.log('Registered.') + return self.run + + def run(self): + self.log('starting') + try: + self.callback() + except Exception as e: + self.log('CRASHED: {}'.format(e)) + raise + else: + self.log('finished successfully') + + def log(self, message): + msg = '[{}] Clock job {}@{}: {}'.format( + datetime.datetime.utcnow(), self.name, + os.getenv('DEIS_APP', 'default_app'), message) + print(msg, file=sys.stderr) + + +def ping_dms(function): + """Pings Dead Man's Snitch after job completion if URL is set.""" + def _ping(): + function() + if settings.DEAD_MANS_SNITCH_URL: + utcnow = datetime.datetime.utcnow() + payload = {'m': 'Run {} on {}'.format(function.__name__, utcnow.isoformat())} + requests.get(settings.DEAD_MANS_SNITCH_URL, params=payload) + _ping.__name__ = function.__name__ + return _ping + + +@scheduled_job('interval', days=1, max_instances=1, coalesce=True) +@ping_dms +def job_update_product_details(): + call_command('update_product_details') + + +def run(): + try: + schedule.start() + except (KeyboardInterrupt, SystemExit): + pass diff --git a/snippets/settings.py b/snippets/settings.py index 6283a8e27..11b02e615 100644 --- a/snippets/settings.py +++ b/snippets/settings.py @@ -44,6 +44,7 @@ 'django_ace', 'product_details', 'clear_cache', + 'django_extensions', # Django apps 'django.contrib.admin', @@ -197,3 +198,5 @@ MEDIA_FILES_ROOT: 'max-age=900', # 15 Minutes MEDIA_BUNDLES_ROOT: 'max-age=2592000', # 1 Month } + +DEAD_MANS_SNITCH_URL = config('DEAD_MANS_SNITCH_URL', default=None)