Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ENH: Add cron scheduler #73

Merged
merged 42 commits into from
Aug 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
2dc0aa1
fix: TimeOfYear and also int anchoring in time
Miksus Aug 6, 2022
dd2d60e
add: always time period "singleton"
Miksus Aug 7, 2022
84e34c4
upd: anchor time periods extends for full periods
Miksus Aug 7, 2022
a0ec20f
fix: month/week names/abbrs always in English
Miksus Aug 7, 2022
9d09dd5
fix: now interval is left by default
Miksus Aug 7, 2022
26af4ce
fix: Now anchored periods are full by default
Miksus Aug 7, 2022
b483ce7
fix: And and Any time periods
Miksus Aug 7, 2022
14f8137
fix: anchor_int with anchor periods
Miksus Aug 7, 2022
abfbdcd
test: add more time construct tests
Miksus Aug 7, 2022
2c56550
upd: better reprs in time
Miksus Aug 7, 2022
63b13df
add: crontab period
Miksus Aug 7, 2022
db37efa
fix: full if end is scope_max in anchor
Miksus Aug 8, 2022
5a9dd5c
add: time periods to frozen dataclasses
Miksus Aug 8, 2022
958bab5
upd: time periods operates on microseconds
Miksus Aug 8, 2022
091527f
fix: static interval
Miksus Aug 9, 2022
3cb7db7
upd: now Interval is frozen dataclass as well
Miksus Aug 9, 2022
76f36ba
fix: anchor end of the time period and time point
Miksus Aug 9, 2022
626c4c5
fix: time interval maximum includes end
Miksus Aug 9, 2022
0a2de2d
test: fix some tests for new interpretation of end
Miksus Aug 9, 2022
ff67cd2
fix: time interval rollforward if it's empty
Miksus Aug 9, 2022
e91a32e
test: add is_full to tests
Miksus Aug 9, 2022
f65b2bb
test: fix some tests related to timepoints and end
Miksus Aug 9, 2022
cda07cc
add: option for right close
Miksus Aug 9, 2022
db2bf75
fix: immutable error in string setting in periods
Miksus Aug 9, 2022
af9ec97
test: add closed assertions
Miksus Aug 9, 2022
7bbfff0
fix: time interval overlaps in All roll
Miksus Aug 10, 2022
2df17de
add: time period All and Any compression
Miksus Aug 10, 2022
f773605
test: fix cron tests
Miksus Aug 10, 2022
ebfd020
add: create_range for anchored time periods
Miksus Aug 10, 2022
17b78ce
upd: add start and end to time period create_range
Miksus Aug 11, 2022
8d03b01
upd: renamed crontab to cron and add step tests
Miksus Aug 11, 2022
6367d9e
add: TaskRunnable (similar to TaskExecutable)
Miksus Aug 11, 2022
9341e72
add: cron to cond API and cond syntax
Miksus Aug 11, 2022
1f7c5ad
test: add sanity checks for continuous periods
Miksus Aug 11, 2022
c744123
fix: Literal import for older Python
Miksus Aug 11, 2022
5c6480e
fix: right of the interval is now always open
Miksus Aug 11, 2022
8d120ca
fix: now cron supports non-standard names in steps
Miksus Aug 11, 2022
04c3501
test: fix parse test
Miksus Aug 11, 2022
ce387b1
test: add interval tests
Miksus Aug 11, 2022
ce8fc27
test: add kwargs cron test
Miksus Aug 11, 2022
c8a7028
docs: add cron to documentation
Miksus Aug 11, 2022
09e5bcc
docs: add PR changes to versions.rst
Miksus Aug 12, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions docs/code/conds/api/cron.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from rocketry.conds import cron

@app.task(cron('* * * * *'))
def do_minutely():
...

@app.task(cron('*/2 12-18 * Oct Fri'))
def do_complex():
"Run at every 2nd minute past every hour from 12 through 18 on Friday in October."
...
16 changes: 16 additions & 0 deletions docs/code/conds/api/cron_kwargs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from rocketry.conds import cron

@app.task(cron(minute="*/5"))
def do_simple():
"Run at every 5th minute"
...

@app.task(cron(minute="*/2", hour="7-18", day_of_month="1,2,3", month="Feb-Aug/2"))
def do_complex():
"""Run at:
- Every second minute
- Between 07:00 (7 a.m.) - 18:00 (6 p.m.)
- On 1st, 2nd and 3rd day of month
- From February to August every second month
"""
...
28 changes: 28 additions & 0 deletions docs/handbooks/conditions/api/cron.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@

.. _cron:

Cron Scheduling
===============

