Isn't it quite unfriendly to make the caller implement the main loop, and require periodic polling of the scheduler in order to run jobs? When I have implemented things like this before, I kept tuples of (time_of_next_run, job) in a heap. Then the scheduler had a loop like:
while True:
now = time.time()
while job_heap[0][0] < now:
time, job = heapq.heappop(job_heap)
job.run()
heapq.heappush(job_heap, (job.next_time(), job))
time.sleep(job_heap[0][0] - time.time())
This reduces the amount of polling needed, since you sleep until the next job is ready.