Skip to content

Commit

Permalink
Schedule port for micropython: removed the dependancy to datetime
Browse files Browse the repository at this point in the history
  • Loading branch information
rguillon authored and rguillon committed Aug 20, 2016
1 parent 9a37002 commit 6340bb2
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 168 deletions.
2 changes: 1 addition & 1 deletion AUTHORS.rst
Expand Up @@ -5,7 +5,7 @@ schedule by

Patches and Suggestions
```````````````````````

- rguillon <https://github.com/rguillon>
- mrhwick <https://github.com/mrhwick>
- mattss <https://github.com/mattss>
- abultman <https://github.com/abultman>
42 changes: 13 additions & 29 deletions README.rst
Expand Up @@ -2,40 +2,29 @@ schedule
========


.. image:: https://api.travis-ci.org/dbader/schedule.svg?branch=master
:target: https://travis-ci.org/dbader/schedule
.. image:: https://api.travis-ci.org/rguillon/schedule.svg?branch=nodatetime
:target: https://travis-ci.org/rguillon/schedule

.. image:: https://coveralls.io/repos/dbader/schedule/badge.svg?branch=master
:target: https://coveralls.io/r/dbader/schedule
.. image:: https://coveralls.io/repos/rguillon/schedule/badge.svg?branch=nodatetime
:target: https://coveralls.io/r/rguillon/schedule

.. image:: https://img.shields.io/pypi/v/schedule.svg
:target: https://pypi.python.org/pypi/schedule
.. image:: https://img.shields.io/pypi/v/micropython-schedule.svg
:target: https://pypi.python.org/pypi/micropython-schedule

.. image:: https://img.shields.io/pypi/dm/schedule.svg
:target: https://pypi.python.org/pypi/schedule
.. image:: https://img.shields.io/pypi/dm/micropython-schedule.svg
:target: https://pypi.python.org/pypi/micropython-schedule

Python job scheduling for humans.
Micro-python job scheduling for humans.

An in-process scheduler for periodic jobs that uses the builder pattern
for configuration. Schedule lets you run Python functions (or any other
callable) periodically at pre-determined intervals using a simple,
human-friendly syntax.
A micronized port of <https://github.com/dbader/schedule>

Inspired by `Adam Wiggins' <https://github.com/adamwiggins>`_ article `"Rethinking Cron" <http://adam.heroku.com/past/2010/4/13/rethinking_cron/>`_ (`Google cache <http://webcache.googleusercontent.com/search?q=cache:F14k7BNcufsJ:adam.heroku.com/past/2010/4/13/rethinking_cron/+&cd=1&hl=de&ct=clnk&gl=de>`_) and the `clockwork <https://github.com/tomykaira/clockwork>`_ Ruby module.

Features
--------
- A simple to use API for scheduling jobs.
- Very lightweight and no external dependencies.
- Excellent test coverage.
- Tested on Python 2.7 and 3.4

Usage
-----

.. code-block:: bash
$ pip install schedule
$ micropython -m upip install micropython-schedule
.. code-block:: python
Expand All @@ -55,16 +44,11 @@ Usage
schedule.run_pending()
time.sleep(1)
FAQ
---

In lieu of a full documentation (coming soon) check out this set of `frequently asked questions <https://github.com/dbader/schedule/blob/master/FAQ.rst>`_ for solutions to some common questions.

Meta
----

Daniel Bader - `@dbader_org <https://twitter.com/dbader_org>`_ - mail@dbader.org
Renaud Guillon - - renaud.guillon@gmail.com

Distributed under the MIT license. See ``LICENSE.txt`` for more information.

