Как получить Cron как планировщик в Python?

Я ищу библиотеку в Python, которая предоставит at и cron как функциональность.

Я бы хотел иметь чистое решение Python, а не полагаться на инструменты, установленные на коробке; таким образом, я работаю на машинах без cron.

для тех, кто не знаком с cron: вы можете планировать задачи на основе выражения типа:

 0 2 * * 7 /usr/bin/run-backup # run the backups at 0200 on Every Sunday
 0 9-17/2 * * 1-5 /usr/bin/purge-temps # run the purge temps command, every 2 hours between 9am and 5pm on Mondays to Fridays.

синтаксис выражения времени cron менее важен, но я хотел бы иметь что-то с этим видом гибкости.

если нет чего-то, что делает это для меня из коробки, любые предложения для строительных блоков, чтобы сделать что-то подобное, будут с благодарностью приняты.

редактировать Меня не интересует запуск процессов, просто "задания" также написаны на Python - Python functions. По необходимости я думаю, что это будет другая нить, но не в другом процессе.

С этой целью я ищу выразительность времени cron выражение, но в Python.

Cron и существует уже много лет, но я стараюсь быть максимально портативным. Я не могу полагаться на его присутствие.

20 ответов


Если вы ищете что-то легкий кассе расписание:

import schedule
import time

def job():
    print("I'm working...")

schedule.every(10).minutes.do(job)
schedule.every().hour.do(job)
schedule.every().day.at("10:30").do(job)

while 1:
    schedule.run_pending()
    time.sleep(1)

разоблачение: Я автор этой библиотеки.


вы можете просто использовать обычный синтаксис передачи аргументов Python для указания crontab. Например, предположим, что мы определяем класс событий, как показано ниже:

from datetime import datetime, timedelta
import time

# Some utility classes / functions first
class AllMatch(set):
    """Universal set - match everything"""
    def __contains__(self, item): return True

allMatch = AllMatch()

def conv_to_set(obj):  # Allow single integer to be provided
    if isinstance(obj, (int,long)):
        return set([obj])  # Single item
    if not isinstance(obj, set):
        obj = set(obj)
    return obj

# The actual Event class
class Event(object):
    def __init__(self, action, min=allMatch, hour=allMatch, 
                       day=allMatch, month=allMatch, dow=allMatch, 
                       args=(), kwargs={}):
        self.mins = conv_to_set(min)
        self.hours= conv_to_set(hour)
        self.days = conv_to_set(day)
        self.months = conv_to_set(month)
        self.dow = conv_to_set(dow)
        self.action = action
        self.args = args
        self.kwargs = kwargs

    def matchtime(self, t):
        """Return True if this event should trigger at the specified datetime"""
        return ((t.minute     in self.mins) and
                (t.hour       in self.hours) and
                (t.day        in self.days) and
                (t.month      in self.months) and
                (t.weekday()  in self.dow))

    def check(self, t):
        if self.matchtime(t):
            self.action(*self.args, **self.kwargs)

(Примечание: не тщательно протестированы)

тогда ваш CronTab может быть указан в обычном синтаксисе python как:

c = CronTab(
  Event(perform_backup, 0, 2, dow=6 ),
  Event(purge_temps, 0, range(9,18,2), dow=range(0,5))
)

таким образом, вы получаете полную мощность механики аргументов Python (смешивание позиционных и ключевых args и можете использовать символические имена для имен недель и месяцев)

В Класс CronTab будет определен как просто спящий с шагом в минуту и вызывающий check () для каждого события. (Есть, вероятно, некоторые тонкости с летнее время / часовые пояса, чтобы быть осторожными, хотя). Вот быстрая реализация:

class CronTab(object):
    def __init__(self, *events):
        self.events = events

    def run(self):
        t=datetime(*datetime.now().timetuple()[:5])
        while 1:
            for e in self.events:
                e.check(t)

            t += timedelta(minutes=1)
            while datetime.now() < t:
                time.sleep((t - datetime.now()).seconds)

несколько вещей, чтобы отметить: дни недели / месяцы Python являются нулевыми индексированными (в отличие от cron), и этот диапазон исключает последний элемент, поэтому синтаксис, такой как "1-5", становится диапазоном(0,5) - ie [0,1,2,3,4]. Если вы предпочитаете синтаксис cron, разбор его не должен однако это будет слишком сложно.


может быть, это возникло только после того, как был задан вопрос; я думал, что просто упоминаю об этом для полноты: https://apscheduler.readthedocs.org/en/latest/


