umeboshi
__ __ __ __ __
/ | / | / | / | / |
$$ | $$ | _____ ____ ______ $$ |____ ______ _______ $$ |____ $$/
$$ | $$ |/ \/ \ / \ $$ \ / \ / |$$ \ / |
$$ | $$ |$$$$$$ $$$$ |/$$$$$$ |$$$$$$$ |/$$$$$$ |/$$$$$$$/ $$$$$$$ |$$ |
$$ | $$ |$$ | $$ | $$ |$$ $$ |$$ | $$ |$$ | $$ |$$ \ $$ | $$ |$$ |
$$ \__$$ |$$ | $$ | $$ |$$$$$$$$/ $$ |__$$ |$$ \__$$ | $$$$$$ |$$ | $$ |$$ |
$$ $$/ $$ | $$ | $$ |$$ |$$ $$/ $$ $$/ / $$/ $$ | $$ |$$ |
$$$$$$/ $$/ $$/ $$/ $$$$$$$/ $$$$$$$/ $$$$$$/ $$$$$$$/ $$/ $$/ $$/
Umeboshi is a Django application for durable long-term scheduling of arbitrary actions.
What you need
You need a flexible, generalized way to schedule future computation in your Django app.
Great! You should use celery.
Ah, but: you do use celery, and you love it, but you have it hooked up to a messaging/queuing protocol like AMQP, backed by an in-memory service like RabbitMQ. RabbitMQ is great for dispatching messages that you expect to be consumed some time within the next few minutes. But some of the computation you want to schedule needs to sit on the wire for days---maybe even months---and you don't feel comfortable dumping it into a queue.
Ok, then you should try Umeboshi
Umeboshi is a new, lightweight system for scheduling computation with a higher level of persistence than queue-backed solutions. Umeboshi saves your tasks to the database, and runs them later.
How it works, roughly
Umeboshi contains only two basic concepts: Routines and Events. Routines are classes that you write that do things in a run()
method. Then, in your code, schedule a Routine to be run at a certain time, with certain arguments. Umeboshi will then save an Event to the database with information in it. When the time you specified comes up, Umeboshi will grab your Routine and run it.
An example
Let's say you rent jetskis. You've got a JetSki model in your Django application with a out_for_rent
field. You're highly successful because you charge your customers a one-time \$600 fee if they hold on to your jetskis for more than 30 days. You use Umeboshi to help you enrich yourself on your watersports-loving (but forgetful) clientele.
from umeboshi.routines import routine
@routine
class JetSkiLateFee(object):
# Umeboshi saves its Events to the db with a trigger name that corresponds
# to the Routine to be run when they're ready.
trigger_name = 'ski-late-fee'
def __init__(self, jetski_id, user_id):
# When a Event is scheduled to be run, it is instantiated with the
# arguments it was saved with. In this case it was saved with two
# integer arguments, `jetski_id` and `user_id`.
self.jetski = JetSki.objects.get(pk=jetski_id)
self.user = User.objects.get(pk=user_id)
def check_validity(self):
# Before the Routine logic is run, Umeboshi can check to make sure that
# it's still valid. In this case, we will check that the jetski in
# question is still actually out for rent by that user. (What if they
# returned it and rented it again, you say? Listen, Skily.biz is an MVP.)
return self.jetski.out_for_rent and self.jetski.user == self.user
def run(self):
# Finally, Umeboshi runs the Routine.
self.user.charge(600, currency="USD")
There's your Routine. Now, to schedule it:
def rent_jetski(jetki, user):
jetski.out_for_rent = True
jetski.user = user
jetski.save()
# In your application logic, you run the `schedule` method on your Routine
# class, scheduling the logic for 30 days from now.
JetSkiLateFee.schedule(datetime_scheduled=now() + timedelta(days=30), args=[jetski.id, user.id])
All done. Umeboshi creates an Event whenever rent_jetski()
is run, and 30
days thence that Event will be processed.
Other features
- Event status reflects whether Event is waiting to be run, was run successfully, was cancelled before running (eg if it failed a validity check), or if it failed to run
- Routine scheduling behaviors allow rules like run-once, schedule-once, last-only (cancel any existing Events when one is scheduled)
- Task groups allow scheduling behavior to be applied to multiple Routines within a group
How to install
Install:
pip install django-umeboshi
And add to your INSTALLED_APPS
:
INSTALLED_APPS = [
...
"umeboshi",
...
]
Write your Routines and wrap them in the @routine
decorator.
@routine()
class UsefulRoutine(object):
trigger_name = 'useful-routine'
behavior = TriggerBehavior.SCHEDULE_ONCE
...