https://github.com/dbader/schedule
https://github.com/rguillon/schedule
147 changes: 71 additions & 76 deletions schedule/__init__.py
Expand Up @@ -34,19 +34,23 @@
[2] https://github.com/tomykaira/clockwork
[3] http://adam.heroku.com/past/2010/6/30/replace_cron_with_clockwork/
"""
import datetime
import functools
import logging
import time

logger = logging.getLogger('schedule')


def now():
return int(time.time())


class CancelJob(object):
pass


class Scheduler(object):

def __init__(self):
self.jobs = []

Expand Down Expand Up @@ -107,18 +111,19 @@ def next_run(self):
@property
def idle_seconds(self):
"""Number of seconds until `next_run`."""
return (self.next_run - datetime.datetime.now()).total_seconds()
return (self.next_run - now())


class Job(object):
"""A periodic job as used by `Scheduler`."""

def __init__(self, interval):
self.interval = interval # pause interval * unit between runs
self.job_func = None # the job job_func to run
self.unit = None # time units, e.g. 'minutes', 'hours', ...
self.at_time = None # optional time at which this job runs
self.last_run = None # datetime of the last run
self.next_run = None # datetime of the next run
self.last_run = None # time of the last run
self.next_run = None # time of the next run
self.period = None # timedelta between runs, only valid for
self.start_day = None # Specific day of the week to start on

Expand All @@ -127,36 +132,9 @@ def __lt__(self, other):
they run next."""
return self.next_run < other.next_run

def __repr__(self):
def format_time(t):
return t.strftime('%Y-%m-%d %H:%M:%S') if t else '[never]'

timestats = '(last run: %s, next run: %s)' % (
format_time(self.last_run), format_time(self.next_run))

if hasattr(self.job_func, '__name__'):
job_func_name = self.job_func.__name__
else:
job_func_name = repr(self.job_func)
args = [repr(x) for x in self.job_func.args]
kwargs = ['%s=%s' % (k, repr(v))
for k, v in self.job_func.keywords.items()]
call_repr = job_func_name + '(' + ', '.join(args + kwargs) + ')'

if self.at_time is not None:
return 'Every %s %s at %s do %s %s' % (
self.interval,
self.unit[:-1] if self.interval == 1 else self.unit,
self.at_time, call_repr, timestats)
else:
return 'Every %s %s do %s %s' % (
self.interval,
self.unit[:-1] if self.interval == 1 else self.unit,
call_repr, timestats)

@property
def second(self):
assert self.interval == 1, 'Use seconds instead of second'
assert self.interval == 1
return self.seconds

@property
Expand All @@ -166,7 +144,7 @@ def seconds(self):

@property
def minute(self):
assert self.interval == 1, 'Use minutes instead of minute'
assert self.interval == 1
return self.minutes

@property
Expand All @@ -176,7 +154,7 @@ def minutes(self):

@property
def hour(self):
assert self.interval == 1, 'Use hours instead of hour'
assert self.interval == 1
return self.hours

@property
Expand All @@ -186,7 +164,7 @@ def hours(self):

@property
def day(self):
assert self.interval == 1, 'Use days instead of day'
assert self.interval == 1
return self.days

@property
Expand All @@ -196,72 +174,72 @@ def days(self):

@property
def week(self):
assert self.interval == 1, 'Use weeks instead of week'
assert self.interval == 1
return self.weeks

@property
def weeks(self):
self.unit = 'weeks'
return self

@property
def monday(self):
assert self.interval == 1, 'Use mondays instead of monday'
assert self.interval == 1
self.start_day = 'monday'
return self.weeks

@property
def tuesday(self):
assert self.interval == 1, 'Use tuesdays instead of tuesday'
assert self.interval == 1
self.start_day = 'tuesday'
return self.weeks

@property
def wednesday(self):
assert self.interval == 1, 'Use wedesdays instead of wednesday'
assert self.interval == 1
self.start_day = 'wednesday'
return self.weeks

@property
def thursday(self):
assert self.interval == 1, 'Use thursday instead of thursday'
assert self.interval == 1
self.start_day = 'thursday'
return self.weeks

@property
def friday(self):
assert self.interval == 1, 'Use fridays instead of friday'
assert self.interval == 1
self.start_day = 'friday'
return self.weeks

@property
def saturday(self):
assert self.interval == 1, 'Use saturdays instead of saturday'
assert self.interval == 1
self.start_day = 'saturday'
return self.weeks

@property
def sunday(self):
assert self.interval == 1, 'Use sundays instead of sunday'
assert self.interval == 1
self.start_day = 'sunday'
return self.weeks

@property
def weeks(self):
self.unit = 'weeks'
return self