проверить сельдерей, у них есть периодические задачи, такие как cron.


"... Crontab модуль для чтения и записи файлов crontab и доступа к системе cron автоматически и просто с помощью прямого API. ..."

http://pypi.python.org/pypi/python-crontab

а также APScheduler, пакет python. Уже написано и отлажено.

http://packages.python.org/APScheduler/cronschedule.html


одна вещь, которую я видел в своих поисках, - это python sched модуль, который может быть то, что вы ищете.


более или менее так же, как и выше, но одновременно с использованием gevent:)

"""Gevent based crontab implementation"""

from datetime import datetime, timedelta
import gevent

# Some utility classes / functions first
def conv_to_set(obj):
    """Converts to set allowing single integer to be provided"""

    if isinstance(obj, (int, long)):
        return set([obj])  # Single item
    if not isinstance(obj, set):
        obj = set(obj)
    return obj

class AllMatch(set):
    """Universal set - match everything"""
    def __contains__(self, item): 
        return True

allMatch = AllMatch()

class Event(object):
    """The Actual Event Class"""

    def __init__(self, action, minute=allMatch, hour=allMatch, 
                       day=allMatch, month=allMatch, daysofweek=allMatch, 
                       args=(), kwargs={}):
        self.mins = conv_to_set(minute)
        self.hours = conv_to_set(hour)
        self.days = conv_to_set(day)
        self.months = conv_to_set(month)
        self.daysofweek = conv_to_set(daysofweek)
        self.action = action
        self.args = args
        self.kwargs = kwargs

    def matchtime(self, t1):
        """Return True if this event should trigger at the specified datetime"""
        return ((t1.minute     in self.mins) and
                (t1.hour       in self.hours) and
                (t1.day        in self.days) and
                (t1.month      in self.months) and
                (t1.weekday()  in self.daysofweek))

    def check(self, t):
        """Check and run action if needed"""

        if self.matchtime(t):
            self.action(*self.args, **self.kwargs)

class CronTab(object):
    """The crontab implementation"""

    def __init__(self, *events):
        self.events = events

    def _check(self):
        """Check all events in separate greenlets"""

        t1 = datetime(*datetime.now().timetuple()[:5])
        for event in self.events:
            gevent.spawn(event.check, t1)

        t1 += timedelta(minutes=1)
        s1 = (t1 - datetime.now()).seconds + 1
        print "Checking again in %s seconds" % s1
        job = gevent.spawn_later(s1, self._check)

    def run(self):
        """Run the cron forever"""

        self._check()
        while True:
            gevent.sleep(60)

import os 
def test_task():
    """Just an example that sends a bell and asd to all terminals"""

    os.system('echo asd | wall')  

cron = CronTab(
  Event(test_task, 22, 1 ),
  Event(test_task, 0, range(9,18,2), daysofweek=range(0,5)),
)
cron.run()

TurboGears корабли с запланированной возможностью задачи основанной на Кронос

Я никогда не использовал Kronos напрямую, но планирование в TG имеет приличный набор функций и является солидным.


Я изменил сценарий.

  1. простота в использовании:

    cron = Cron()
    cron.add('* * * * *'   , minute_task) # every minute
    cron.add('33 * * * *'  , day_task)    # every hour
    cron.add('34 18 * * *' , day_task)    # every day
    cron.run()
    
  2. попробуйте запустить задачу в первую секунду минуты.

код на Github


ни одно из перечисленных решений даже не пытается проанализировать сложную строку расписания cron. Итак, вот моя версия, используя croniter. Основная суть:

schedule = "*/5 * * * *" # Run every five minutes

nextRunTime = getNextCronRunTime(schedule)
while True:
     roundedDownTime = roundDownTime()
     if (roundedDownTime == nextRunTime):
         ####################################
         ### Do your periodic thing here. ###
         ####################################
         nextRunTime = getNextCronRunTime(schedule)
     elif (roundedDownTime > nextRunTime):
         # We missed an execution. Error. Re initialize.
         nextRunTime = getNextCronRunTime(schedule)
     sleepTillTopOfNextMinute()

вспомогательные процедуры:

from croniter import croniter
from datetime import datetime, timedelta

# Round time down to the top of the previous minute
def roundDownTime(dt=None, dateDelta=timedelta(minutes=1)):
    roundTo = dateDelta.total_seconds()
    if dt == None : dt = datetime.now()
    seconds = (dt - dt.min).seconds
    rounding = (seconds+roundTo/2) // roundTo * roundTo
    return dt + timedelta(0,rounding-seconds,-dt.microsecond)

