Quick Start¶
Calling Your First Function With A Scheduler¶
The first thing that you will need is a driver, which is what allows you to interface with an external timekeeping system that can actually invoke your code.
The simplest driver you can use is the memory driver, in
fritter.drivers.memory
. Getting one is simple enough:
from fritter.drivers.memory import MemoryDriver
driver = MemoryDriver()
Once you have a driver, you can schedule work on it with a Scheduler
, which you can create with
fritter.scheduler.schedulerFromDriver()
.
Let’s begin with a PhysicalScheduler
, which is a scheduler that uses a
float
timestamp to track time and can invoke any 0-argument callable.
from fritter.boundaries import PhysicalScheduler
from fritter.scheduler import schedulerFromDriver
scheduler = schedulerFromDriver(driver)
Now, let’s define some work to do. Again, our scheduler considers any callable object which takes no arguments and returns nothing to be a thing it can schedule for future execution, so we can define a regular function for this.
We’ll make it print out the current time according to the scheduler via its
now
method.
def hello() -> None:
print("hello", scheduler.now())
scheduler.callAt(1.0, hello)
scheduler.callAt(2.0, hello)
scheduler.callAt(3.0, hello)
A memory driver is just an in-memory list of timers, and will never do anything
on its own, so next we will need to tell it to move time forward for us, via
its advance
method.
driver.advance()
From this, you can see 1.0
.
MemoryDriver.advance
,
when given no arguments, will always advance the internal timestamp of the
MemoryDriver
to whatever the time of its next scheduled work is, call any
callables on the way there, then stop. This does not necessarily mean it only
does one bit of work; if two bits of work are scheduled at precisely the same
time, it’ll run them both.
Since its main purpose is for testing, you can also ask the
MemoryDriver
if it has any
more work to do:
print(driver.isScheduled())
This should show you True
, since there is still the work at timestamp 2.0
and 3.0 yet to complete. Therefore this idiom will keep running at maximum
speed, completing all scheduled work immediately, and stopping when it’s done:
while driver.isScheduled():
driver.advance()
This should show us hello 2.0
, and hello 3.0
, as each callable runs,
then time advances to the scheduled time of the next one. You can ask the
driver the time directly with driver.now()
, and indeed, that should show
you 3.0
. Even if no work is scheduled though, you can set the clock by
advancing by a specific interval:
driver.advance(5000)
print(driver.now())
This should show you 5003.0
, as you’ve now advanced 5000 seconds further.
Running In Real Life¶
Of course, the memory driver, while helpful for testing, does not hook up to a
real clock. For that, you’ll need one of the other drivers. Let’s start with
asyncio
:
from asyncio import Future, run
from fritter.boundaries import PhysicalScheduler
from fritter.drivers.asyncio import AsyncioTimeDriver
from fritter.scheduler import schedulerFromDriver
async def example() -> None:
s: PhysicalScheduler = schedulerFromDriver(AsyncioTimeDriver())
f = Future[None]()
def bye() -> None:
f.set_result(None)
start = s.now()
s.callAt(start + 1.5, bye)
await f
end = s.now()
print(f"elapsed={end-start}")
run(example())
When run, this should print out “elapsed=<a number slightly greater than 1.5>
”.
These are all the basics of running basic timed calls with fritter
:
find a driver that works for the framework you’re using; currently, in-memory, asyncio, or twisted (with more to come)
instantiate a
fritter.scheduler.Scheduler
using that driverschedule work to occur at a particular timestamp in that driver’s time-coordinate system using that scheduler with
scheduler.callAt
.