I'm looking for a library in Python which will provide at and cron like functionality.
I'd quite like have a pure Python solution, rather than relying on tools installed on the box; this way I run on machines with no cron.
For those unfamiliar with cron: you can schedule tasks based upon an expression like:
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.
The cron time expression syntax is less important, but I would like to have something with this sort of flexibility.
If there isn't something that does this for me out-the-box, any suggestions for the building blocks to make something like this would be gratefully received.
Edit
I'm not interested in launching processes, just "jobs" also written in Python - python functions. By necessity I think this would be a different thread, but not in a different process.
To this end, I'm looking for the expressivity of the cron time expression, but in Python.
Cron has been around for years, but I'm trying to be as portable as possible. I cannot rely on its presence.
If you're looking for something lightweight 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)
Disclosure: I'm the author of that library.
You could just use normal Python argument passing syntax to specify your crontab. For example, suppose we define an Event class as below:
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)
(Note: Not thoroughly tested)
Then your CronTab can be specified in normal python syntax as:
c = CronTab(
Event(perform_backup, 0, 2, dow=6 ),
Event(purge_temps, 0, range(9,18,2), dow=range(0,5))
)
This way you get the full power of Python's argument mechanics (mixing positional and keyword args, and can use symbolic names for names of weeks and months)
The CronTab class would be defined as simply sleeping in minute increments, and calling check() on each event. (There are probably some subtleties with daylight savings time / timezones to be wary of though). Here's a quick implementation:
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)
A few things to note: Python's weekdays / months are zero indexed (unlike cron), and that range excludes the last element, hence syntax like "1-5" becomes range(0,5) - ie [0,1,2,3,4]. If you prefer cron syntax, parsing it shouldn't be too difficult however.
More or less same as above but concurrent using 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()
None of the listed solutions even attempt to parse a complex cron schedule string. So, here is my version, using croniter. Basic gist:
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()
Helper routines:
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)
I know there are a lot of answers, but another solution could be to go with decorators. This is an example to repeat a function everyday at a specific time. The cool think about using this way is that you only need to add the Syntactic Sugar to the function you want to schedule:
#repeatEveryDay(hour=6, minutes=30)
def sayHello(name):
print(f"Hello {name}")
sayHello("Bob") # Now this function will be invoked every day at 6.30 a.m
And the decorator will look like:
def repeatEveryDay(hour, minutes=0, seconds=0):
"""
Decorator that will run the decorated function everyday at that hour, minutes and seconds.
:param hour: 0-24
:param minutes: 0-60 (Optional)
:param seconds: 0-60 (Optional)
"""
def decoratorRepeat(func):
#functools.wraps(func)
def wrapperRepeat(*args, **kwargs):
def getLocalTime():
return datetime.datetime.fromtimestamp(time.mktime(time.localtime()))
# Get the datetime of the first function call
td = datetime.timedelta(seconds=15)
if wrapperRepeat.nextSent == None:
now = getLocalTime()
wrapperRepeat.nextSent = datetime.datetime(now.year, now.month, now.day, hour, minutes, seconds)
if wrapperRepeat.nextSent < now:
wrapperRepeat.nextSent += td
# Waiting till next day
while getLocalTime() < wrapperRepeat.nextSent:
time.sleep(1)
# Call the function
func(*args, **kwargs)
# Get the datetime of the next function call
wrapperRepeat.nextSent += td
wrapperRepeat(*args, **kwargs)
wrapperRepeat.nextSent = None
return wrapperRepeat
return decoratorRepeat
I like how the pycron package solves this problem.
import pycron
import time
while True:
if pycron.is_now('0 2 * * 0'): # True Every Sunday at 02:00
print('running backup')
time.sleep(60) # The process should take at least 60 sec
# to avoid running twice in one minute
else:
time.sleep(15) # Check again in 15 seconds
There isn't a "pure python" way to do this because some other process would have to launch python in order to run your solution. Every platform will have one or twenty different ways to launch processes and monitor their progress. On unix platforms, cron is the old standard. On Mac OS X there is also launchd, which combines cron-like launching with watchdog functionality that can keep your process alive if that's what you want. Once python is running, then you can use the sched module to schedule tasks.
Another trivial solution would be:
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)
And the class 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
I don't know if something like that already exists. It would be easy to write your own with time, datetime and/or calendar modules, see http://docs.python.org/library/time.html
The only concern for a python solution is that your job needs to be always running and possibly be automatically "resurrected" after a reboot, something for which you do need to rely on system dependent solutions.
I've created a function who makes a loading bar in the terminal window. It looks like this:
def loadingBar(length, time):
void = '-'
fill = '#'
count = 100/length
increaseCount = 0
sleepTime = time/20
for i in range(length):
print('['+(fill*i)+(void*(length-i))+'] '+str(int(increaseCount))+'%',end='\r')
increaseCount += count
time.sleep(sleepTime)
print('['+(fill*(i+1))+(void*(length-(i+1)))+'] '+str(int(increaseCount))+'%',end='\n')
I want to customize the sleep time with the variable “sleepTime”, but I have an error who says :
AttributeError: 'float' object has no attribute 'sleep'
I don't understand because the variable “time” and the variable “sleepTime” are float!
Note : I'm not very good at English.
Currently, the parameter time has the same name as the module time.
Change:
def loadingBar(length, time):
To:
def loadingBar(length, time1):
and
sleepTime = time/20
to
sleepTime = time1/20
This will work:
def loadingBar(length, time1):
void = '-'
fill = '#'
count = 100/length
increaseCount = 0
sleepTime = time1/20
for i in range(length):
print('['+(fill*i)+(void*(length-i))+'] '+str(int(increaseCount))+'%',end='\r')
increaseCount += count
time.sleep(sleepTime)
print('['+(fill*(i+1))+(void*(length-(i+1)))+'] '+str(int(increaseCount))+'%',end='\n')
I have a list of ~300K URLs for an API i need to get data from.
The API limit is 100 calls per second.
I have made a class for the asynchronous but this is working to fast and I am hitting an error on the API.
How do I slow down the asynchronous, so that I can make 100 calls per second?
import grequests
lst = ['url.com','url2.com']
class Test:
def __init__(self):
self.urls = lst
def exception(self, request, exception):
print ("Problem: {}: {}".format(request.url, exception))
def async(self):
return grequests.map((grequests.get(u) for u in self.urls), exception_handler=self.exception, size=5)
def collate_responses(self, results):
return [x.text for x in results]
test = Test()
#here we collect the results returned by the async function
results = test.async()
response_text = test.collate_responses(results)
The first step that I took was to create an object who can distribute a maximum of n coins every t ms.
import time
class CoinsDistribution:
"""Object that distribute a maximum of maxCoins every timeLimit ms"""
def __init__(self, maxCoins, timeLimit):
self.maxCoins = maxCoins
self.timeLimit = timeLimit
self.coin = maxCoins
self.time = time.perf_counter()
def getCoin(self):
if self.coin <= 0 and not self.restock():
return False
self.coin -= 1
return True
def restock(self):
t = time.perf_counter()
if (t - self.time) * 1000 < self.timeLimit:
return False
self.coin = self.maxCoins
self.time = t
return True
Now we need a way of forcing function to only get called if they can get a coin.
To do that we can write a decorator function that we could use like that:
#limitCalls(callLimit=1, timeLimit=1000)
def uniqFunctionRequestingServer1():
return 'response from s1'
But sometimes, multiple functions are calling requesting the same server so we would want them to get coins from the the same CoinsDistribution object.
Therefor, another use of the decorator would be by supplying the CoinsDistribution object:
server_2_limit = CoinsDistribution(3, 1000)
#limitCalls(server_2_limit)
def sendRequestToServer2():
return 'it worked !!'
#limitCalls(server_2_limit)
def sendAnOtherRequestToServer2():
return 'it worked too !!'
We now have to create the decorator, it can take either a CoinsDistribution object or enough data to create a new one.
import functools
def limitCalls(obj=None, *, callLimit=100, timeLimit=1000):
if obj is None:
obj = CoinsDistribution(callLimit, timeLimit)
def limit_decorator(func):
#functools.wraps(func)
def limit_wrapper(*args, **kwargs):
if obj.getCoin():
return func(*args, **kwargs)
return 'limit reached, please wait'
return limit_wrapper
return limit_decorator
And it's done ! Now you can limit the number of calls any API that you use and you can build a dictionary to keep track of your CoinsDistribution objects if you have to manage a lot of them (to differrent API endpoints or to different APIs).
Note: Here I have choosen to return an error message if there are no coins available. You should adapt this behaviour to your needs.
You can just keep track of how much time has passed and decide if you want to do more requests or not.
This will print 100 numbers per second, for example:
from datetime import datetime
import time
start = datetime.now()
time.sleep(1);
counter = 0
while (True):
end = datetime.now()
s = (end-start).seconds
if (counter >= 100):
if (s <= 1):
time.sleep(1) # You can keep track of the time and sleep less, actually
start = datetime.now()
counter = 0
print(counter)
counter += 1
This other question in SO shows exactly how to do this. By the way, what you need is usually called throttling.
I am trying to make an elevator simulation because of an interesting problem I saw on CareerCup. My problem is that I want the elevator to "take time" to move from one floor to another. Right now it just instantly moves to the next floor in its "to visit" list. I'm not sure how to program it so that "pickup requests" can be coming in while the elevator is moving. I think this may require threading, and the time.sleep() function. How do I make one thread that makes random requests to the elevator, and another thread that has the elevator trying to meet all of the requests? This is what I have so far:
import time
from random import *
import math
class Elevator:
def __init__(self, num_floors):
self.going_up = False
self.going_down = False
self.docked = True
self.curr_floor = 0
self.num_floors = num_floors
self.floors_to_visit = []
self.people_waiting = []
def print_curr_status(self):
for i in range(self.num_floors):
if i == self.curr_floor:
print('. []')
else:
print('.')
print ("to_visit: ", self.floors_to_visit)
def handle_call_request(self, person):
if not self.going_up and not self.going_down:
self.floors_to_visit = [person.curr_floor] + self.floors_to_visit
self.going_up = True
self.docked = False
self.people_waiting.append(person)
else:
self.floors_to_visit.append(person.curr_floor)
self.people_waiting.append(person)
def handle_input_request(self, floor_num):
self.floors_to_visit.append(floor_num)
def go_to_next(self):
if not self.floors_to_visit:
self.print_curr_status()
return
self.curr_floor = self.floors_to_visit.pop(0)
for i,person in enumerate(self.people_waiting):
if person.curr_floor == self.curr_floor:
person.riding = True
person.press_floor_num()
self.people_waiting.pop(i)
return
class Person:
def __init__(self, assigned_elevator, curr_floor):
self.curr_floor = curr_floor
self.desired_floor = math.floor(random() * 10)
self.assigned_elevator = assigned_elevator
self.riding = False
def print_floor(self):
print(self.desired_floor)
def call_elevator(self):
self.assigned_elevator.handle_call_request(self)
def press_floor_num(self):
self.assigned_elevator.handle_input_request(self.desired_floor)
my_elevator = Elevator(20)
while True:
for i in range(3):
some_person = Person(my_elevator, math.floor(random() * 10))
some_person.call_elevator()
my_elevator.go_to_next()
my_elevator.print_curr_status()
time.sleep(1)
No threding is neccessary. You can introduce 2 new variables: one keeping track on the time the elevator started and one for the time an elevator ride should take. Then just just check when the elevator has run long enough. You can do this calling the function time.time(); it'll return the time in seconds since January 1, 1970 (since you're only interested in the difference it doesn't matter; you just need a function that increment in time). Although, this function often can't give a more accurate time period than 1 second. If you feel it's to inaccurate on your machine then you could use datetime.
class Elevator:
def __init__(self, num_floors):
self.start_time = 0
self.ride_duration = 1
...
def call_elevator(self):
self.start_time = time.time()
self.assigned_elevator.handle_call_request(self)
def go_to_next(self):
if time.time() - self.start_time < self.ride_duration:
return # Do nothing.
else:
...
You'll probably need to refactor the code to suit your needs and add some logic on what to do when the elevator is in use, etc.
My intention is to have two music tracks, which are similar in nature, fade between each other at various times. When such a fade occurs, one music track should fade from full volume to muted in a short period of time, and, simultaneously, the other track should fade from 0 to 100 and continue playing from the same time index. They must be able to do this dynamically at any time - when a certain action occurs, the fade will occur and the new track will start playing at the same position that the other one left off at.
This might be plausible by either using volume manipulation or by starting and stopping the music (however, it appears that only a "fadeout" option exists, and there is a lack of a "fadein" option). How can I do this? What is the best method, if any, that exists? If it is impossible using Pygame, alternatives to Pygame are acceptable.
This isn't exactly an answer to the question, but for future-googlers I wrote a script to fade-in my music from volume 0 in the morning and this is what I used:
max_volume = 40
current_volume = 0
# set the volume to the given percent using amixer
def set_volume_to(percent):
subprocess.call(["amixer", "-D", "pulse", "sset", "Master",
str(percent) + "%", "stdout=devnull"])
# play the song and fade in the song to the max_volume
def play_song(song_file):
global current_volume
print("Song starting: " + song_file)
pygame.mixer.music.load(song_file)
pygame.mixer.music.play()
# gradually increase volume to max
while pygame.mixer.music.get_busy():
if current_volume < max_volume:
set_volume_to(current_volume)
current_volume += 1
pygame.time.Clock().tick(1)
play_song("foo.mp3")
Try this, it's pretty straight forward..
import pygame
pygame.mixer.init()
pygame.init()
# Maybe you can subclass the pygame.mixer.Sound and
# add the methods below to it..
class Fader(object):
instances = []
def __init__(self, fname):
super(Fader, self).__init__()
assert isinstance(fname, basestring)
self.sound = pygame.mixer.Sound(fname)
self.increment = 0.01 # tweak for speed of effect!!
self.next_vol = 1 # fade to 100 on start
Fader.instances.append(self)
def fade_to(self, new_vol):
# you could change the increment here based on something..
self.next_vol = new_vol
#classmethod
def update(cls):
for inst in cls.instances:
curr_volume = inst.sound.get_volume()
# print inst, curr_volume, inst.next_vol
if inst.next_vol > curr_volume:
inst.sound.set_volume(curr_volume + inst.increment)
elif inst.next_vol < curr_volume:
inst.sound.set_volume(curr_volume - inst.increment)
sound1 = Fader("1.wav")
sound2 = Fader("2.wav")
sound1.sound.play()
sound2.sound.play()
sound2.sound.set_volume(0)
# fading..
sound1.fade_to(0)
sound2.fade_to(1)
while True:
Fader.update() # a call that will update all the faders..
Pseudocode:
track1 = ...
track2 = ...
track1.play_forever()
track1.volume = 100
track2.play_forever()
track2.volume = 0
playing = track1
tracks = [track1, track2]
def volume_switcher():
while True:
playing.volume = min(playing.volume + 1, 100)
for track in tracks:
if track != playing:
track.volume = max(track.volume - 1, 100)
time.sleep(0.1)
Thread(target=volume_switcher).start()
So it looks like what you want to do in pygame is create two 'Sound' objects, and create a linear interpolation on the volume between the two of them.
I would create two vectors, each from [0,100], and relate them inversely with some constant.
So when sound A is at 100, sound b is at 0. Then when an action occurs, you modify the constant.
t=0
A: [0 ... 100]
B: [0 ... 100]
t=1
ACTION
t=1.1
A:[0 .. 50 .. 100]
B:[0 .. 50 .. 100]
t=2
A:[0 ... 100]
B:[0 ... 100]
Now some code. I'm not familiar with pygame, but this should put you on the right track.
class Song(object):
def __init__(self, songfilename):
self.song = pygame.mixer.Sound(songfilename)
def setVolume(self, somenumber):
#number validation
#possibly do some volume curve here if you wanted
self.song.set_volume(somenumber)
class SongFader(object):
def __init__(self, song1, song2):
self.song1 = song1
self.song2 = song2
self.__xAxisMax = 100
self.__xAxisMin = 0
def fade(self, xaxis):
assert(self.__xAxisMin <= xaxis <= self.__xAxisMax)
#could be any numbers you want.
#i chose 0-100 for convenience
self.song1.setVolume(xaxis)
self.song2.setVolume(self.__xAxisMax-xaxis)
song1 = Song('Song1.wav')
song2 = Song('Song2.wav')
fader = SongFader(song1, song2)
#Inside some event loop when you action is triggered
fader.fade(100) #Only song2 is playing
fader.fade(50) #Songs are evenly split
fader.fade(0) #Only left song is playing
edit
The linear interpolation is probably the more important concept here, so i have modified the fader class, with inspiration from Eric 's thread idea.
class SongFader(object):
def __init__(self, song1, song2):
self.song1 = song1
self.song2 = song2
self.lefttoright = False
self.starttime = 0
self.endtime = 0
def fade(self, starttime, fadeleft):
self.lefttoright = fadeleft == True #Being verbose here
self.starttime = starttime #assuming time is in millis
self.endtime = starttime + 1000
Thread(target = self.fadeHelper).start()
#this is where you define how the two songs are faded
def fadeHelper(self):
#if using thread, make sure you mutex the 'self.' variables
starttime = self.starttime
endtime = self.endtime
lefttoright = self.lefttoright
while starttime < endtime:
fadevalue = (starttime - endtime) / 1000 #a val between [0,1]
if lefttoright:
self.song1.setVolume(fadevalue)
self.song2.setVolume(1-fadevalue)
else:
self.song1.setVolume(1-fadevalue)
self.song2.setVolume(fadefalue)
starttime = getGameTimeFromSomewhere()