# Get next run time from now, based on schedule specified by cron string
def getNextCronRunTime(schedule):
    return croniter(schedule, datetime.now()).get_next(datetime)

# Sleep till the top of the next minute
def sleepTillTopOfNextMinute():
    t = datetime.utcnow()
    sleeptime = 60 - (t.second + t.microsecond/1000000.0)
    time.sleep(sleeptime)

У меня есть небольшое исправление для CronTab class run метод, предложенный Брайаном.

время было отключено на одну секунду, что привело к односекундной жесткой петле в конце каждой минуты.

class CronTab(object):
    def __init__(self, *events):
        self.events = events

    def run(self):
        t=datetime(*datetime.now().timetuple()[:5])
        while 1:
            for e in self.events:
                e.check(t)

            t += timedelta(minutes=1)
            n = datetime.now()
            while n < t:
                s = (t - n).seconds + 1
                time.sleep(s)
                n = datetime.now()

Проверьте Луиджи (https://github.com/spotify/luigi). Он написан на python и имеет хороший веб-интерфейс для задач мониторинга. Он также имеет график зависимостей. Может быть, это слишком для того, что вам нужно, но это, вероятно, сделает трюк.


нет" чистого python " способ сделать это, потому что какой-то другой процесс должен был бы запустить python для запуска вашего решения. Каждая платформа будет иметь один или двадцать различных способов запуска процессов и контролировать их ход. На платформах unix cron является старым стандартом. В Mac OS X также есть launchd, который сочетает cron-подобный запуск с функциями сторожевого пса, которые могут поддерживать ваш процесс, если это то, что вы хотите. После запуска python вы можете использовать модуль sched для планирования задач.


только в случае, если вы используете Windows, существует pycron. Проверьтеhttp://sourceforge.net/projects/pycron/ . Для linux я буду использовать cron или sched.


Брайан работает довольно хорошо. Однако, как указывали другие, в коде запуска есть тонкая ошибка. Кроме того, я обнаружил, что это слишком сложно для нужд.

вот моя более простая и функциональная альтернатива для кода запуска, если кому-то это нужно:

def run(self):
    while 1:
        t = datetime.now()
        for e in self.events:
            e.check(t)

        time.sleep(60 - t.second - t.microsecond / 1000000.0)

другим тривиальным решением было бы:

from aqcron import At
from time import sleep
from datetime import datetime

# Event scheduling
event_1 = At( second=5 )
event_2 = At( second=[0,20,40] )

while True:
    now = datetime.now()

    # Event check
    if now in event_1: print "event_1"
    if now in event_2: print "event_2"

    sleep(1)

и aqcron класс.На это:

# aqcron.py

class At(object):
    def __init__(self, year=None,    month=None,
                 day=None,     weekday=None,
                 hour=None,    minute=None,
                 second=None):
        loc = locals()
        loc.pop("self")
        self.at = dict((k, v) for k, v in loc.iteritems() if v != None)

    def __contains__(self, now):
        for k in self.at.keys():
            try:
                if not getattr(now, k) in self.at[k]: return False
            except TypeError:
                if self.at[k] != getattr(now, k): return False
        return True

Если вы ищете распределенный планировщик, вы можете проверитьhttps://github.com/sherinkurian/mani - ему нужен redis, хотя это может быть не то, что вы ищете. (обратите внимание, что я автор) это было построено для обеспечения отказоустойчивости за счет запуска часов на нескольких узлах.


Я не знаю, существует ли что-то подобное. Было бы легко написать свой собственный с модулями time, datetime и / или calendar, см. http://docs.python.org/library/time.html

единственная проблема для решения python заключается в том, что ваша работа должна быть всегда запущена и, возможно, автоматически "воскреснет" после перезагрузки, то, для чего вы do нужно полагаться на системные зависимые решения.


Я взял решение Брайана, внес несколько изменений, добавил начало стандартного синтаксического анализатора файлов crontab и поместил его в https://bitbucket.org/dbenamy/devcron.


вы можете проверить [1] кроны PiCloud [2], но обратите внимание, что ваши задания не будут работать на вашей собственной машине. Это также услуга, за которую вам нужно будет заплатить, если вы используете более 20 часов вычислительного времени в месяц.

[1] http://www.picloud.com

[2] http://docs.picloud.com/cron.html