[Pkg-privacy-commits] [onionbalance] 91/117: Remove schedule dependency

Donncha O'Cearbahill donncha-guest at moszumanska.debian.org
Wed Dec 16 23:18:52 UTC 2015


This is an automated email from the git hooks/post-receive script.

donncha-guest pushed a commit to branch debian/sid
in repository onionbalance.

commit 8936c55e2ff8485896f858a43363899418b7b4fd
Author: Donncha O'Cearbhaill <donncha at donncha.is>
Date:   Wed Sep 16 17:39:13 2015 +0200

    Remove schedule dependency
    
    The schedule Python package is not packaged in Debian. It is a single
    file package and I forking it and including the code directly in the
    OnionBalance package.
---
 onionbalance/manager.py  |   2 +-
 onionbalance/schedule.py | 396 +++++++++++++++++++++++++++++++++++++++++++++++
 requirements.txt         |   1 -
 setup.py                 |   1 -
 4 files changed, 397 insertions(+), 3 deletions(-)

diff --git a/onionbalance/manager.py b/onionbalance/manager.py
index 092fe0e..e4e0a89 100644
--- a/onionbalance/manager.py
+++ b/onionbalance/manager.py
@@ -12,12 +12,12 @@ import logging
 # import Crypto.PublicKey
 import stem
 from stem.control import Controller, EventType
-import schedule
 
 from onionbalance import log
 from onionbalance import settings
 from onionbalance import config
 from onionbalance import eventhandler
+from onionbalance import schedule
 
 import onionbalance.service
 import onionbalance.instance