def at(self, time_str):
"""Schedule the job every day at a specific time.
Calling this is only valid for jobs scheduled to run every
N day(s).
"""
assert self.unit in ('days', 'hours') or self.start_day
hour, minute = time_str.split(':')
hour, minute = [t for t in time_str.split(':')]
minute = int(minute)
if self.unit == 'days' or self.start_day:
hour = int(hour)
assert 0 <= hour <= 23
elif self.unit == 'hours':
hour = 0
assert 0 <= minute <= 59
self.at_time = datetime.time(hour, minute)
self.at_time = [hour, minute]
return self

def do(self, job_func, *args, **kwargs):
Expand All @@ -285,13 +263,13 @@ def do(self, job_func, *args, **kwargs):
@property
def should_run(self):
"""True if the job should be run now."""
return datetime.datetime.now() >= self.next_run
return now() >= self.next_run

def run(self):
"""Run the job and immediately reschedule it."""
logger.info('Running job %s', self)
ret = self.job_func()
self.last_run = datetime.datetime.now()
self.last_run = now()
self._schedule_next_run()
return ret

Expand All @@ -300,8 +278,41 @@ def _schedule_next_run(self):
# Allow *, ** magic temporarily:
# pylint: disable=W0142
assert self.unit in ('seconds', 'minutes', 'hours', 'days', 'weeks')
self.period = datetime.timedelta(**{self.unit: self.interval})
self.next_run = datetime.datetime.now() + self.period
f = 1
if self.unit == 'minutes':
f = 60
elif self.unit == 'hours':
f = 60 * 60
elif self.unit == 'days':
f = 60 * 60 * 24
elif self.unit == 'weeks':
f = 60 * 60 * 24 * 7
self.period = f * self.interval
self.next_run = now()

if self.at_time is not None:
tm = list(time.localtime(self.next_run))
# Check if the target time may is passed for today
passed_today = (self.period > 60 * 60 * 24)
passed_today = passed_today or(
(self.unit == 'days') and (tm[3] > self.at_time[0]) or (
tm[3] == self.at_time[0] and tm[4] >= self.at_time[1]))
passed_today = passed_today or (
(self.unit == 'hours') and (tm[4] >= self.at_time[1]))

# only force the target hour for days and weeks, not for hours
if self.unit in ('days', 'weeks'):
tm[3] = self.at_time[0]
tm[4] = self.at_time[1]
tm[5] = 0
self.next_run = time.mktime(tuple(tm))

# if the target time is not passed for today then the task may be
# run today
if not passed_today:
self.next_run -= self.period

self.next_run += self.period
if self.start_day is not None:
assert self.unit == 'weeks'
weekdays = (
Expand All @@ -313,34 +324,18 @@ def _schedule_next_run(self):
'saturday',
'sunday'
)
wd = time.localtime(self.next_run)[6]
assert self.start_day in weekdays
weekday = weekdays.index(self.start_day)
days_ahead = weekday - self.next_run.weekday()
days_ahead = weekday - wd
if days_ahead <= 0: # Target day already happened this week
days_ahead += 7
self.next_run += datetime.timedelta(days_ahead) - self.period
if self.at_time is not None:
assert self.unit in ('days', 'hours') or self.start_day is not None
kwargs = {
'minute': self.at_time.minute,
'second': self.at_time.second,
'microsecond': 0
}
if self.unit == 'days' or self.start_day is not None:
kwargs['hour'] = self.at_time.hour
self.next_run = self.next_run.replace(**kwargs)
# If we are running for the first time, make sure we run
# at the specified time *today* (or *this hour*) as well
if not self.last_run:
now = datetime.datetime.now()
if (self.unit == 'days' and self.at_time > now.time() and
self.interval == 1):
self.next_run = self.next_run - datetime.timedelta(days=1)
elif self.unit == 'hours' and self.at_time.minute > now.minute:
self.next_run = self.next_run - datetime.timedelta(hours=1)
self.next_run += days_ahead * 60 * 60 * 24 - self.period

if self.start_day is not None and self.at_time is not None:

# Let's see if we will still make that time we specified today
if (self.next_run - datetime.datetime.now()).days >= 7:
if (self.next_run - now()) >= 7 * 60 * 60 * 24:
self.next_run -= self.period

# The following methods are shortcuts for not having to
Expand Down

0 comments on commit 6340bb2

Please sign in to comment.