Rocketry also natively supports `cron-like scheduling <https://en.wikipedia.org/wiki/Cron>`_.

You can input a cron statement as a string:

Examples
--------

.. literalinclude:: /code/conds/api/cron.py
:language: py

Or you can use named arguments:

.. literalinclude:: /code/conds/api/cron_kwargs.py
:language: py

See more from the official definition of cron.

.. note::

Unlike most of the condition, the cron condition checks whether the task
has run on the period (as standard with cron schedulers) and not whether
the task has finished on the given period.
1 change: 1 addition & 0 deletions docs/handbooks/conditions/api/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Here are some examples:

logical
periodical
cron
pipeline
task_status
scheduler
2 changes: 1 addition & 1 deletion docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ applications. It is simple, clean and extensive.
**Core functionalities:**

- Powerful scheduling syntax
- A lot of built-in scheduling options
- A lot of built-in scheduling options (including :ref:`cron <cron>`)
- Task parallelization
- Task parametrization
- Task pipelining
Expand Down
9 changes: 9 additions & 0 deletions docs/versions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@
Version history
===============

- ``2.3.0``

- Add: Cron style scheduling
- Add: New condition, ``TaskRunnable``
- Add: ``always`` time period
- Fix: Various bugs related to ``Any``, ``All`` and ``StaticInterval`` time periods
- Fix: Integers as start and end in time periods
- Upd: Now time periods are immutable

- ``2.2.0``

- Add: Async support
Expand Down
14 changes: 12 additions & 2 deletions rocketry/conditions/api.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import Callable, Union
from rocketry.conditions.scheduler import SchedulerStarted
from rocketry.conditions.task.task import DependFailure, DependFinish, DependSuccess, TaskFailed, TaskFinished, TaskStarted, TaskSucceeded
from rocketry.conditions.task.task import DependFailure, DependFinish, DependSuccess, TaskFailed, TaskFinished, TaskRunnable, TaskStarted, TaskSucceeded
from rocketry.core import (
BaseCondition
)
Expand All @@ -9,6 +9,7 @@
)
from rocketry.core.condition.base import All, Any, Not
from rocketry.core.task import Task
from rocketry.time import Cron
from .time import IsPeriod
from .task import TaskExecutable, TaskRunning
from rocketry.time import (
Expand Down Expand Up @@ -137,6 +138,15 @@ def every(past:str, based="run"):
else:
raise ValueError(f"Invalid status: {based}")

def cron(__expr=None, **kwargs):
if __expr:
args = __expr.split(" ")
else:
args = ()

period = Cron(*args, **kwargs)
return TaskRunnable(period=period)

# Task pipelining
# ---------------

Expand Down Expand Up @@ -188,4 +198,4 @@ def running(more_than:str=None, less_than=None, task=None):
# ---------

def scheduler_running(more_than:str=None, less_than=None):
return SchedulerStarted(period=TimeSpanDelta(near=more_than, far=less_than))
return SchedulerStarted(period=TimeSpanDelta(near=more_than, far=less_than))
31 changes: 31 additions & 0 deletions rocketry/conditions/task/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ def get_state(self, task=Task(), session=Session()):
isin_period = (
# TimeDelta has no __contains__. One cannot say whether now is "past 2 hours".
# And please tell why this does not raise an exception then? - Future me
# Because the period is used in the sub statements and TimeDelta is still accepted - Senior me
True
if isinstance(period, TimeDelta)
else IsPeriod(period=period).observe()
Expand Down Expand Up @@ -292,6 +293,36 @@ def _from_period(cls, span_type=None, **kwargs):
return cls(period=period)


class TaskRunnable(BaseCondition):
"""Condition for checking whether a given
task has not run (for given period).
Useful to set the given task to run once
in given period.
"""

def __init__(self, task=None, period=None):
self.period = period
self.task = task
super().__init__()

def get_state(self, task=Task(), session=Session()):
task = self.task if self.task is not None else task
period = self.period

has_not_run = TaskStarted(period=period, task=task) == 0

isin_period = (
True
if isinstance(period, TimeDelta)
else IsPeriod(period=period).observe()
)

return (
isin_period
and has_not_run.observe(task=task, session=session)
)


class DependFinish(DependMixin):
"""Condition for checking whether a given
task has not finished after running a dependent
Expand Down
3 changes: 2 additions & 1 deletion rocketry/conds/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@
started, succeeded, failed, finished,

scheduler_running,
running
running,
cron
)
5 changes: 4 additions & 1 deletion rocketry/core/time/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
StaticInterval,
All, Any,

PARSERS
PARSERS,

# Constants
always,
)


Expand Down
Loading