¿Cómo obtengo un Cron como scheduler en Python?


Estoy buscando una biblioteca en Python que proporcione at y cron funcionalidad similar.

Me gustaría tener una solución pura de Python, en lugar de depender de las herramientas instaladas en la caja; de esta manera, corro en máquinas sin cron.

Para aquellos que no están familiarizados con cron: puede programar tareas basadas en una expresión como:

 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.

La sintaxis de la expresión cron time es menos importante, pero me gustaría tener algo con este tipo de flexibilidad.

Si no hay algo que haga esto para mí fuera de la caja, cualquier sugerencia para los bloques de construcción para hacer algo como esto sería recibido con gratitud.

Editar No estoy interesado en lanzar procesos, solo "trabajos" también escritos en Python-funciones python. Por necesidad creo que esto sería un hilo diferente, pero no en un proceso diferente.

Para este fin, estoy buscando la expresividad de la expresión de tiempo cron, pero en Python.

Cron ha existido durante años, pero estoy tratando de ser lo más portátil posible. No puedo confiar en su presencia.

Author: Damjan Pavlica, 2008-12-17

20 answers

Si estás buscando algo ligero checkout schedule :

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)

Divulgación : Soy el autor de esa biblioteca.

 369
Author: dbader,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-04-23 21:46:55

Simplemente podría usar la sintaxis normal de paso de argumentos de Python para especificar su crontab. Por ejemplo, supongamos que definimos una clase de evento de la siguiente manera:

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)

(Nota: No probado a fondo)

Entonces su CronTab se puede especificar en la sintaxis normal de python como:

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

De esta manera se obtiene toda la potencia de la mecánica argumental de Python (mezclando argumentos posicionales y de palabras clave, y se pueden usar nombres simbólicos para nombres de semanas y meses)

La clase CronTab se definiría como simplemente durmiendo en incrementos de minutos, y llamando a check () en cada evento. (Probablemente hay algunas sutilezas con el horario de verano / zonas horarias a tener cuidado). Aquí hay una implementación rápida:

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)

Algunas cosas a tener en cuenta: Los días de la semana / meses de Python están indexados a cero (a diferencia de cron), y ese rango excluye el último elemento, por lo tanto una sintaxis como "1-5" se convierte en rango(0,5) - ie [0,1,2,3,4]. Si prefiere la sintaxis cron, sin embargo, analizarla no debería ser demasiado difícil.

 58
Author: Brian,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-05-11 17:09:15

Tal vez esto ha surgido solo después de que se hizo la pregunta; pensé que solo lo mencionaba para completar: https://apscheduler.readthedocs.org/en/latest /

 39
Author: ssc,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-02-07 02:08:01

Echa un vistazo Apio, tienen tareas periódicas como cron.

 27
Author: Vishal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-09-02 10:59:40

"... Módulo Crontab para leer y escribir archivos crontab y acceder al cron del sistema automáticamente y simplemente usando una API directa. ..."

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

Y también APScheduler, un paquete python. Ya está escrito y depurado.

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

 18
Author: bootload,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-12-12 02:51:19

Una cosa que he visto en mis búsquedas es la de Python sched módulo que podría ser el tipo de cosa que estás buscando.

 14
Author: Sean,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-12-17 01:45:17

Más o menos igual que arriba pero concurrente usando 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()
 10
Author: Hackeron,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-06-01 02:01:21

TurboGears se envía con capacidad de tarea programada basada en Kronos

Nunca he usado Kronos directamente, pero la programación en TG tiene un conjunto decente de características y es sólida.

 9
Author: James Brady,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-12-17 06:23:28

He modificado el script.

  1. Fácil de usar:

    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. Trate de iniciar la tarea en el primer segundo de un minuto.

Código en Github

 7
Author: ning,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-08-02 20:54:31

Ninguna de las soluciones listadas intenta siquiera analizar una cadena de programación cron compleja. Entonces, aquí está mi versión, usando croniter . Síntesis básica:

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()

Rutinas de ayuda:

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)
 7
Author: rouble,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-07-03 15:42:16

Tengo una corrección menor para el método de ejecución de la clase CronTab sugerido por Brian.

El tiempo se agotó por un segundo, lo que llevó a un bucle duro de un segundo al final de cada minuto.

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()
 6
Author: benc,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:18:24

Echa un vistazo a luigi ( https://github.com/spotify/luigi ). Está escrito en python y tiene una interfaz de usuario web agradable para tareas de monitoreo. También tiene un gráfico de dependencias. Podría ser exagerado para lo que necesita, pero probablemente hará el truco.

 5
Author: amwinter,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-09-30 16:57:26

No hay una forma "pura de python" de hacer esto porque algún otro proceso tendría que lanzar python para ejecutar su solución. Cada plataforma tendrá una o veinte formas diferentes de lanzar procesos y monitorear su progreso. En plataformas unix, cron es el viejo estándar. En Mac OS X también está launchd, que combina el lanzamiento similar a cron con la funcionalidad watchdog que puede mantener vivo su proceso si eso es lo que desea. Una vez que python se está ejecutando, entonces puede utilizar el sched módulo para programar tareas.

 3
Author: Nick,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-12-17 05:45:01

Por si acaso, si estás usando windows, existe un pycron. Echa un vistazo http://sourceforge.net/projects/pycron/ . Para Linux, voy a ir por cron o sched.

 1
Author: JV.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-12-17 05:49:29

La solución de Brian está funcionando bastante bien. Sin embargo, como otros han señalado, hay un error sutil en el código de ejecución. También me pareció demasiado complicado para las necesidades.

Aquí está mi alternativa más simple y funcional para el código de ejecución en caso de que alguien lo necesite:

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)
 1
Author: raph.amiard,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:47:26

Otra solución trivial sería:

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)

Y la clase aqcron.At is:

# 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
 1
Author: fdb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-09-10 22:51:04

Si está buscando un programador distribuido, puede consultar https://github.com/sherinkurian/mani - aunque sí necesita redis, puede que no sea lo que estás buscando. (tenga en cuenta que soy el autor) esto fue construido para garantizar la tolerancia a errores al tener reloj ejecutado en más de un nodo.

 1
Author: shrnkrn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-19 16:53:01

No se si algo así ya existe. Sería fácil escribir el suyo propio con módulos time, datetime y/o calendar, ver http://docs.python.org/library/time.html

La única preocupación para una solución de python es que su trabajo debe estar siempre en ejecución y posiblemente ser "resucitado" automáticamente después de un reinicio, algo para lo que hace necesita confiar en soluciones dependientes del sistema.

 0
Author: Davide,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-12-17 01:01:33

Tomé la solución de Brian, hice algunos cambios, agregué los inicios de un analizador de archivos crontab estándar, y lo puse en https://bitbucket.org/dbenamy/devcron .

 0
Author: Dan Benamy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-12-24 10:43:54

Puede consultar los Crones de PiCloud [1] [2], pero tenga en cuenta que sus trabajos no se ejecutarán en su propia máquina. También es un servicio que tendrá que pagar si utiliza más de 20 horas de tiempo de cómputo al mes.

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

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

 0
Author: BrainCore,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-04-25 00:23:47