Skip to content

Commit

Permalink
Runtime. register_cron() now handles periods. Python 3.3 support dropped
Browse files Browse the repository at this point in the history
* register_cron() now handles periods

* Introduced RuntimeConfigurationError

* Test added

* Update tox.ini

* Update .travis.yml

* Update index.rst
  • Loading branch information
idlesign committed Mar 15, 2018
1 parent b1abdee commit ae91b9d
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 10 deletions.
1 change: 0 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ sudo: false

python:
- 2.7
- 3.3
- 3.4
- 3.5
- 3.6
Expand Down
2 changes: 1 addition & 1 deletion docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ By that time you already know that **uwsgiconf** is just another configuration m
Requirements
------------

1. Python 2.7+, 3.3+
1. Python 2.7+, 3.4+
2. ``click`` package (optional, for CLI)


Expand Down
23 changes: 23 additions & 0 deletions tests/runtime/test_runtime_scheduling.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import pytest

from uwsgiconf.runtime.scheduling import *


Expand All @@ -17,3 +19,24 @@ def test_cron():
@register_cron(hour=-3)
def fire1():
pass

with pytest.raises(RuntimeConfigurationError):
register_cron(hour='-%s/2')

now = datetime.now()

@register_cron(hour='%s-%s/2' % (now.hour, now.hour+3), weekday='0-6')
def runnable1():
return 100

@register_cron(hour='%s-%s' % (now.hour, now.hour+3))
def runnable2():
return 200

@register_cron(hour='%s-%s' % (now.hour-3, now.hour-1))
def not_runnable():
return 200

assert runnable1() == 100
assert runnable2() == 200
assert not_runnable() is None
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# See http://tox.readthedocs.org/en/latest/examples.html for samples.
[tox]
envlist =
py{27,32,33,34,35,36}
py{27,34,35,36}

skip_missing_interpreters = True

Expand Down
4 changes: 4 additions & 0 deletions uwsgiconf/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,7 @@ class UwsgiconfException(Exception):

class ConfigurationError(UwsgiconfException):
pass


class RuntimeConfigurationError(ConfigurationError):
pass
105 changes: 98 additions & 7 deletions uwsgiconf/runtime/scheduling.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
from datetime import datetime
from functools import partial, wraps

from .signals import _automate_signal
from .. import uwsgi
from ..exceptions import RuntimeConfigurationError
from ..utils import string_types


# todo: add_ms_timer
Expand Down Expand Up @@ -92,17 +97,23 @@ def repeat():
but instead of "*", use -1,
and instead of "/2", "/3", etc. use -2 and -3, etc.
:param int weekday: Day of a the week number. Defaults to `each`.
.. note:: Periods - rules like hour='10-18/2' (from 10 till 18 every 2 hours) - are allowed,
but they are emulated by uwsgiconf. Use strings to define periods.
Keep in mind, that your actual function will be wrapped into another one, which will check
whether it is time to call your function.
:param int|str|unicode weekday: Day of a the week number. Defaults to `each`.
0 - Sunday 1 - Monday 2 - Tuesday 3 - Wednesday
4 - Thursday 5 - Friday 6 - Saturday
:param int month: Month number 1-12. Defaults to `each`.
:param int|str|unicode month: Month number 1-12. Defaults to `each`.
:param int day: Day of the month number 1-31. Defaults to `each`.
:param int|str|unicode day: Day of the month number 1-31. Defaults to `each`.
:param int hour: Hour 0-23. Defaults to `each`.
:param int|str|unicode hour: Hour 0-23. Defaults to `each`.
:param int minute: Minute 0-59. Defaults to `each`.
:param int|str|unicode minute: Minute 0-59. Defaults to `each`.
:param int|Signal|str|unicode target: Existing signal to raise
or Signal Target to register signal implicitly.
Expand All @@ -125,6 +136,86 @@ def repeat():
:raises ValueError: If unable to add cron rule.
"""
args = [(-1 if arg is None else arg) for arg in (minute, hour, day, month, weekday)]
task_args_initial = {name: val for name, val in locals().items() if val is not None and name != 'target'}
task_args_casted = {}

def skip_task(check_funcs):
now = datetime.now()
allright = all((func(now) for func in check_funcs))
return not allright

def check_date(now, attr, target_range):
attr = getattr(now, attr)

if callable(attr): # E.g. weekday.
attr = attr()

return attr in target_range

check_date_funcs = []

for name, val in task_args_initial.items():

# uWSGI won't accept strings, so we emulate ranges here.
if isinstance(val, string_types):

# Rules like 10-18/2 (from 10 till 18 every 2 hours).
val, _, step = val.partition('/')
step = int(step) if step else 1
start, _, end = val.partition('-')

if not (start and end):
raise RuntimeConfigurationError(
'String cron rule without a range is not supported. Use integer notation.')

start = int(start)
end = int(end)

now_attr_name = name

period_range = set(range(start, end+1, step))

if name == 'weekday':
# Special case for weekday indexes: swap uWSGI Sunday 0 for ISO Sunday 7.
now_attr_name = 'isoweekday'

if 0 in period_range:
period_range.discard(0)
period_range.add(7)

# Gather date checking functions in one place.
check_date_funcs.append(partial(check_date, attr=now_attr_name, target_range=period_range))

# Use minimal duration (-1).
val = None

task_args_casted[name] = val

if not check_date_funcs:
# No special handling of periods, quit early.
args = [(-1 if arg is None else arg) for arg in (minute, hour, day, month, weekday)]
return _automate_signal(target, func=lambda sig: uwsgi.add_cron(int(sig), *args))

skip_task = partial(skip_task, check_date_funcs)

def decor(func_action):
"""Decorator wrapping."""

@wraps(func_action)
def func_action_wrapper(*args, **kwargs):
"""Action function wrapper to handle periods in rules."""

if skip_task():
# todo Maybe allow user defined value for this return.
return None

return func_action(*args, **kwargs)

args = []
for arg_name in {'minute', 'hour', 'day', 'month', 'weekday'}:
arg = task_args_casted.get(arg_name, None)
args.append(-1 if arg is None else arg)

return _automate_signal(target, func=lambda sig: uwsgi.add_cron(int(sig), *args))(func_action_wrapper)

return _automate_signal(target, func=lambda sig: uwsgi.add_cron(int(sig), *args))
return decor

0 comments on commit ae91b9d

Please sign in to comment.