Package: krolib
⏰ Krolib: Schedules for HumansMagic library and DSL to handle complex schedules.🚀 InstallationAs easy as usual.$pipinstallkrolibWhy?Cron is not enoughYes, you can create almost any kind of schedule rotation or delay for you app
but there are some very special cases when Cron can't help you, like "do
something monthly, every second weekend at 6am until 2020". Also, you can
create only app-level job, what if you need periodical function execution
inside your application?Declarative schedule formatCreate your schedules programmatically, serialize them, store in DB, modify,
update in runtime and always have a whole picture of what is the final result.Readable and flexible structure like:{'timezone':'Europe/Kiev','start':{'relative_timeshift':{'delay':2,'time_units':TimeUnits.MONTHS,}}}Supports asyncio out of the boxWorks like magic and very helpful to create periodical coroutines:fromkrolib.asyncioimportschedulerfromkrolib.structsimportTimeUnits,PeriodicalUnits@scheduler({'start':{'relative_timeshift':{'delay':1,'time_units':TimeUnits.SECONDS,}},'periodical':{'repeats':PeriodicalUnits.SECONDLY,'every':1,},'stop':{'never':False,'after_num_repeats':2}})asyncdefsome_coroutine():print('PING')awaitsome_coroutine()# will be delayed and called twice!Delay several coroutines concurrently:fromkrolib.asyncioimportschedulerfromkrolib.structsimportTimeUnits,PeriodicalUnits@scheduler({'start':{'relative_timeshift':{'delay':1,'time_units':TimeUnits.SECONDS,}},'periodical':{'repeats':PeriodicalUnits.SECONDLY,'every':1,},'stop':{'never':False,'after_num_repeats':2}})asyncdefsome_coroutine():print('PING')@scheduler({'start':{'relative_timeshift':{'delay':1,'time_units':TimeUnits.SECONDS,}},'periodical':{'repeats':PeriodicalUnits.SECONDLY,'every':1,},'stop':{'never':False,'after_num_repeats':2}})asyncdefanother_coroutine():print('PONG')# concurrent execution, get your PING PONG twice!awaitasyncio.gather(some_coroutine(),another_coroutine())More examplesDelay for 1 hour:fromkrolib.utilsimportjust_nowfromkrolib.parserimportschedule_parserfromkrolib.structsimportTimeUnitsnow=just_now()schedule={'start':{'relative_timeshift':{'delay':1,'time_units':TimeUnits.HOURS,}},}schedule_gen=schedule_parser(schedule)result=next(schedule_gen)assert(result-now).total_seconds()==3600# secondsEvery last day of the month, infinite:importdatetimeimportpytzfromkrolib.structsimport(PeriodicalUnits,RelativeUnits,RelativeIndexUnits,)fromkrolib.parserimportschedule_parser# today is this date, for examplenow=datetime.datetime(year=2018,month=1,day=1)schedule={'periodical':{'repeats':PeriodicalUnits.MONTHLY,'relative_day':RelativeUnits.DAY,'relative_day_index':RelativeIndexUnits.LAST,}}schedule_gen=schedule_parser(schedule,now_dt=now)results=[next(schedule_gen)for_inrange(3)]assertresults==[datetime.datetime(2018,1,31,0,0,tzinfo=pytz.UTC),datetime.datetime(2018,2,28,0,0,tzinfo=pytz.UTC),datetime.datetime(2018,3,31,0,0,tzinfo=pytz.UTC),]Since tomorrow, every week with stop after the second one:importdatetimefromkrolib.utilsimportjust_nowfromkrolib.parserimportschedule_parserfromkrolib.structsimportPeriodicalUnitsnow=just_now()start_on=now+datetime.timedelta(days=1)schedule={'start':{'on':start_on,},'periodical':{'repeats':PeriodicalUnits.WEEKLY,'every':1,},'stop':{'never':False,'after_num_repeats':2}}schedule_gen=schedule_parser(schedule)one_dt,two_dt=list(schedule_gen)assertone_dt==start_onassert(two_dt-one_dt).days==7Schedule FormatThe general schema structure which is currently supported by Krolib is
presented here as a pseudo-Python code.{'start':{'on':datetime.datetime,'relative_timeshift':{'delay':int,'time_units':'seconds/minutes/hours/days/weeks/months',}},'periodical':{'repeats':'yearly/monthly/weekly/daily/hourly/minutely/secondly','every':int,'month':1..12orNone,'day':1..31orNone,'weekday':[0..6]orNone,'hour':0..23orNone,'minute':0..59orNone,'second':0..59orNone,},'stop':{'never':TrueorFalse,'on':datetime.datetime,'after_num_repeats':int},'timezone':strorNone,}Also, there is special form for relative periodical rotations:{'start':{'on':datetime.datetime,'relative_timeshift':{'delay':int,'time_units':'seconds/minutes/hours/days/weeks/months',}},'periodical':{'repeats':'yearly/monthly','relative_day':'day/weekday/weekend/sunday/monday/tuesday/wednesday/thursday/friday/saturday','relative_day_index':'first/second/third/fourth/last','every':int,'hour':0..23orNone,'minute':0..59orNone,'second':0..59orNone,},'stop':{'never':TrueorFalse,'on':datetime.datetime,'after_num_repeats':int},'timezone':strorNone,}Schedule structureThe schedule data structure has three main sections:startperiodicalstopThe schedule structure also holds atimezoneattribute, which sets the
timezone for all the sections of the schedule. It should hold a string with the
timezone identifier, for exampleEurope/Kiev. Supported list of the timezones
could be foundherePlease take into account, that timezone validation and processing utilizespytzlibrary, so finally you might want to dig into the sources of this
library in case if you face any troubles.Start sectionStart section is used to specify the exact time or delay before the rule or
block starts/continues it's execution (processing).onkeyThis key holds the exact time (datetime object) when to start the
execution/processing. When this key is set, values in therelative_timeshiftsection should not be filled with any values.{'start':{'on':datetime.datetime.utcnow(),}}relative_timeshiftkeyThis key holds an object with two keys —delayandtime_units.delayholds a string with an integer in it for the amount of time units.time_unitsholds a string with one of the values:secondsminuteshoursdaysweeksmonthsWhen the relative timeshift is set, both delay and time_units should hold the
proper values.Example of this kind of start section with the delay for 3 days:{'start':{'relative_timeshift':{'delay':'3','time_units':'days',}}}Periodical sectionThe periodical section is used to describe a schedule with the recurring
events. If you use this section, you should also set the values in the stop
section of the schedule data structure.repeatskeyThis key holds the type of repeats:yearlymonthlyweeklydailyhourlyminuletysecondlyExample of yearly repeats on September, 3-rd at 20:30:{'periodical':{'repeats':'yearly','every':1,'month':9,'day':3,'hour':20,'minute':30,}}everykeyThis key gives additional flexibility for repeats. It holds a non-negative
integer greater than zero (a natural number) and is used to calculate skips.For example, for daily repeats the every key could be set to 1 — this would
mean every day repeats, to 3 — this would mean skip two days, then execute
every third day, skip two days again, etc.{'periodical':{'repeats':'daily','every':5,'hour':20,'minute':30,}}month,day,hour,minuteandsecondkeysThese keys hold integers which correspond to appropriate key. For example, 1-12
(inclusive) for month, 1-31 (inclusive) for day and so on.weekdaykeyThis key holds the information about the weekdays for the recurring event.
Valid weekday range is from 0 to 6 where 0 is Monday and 6 is Sunday.Example of weekly repeats (every week without skipping) on Mondays and Fridays
at 15.00:{'periodical':{'repeats':'weekly','every':1,'weekday':[0,4],'hour':15,'minute':0,}}Relative days caseFormonthlyandyearlyvalues of therepeatskey, altenative relative
days schedule could be set.In this case thedayandweekdaykeys should not be set andrelative_dayandrelative_day_indexshould be. These keys indicate some relative day of
the month.relative_daykeyThis field should hold the string with one of the following values:dayweekdayweekendsundaymondaytuesdaywednesdaythursdayfridaysaturdayrelative_day_indexkeyThis field should hold a string with one of the following values:firstsecondthirdfourthlastExample of monthly repeats on the third Friday of the month at 15.00:{'periodical':{'repeats':'monthly','every':1,'hour':15,'minute':0,'relative_day':'friday','relative_day_index':'third'}}Stop sectionThis section is obligatory. It holds information about when to stop the
recurring event execution. Hasnever,onandafter_num_repeatskeys.neverkeyThis key holds a boolean value. If set toTrue— other keys of this section
should not hold any values, because this means that recurring event will never
stop it's execution. If set toFalse— other keys should be also set.Stop section with never ending repeats:{'stop':{'never':True,}}onkeyThis key holds exact time (datetime object) when to stop the recurring:{'stop':{'never':False,'on':datetime.datetime.utcnow()+datetime.timedelta(days=1)}}after_num_repeatskeyThis key holds an integer indicating amount of times to repeat the recurring
schedule.Stop section with stop after 25 times executing/processing smth:{'stop':{'never':False,'after_num_repeats':25}}🤝 Special ThanksAlexander Omyshev@akalexDmitry Nikonenko@ndmytroPhil Steitz@psteitzRalph Goers@rgoersVitalii Iaskal@vavilon17📝 LicensePublished under Apache Software License 2.0, seeLICENSEfile.