"""Implementation of essential functions for a `BaseJob`.Author: Jendrik A. Potyka, Fabian A. Preiss"""from__future__importannotationsimportdatetimeasdtfromtypingimportOptional,castimporttypeguardastgfromscheduler.base.definitionimportJOB_TIMING_TYPE_MAPPING,JobTypefromscheduler.base.job_timerimportJobTimerfromscheduler.base.timingtypeimportTimingJobUnionfromscheduler.errorimportSchedulerErrorfromscheduler.messageimport(_TZ_ERROR_MSG,DUPLICATE_EFFECTIVE_TIME,START_STOP_ERROR,TZ_ERROR_MSG,)fromscheduler.trigger.coreimportWeekdayfromscheduler.utilimportare_times_unique,are_weekday_times_unique
[docs]defprettify_timedelta(timedelta:dt.timedelta)->str:""" Humanize timedelta string readability for negative values. Parameters ---------- timedelta : datetime.timedelta datetime instance Returns ------- str Human readable string representation rounded to seconds """seconds=timedelta.total_seconds()ifseconds<0:res=f"-{-timedelta}"else:res=str(timedelta)returnres.split(",")[0].split(".")[0]
[docs]defget_pending_timer(timers:list[JobTimer])->JobTimer:"""Get the the timer with the largest overdue time."""unsorted_timer_datetimes:dict[JobTimer,dt.datetime]={}fortimerintimers:unsorted_timer_datetimes[timer]=timer.datetimesorted_timers=sorted(unsorted_timer_datetimes,key=unsorted_timer_datetimes.get,# type: ignore)returnsorted_timers[0]
[docs]defsane_timing_types(job_type:JobType,timing:TimingJobUnion)->None:""" Determine if the `JobType` is fulfilled by the type of the specified `timing`. Parameters ---------- job_type : JobType :class:`~scheduler.job.JobType` to test agains. timing : TimingJobUnion The `timing` object to be tested. Raises ------ TypeError If the `timing` object has the wrong `Type` for a specific `JobType`. """try:tg.check_type(timing,JOB_TIMING_TYPE_MAPPING[job_type]["type"])ifjob_type==JobType.CYCLIC:ifnotlen(timing)==1:raiseTypeErrorexceptTypeErroraserr:raiseSchedulerError(JOB_TIMING_TYPE_MAPPING[job_type]["err"])fromerr
[docs]defstandardize_timing_format(job_type:JobType,timing:TimingJobUnion)->TimingJobUnion:r""" Return timings in standardized form. Clears irrelevant time positionals for `JobType.MINUTELY` and `JobType.HOURLY`. """ifjob_typeisJobType.MINUTELY:timing=[time.replace(hour=0,minute=0)fortimeincast(list[dt.time],timing)]elifjob_typeisJobType.HOURLY:timing=[time.replace(hour=0)fortimeincast(list[dt.time],timing)]returntiming
[docs]defcheck_timing_tzinfo(job_type:JobType,timing:TimingJobUnion,tzinfo:Optional[dt.tzinfo],)->None:"""Raise if `timing` incompatible with `tzinfo` for `job_type`."""ifjob_typeisJobType.WEEKLY:forweekdayincast(list[Weekday],timing):ifbool(weekday.time.tzinfo)^bool(tzinfo):raiseSchedulerError(TZ_ERROR_MSG)elifjob_typein(JobType.MINUTELY,JobType.HOURLY,JobType.DAILY):fortimeincast(list[dt.time],timing):ifbool(time.tzinfo)^bool(tzinfo):raiseSchedulerError(TZ_ERROR_MSG)
[docs]defcheck_duplicate_effective_timings(job_type:JobType,timing:TimingJobUnion,tzinfo:Optional[dt.tzinfo],)->None:"""Raise given timings are not effectively duplicates."""ifjob_typeisJobType.WEEKLY:ifnotare_weekday_times_unique(cast(list[Weekday],timing),tzinfo):raiseSchedulerError(DUPLICATE_EFFECTIVE_TIME)elifjob_typein(JobType.MINUTELY,JobType.HOURLY,JobType.DAILY,):ifnotare_times_unique(cast(list[dt.time],timing)):raiseSchedulerError(DUPLICATE_EFFECTIVE_TIME)
[docs]defset_start_check_stop_tzinfo(start:Optional[dt.datetime],stop:Optional[dt.datetime],tzinfo:Optional[dt.tzinfo],)->dt.datetime:"""Raise if `start`, `stop` and `tzinfo` incompatible; Make start."""ifstart:ifbool(start.tzinfo)^bool(tzinfo):raiseSchedulerError(_TZ_ERROR_MSG.format("start"))else:start=dt.datetime.now(tzinfo)ifstop:ifbool(stop.tzinfo)^bool(tzinfo):raiseSchedulerError(_TZ_ERROR_MSG.format("stop"))ifstopisnotNone:ifstart>=stop:raiseSchedulerError(START_STOP_ERROR)returnstart