diff --git a/onionbalance/schedule.py b/onionbalance/schedule.py
new file mode 100644
index 0000000..5dde1e4
--- /dev/null
+++ b/onionbalance/schedule.py
@@ -0,0 +1,396 @@
+# -*- coding: utf-8 -*-
+"""
+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.
+
+Inspired by Addam Wiggins' article "Rethinking Cron" [1] and the
+"clockwork" Ruby module [2][3].
+
+Features:
+    - A simple to use API for scheduling jobs.
+    - Very lightweight and no external dependencies.
+    - Excellent test coverage.
+    - Works with Python 2.7 and 3.3
+
+Usage:
+    >>> import schedule
+    >>> import time
+
+    >>> def job(message='stuff'):
+    >>>     print("I'm working on:", message)
+
+    >>> schedule.every(10).minutes.do(job)
+    >>> schedule.every().hour.do(job, message='things')
+    >>> schedule.every().day.at("10:30").do(job)
+
+    >>> while True:
+    >>>     schedule.run_pending()
+    >>>     time.sleep(1)
+
+[1] http://adam.heroku.com/past/2010/4/13/rethinking_cron/
+[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')
+
+
+class CancelJob(object):
+    pass
+
+
+class Scheduler(object):
+    def __init__(self):
+        self.jobs = []
+
+    def run_pending(self):
+        """Run all jobs that are scheduled to run.
+
+        Please note that it is *intended behavior that tick() does not
+        run missed jobs*. For example, if you've registered a job that
+        should run every minute and you only call tick() in one hour
+        increments then your job won't be run 60 times in between but
+        only once.
+        """
+        runnable_jobs = (job for job in self.jobs if job.should_run)
+        for job in sorted(runnable_jobs):
+            self._run_job(job)
+
+    def run_all(self, delay_seconds=0):
+        """Run all jobs regardless if they are scheduled to run or not.
+
+        A delay of `delay` seconds is added between each job. This helps
+        distribute system load generated by the jobs more evenly
+        over time."""
+        logger.info('Running *all* %i jobs with %is delay inbetween',
+                    len(self.jobs), delay_seconds)
+        for job in self.jobs:
+            self._run_job(job)
+            time.sleep(delay_seconds)
+
+    def clear(self):
+        """Deletes all scheduled jobs."""
+        del self.jobs[:]
+
+    def cancel_job(self, job):
+        """Delete a scheduled job."""
+        try:
+            self.jobs.remove(job)
+        except ValueError:
+            pass
+
+    def every(self, interval=1):
+        """Schedule a new periodic job."""
+        job = Job(interval)
+        self.jobs.append(job)
+        return job
+
+    def _run_job(self, job):
+        ret = job.run()
+        if isinstance(ret, CancelJob) or ret is CancelJob:
+            self.cancel_job(job)
+
+    @property
+    def next_run(self):
+        """Datetime when the next job should run."""
+        if not self.jobs:
+            return None
+        return min(self.jobs).next_run
+
+    @property
+    def idle_seconds(self):
+        """Number of seconds until `next_run`."""
+        return (self.next_run - datetime.datetime.now()).total_seconds()
+
+
+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.period = None  # timedelta between runs, only valid for
+        self.start_day = None  # Specific day of the week to start on
+
+    def __lt__(self, other):
+        """PeriodicJobs are sortable based on the scheduled time
+        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
+        return self.seconds
+
+    @property
+    def seconds(self):
+        self.unit = 'seconds'
+        return self
+
+    @property
+    def minute(self):
+        assert self.interval == 1
+        return self.minutes
+
+    @property
+    def minutes(self):
+        self.unit = 'minutes'
+        return self
+
+    @property
+    def hour(self):
+        assert self.interval == 1
+        return self.hours
+
+    @property
+    def hours(self):
+        self.unit = 'hours'
+        return self
+
+    @property
+    def day(self):
+        assert self.interval == 1
+        return self.days
+
+    @property
+    def days(self):
+        self.unit = 'days'
+        return self
+
+    @property
+    def week(self):
+        assert self.interval == 1
+        return self.weeks
+
+    @property
+    def monday(self):
+        assert self.interval == 1
+        self.start_day = 'monday'
+        return self.weeks
+
+    @property
+    def tuesday(self):
+        assert self.interval == 1
+        self.start_day = 'tuesday'
+        return self.weeks
+
+    @property
+    def wednesday(self):
+        assert self.interval == 1
+        self.start_day = 'wednesday'
+        return self.weeks
+
+    @property
+    def thursday(self):
+        assert self.interval == 1
+        self.start_day = 'thursday'
+        return self.weeks
+
+    @property
+    def friday(self):
+        assert self.interval == 1
+        self.start_day = 'friday'
+        return self.weeks
+
+    @property
+    def saturday(self):
+        assert self.interval == 1
+        self.start_day = 'saturday'
+        return self.weeks
+
+    @property
+    def sunday(self):
+        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 = [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)
+        return self
+
+    def do(self, job_func, *args, **kwargs):
+        """Specifies the job_func that should be called every time the
+        job runs.
+
+        Any additional arguments are passed on to job_func when
+        the job runs.
+        """
+        self.job_func = functools.partial(job_func, *args, **kwargs)
+        try:
+            functools.update_wrapper(self.job_func, job_func)
+        except AttributeError:
+            # job_funcs already wrapped by functools.partial won't have
+            # __name__, __module__ or __doc__ and the update_wrapper()
+            # call will fail.
+            pass
+        self._schedule_next_run()
+        return self
+
+    @property
+    def should_run(self):
+        """True if the job should be run now."""
+        return datetime.datetime.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._schedule_next_run()
+        return ret
+
+    def _schedule_next_run(self):
+        """Compute the instant when this job should run next."""
+        # Allow *, ** magic temporarily:
+        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
+        if self.start_day is not None:
+            assert self.unit == 'weeks'
+            weekdays = (
+                'monday',
+                'tuesday',
+                'wednesday',
+                'thursday',
+                'friday',
+                'saturday',
+                'sunday'
+            )
+            assert self.start_day in weekdays
+            weekday = weekdays.index(self.start_day)
+            days_ahead = weekday - self.next_run.weekday()
+            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)
+        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:
+                self.next_run -= self.period
+
+# The following methods are shortcuts for not having to
+# create a Scheduler instance:
+
+default_scheduler = Scheduler()
+jobs = default_scheduler.jobs  # todo: should this be a copy, e.g. jobs()?
+
+
+def every(interval=1):
+    """Schedule a new periodic job."""
+    return default_scheduler.every(interval)
+
+
+def run_pending():
+    """Run all jobs that are scheduled to run.
+
+    Please note that it is *intended behavior that run_pending()
+    does not run missed jobs*. For example, if you've registered a job
+    that should run every minute and you only call run_pending()
+    in one hour increments then your job won't be run 60 times in
+    between but only once.
+    """
+    default_scheduler.run_pending()
+
+
+def run_all(delay_seconds=0):
+    """Run all jobs regardless if they are scheduled to run or not.
+
+    A delay of `delay` seconds is added between each job. This can help
+    to distribute the system load generated by the jobs more evenly over
+    time."""
+    default_scheduler.run_all(delay_seconds=delay_seconds)
+
+
+def clear():
+    """Deletes all scheduled jobs."""
+    default_scheduler.clear()
+
+
+def cancel_job(job):
+    """Delete a scheduled job."""
+    default_scheduler.cancel_job(job)
+
+
+def next_run():
+    """Datetime when the next job should run."""
+    return default_scheduler.next_run
+
+
+def idle_seconds():
+    """Number of seconds until `next_run`."""
+    return default_scheduler.idle_seconds
diff --git a/requirements.txt b/requirements.txt
index 1394418..5313a02 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,5 +1,4 @@
 stem==1.4.1b
 PyYAML==3.11
 pycrypto==2.6.1
-schedule==0.3.1
 future==0.14.3
diff --git a/setup.py b/setup.py
index 19be4ef..15d14a8 100644
--- a/setup.py
+++ b/setup.py
@@ -43,7 +43,6 @@ setup(
         'stem>=1.4.0-dev',
         'PyYAML>=3.11',
         'pycrypto>=2.6.1',
-        'schedule>=0.3.1',
         'future>=0.14.0',
         ],
     tests_require=['tox', 'pytest-mock', 'pytest', 'mock', 'pexpect'],

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-privacy/packages/onionbalance.git



More information about the Pkg-privacy-commits mailing list