Contact: fumanchu@aminus.org

Log in as guest/misc to create tickets

Changeset 40

Show
Ignore:
Timestamp:
02/10/06 21:35:20
Author:
fumanchu
Message:

Quite a few changes:

  1. Fixed eachday bug where time wasn't being combined correctly.
  2. Moved the dispatch-to-generator from the Recurrence class into the Locale class. This will allow some Locales to use other (potentially non-regex) schemes to parse natural-lang descriptions.
  3. Recurrence has a new reset() method.
  4. Removed the interval function and Recurrence method--it was too magical (advancing the gneerator each time).
  5. Changed Worker.active (default True) to Worker.paused (default False).
  6. Added Worker.nextrun, so consumers can know the next expected run time.
  7. Worker.motivate might now do nothing if its recurrence instance is exhausted (prviously, it assumed an infinite recurrence).
  8. Worker.curthread will now be a Timer object, even if recurrence is None (previously, it would have been a threading.Thread object).
  9. Worker.motivate now guards against setting a Timer to a datetime in the past.
Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • recur.py

    r24 r40  
    342342            startDate = sane_date(startDate.year, startDate.month, 
    343343                                  startDate.day + 1) 
    344             startDate = datetime.datetime.combine(startDate, timeofday) 
    345344    except AttributeError: 
    346345        # datetime.date has no time() attribute 
    347346        pass 
     347    startDate = datetime.datetime.combine(startDate, timeofday) 
    348348     
    349349    while (endDate is None) or (startDate <= endDate): 
     
    617617            else: 
    618618                self.regexes[key] = re.compile(regSet, re.IGNORECASE) 
     619     
     620    def __call__(self, description): 
     621        for rule, regSet in self.regexes.items(): 
     622            if isinstance(regSet, list): 
     623                for index, regex in enumerate(regSet): 
     624                    matches = regex.match(description) 
     625                    if matches: 
     626                        return rule, (index,) + matches.groups() 
     627            else: 
     628                matches = regSet.match(description) 
     629                if matches: 
     630                    return rule, matches.groups() 
     631         
     632        raise ValueError(u"The supplied description ('%s') " 
     633                         u"could not be parsed." % description) 
     634 
    619635localeEnglish = Locale() 
    620636 
     
    660676        self.endDate = endDate 
    661677        self.locale = locale 
    662          
    663         def setargs(matches, index=None): 
    664             args = [startDate] 
    665             if index is not None: 
    666                 args.append(index) 
    667             for eachArg in matches.groups(): 
    668                 args.append(eachArg) 
    669             args.append(endDate) 
    670             self.function = rule 
    671             a = self.args = tuple(args) 
    672             try: 
    673                 self.generator = rule(*a) 
    674             except TypeError: 
    675                 raise StandardError(a) 
    676          
    677         for rule, regSet in locale.regexes.items(): 
    678             if isinstance(regSet, list): 
    679                 for index, regex in enumerate(regSet): 
    680                     matches = regex.match(description) 
    681                     if matches: 
    682                         setargs(matches, index) 
    683                         return 
    684             else: 
    685                 matches = regSet.match(description) 
    686                 if matches: 
    687                     setargs(matches) 
    688                     return 
    689          
    690         raise ValueError(u"The supplied description ('%s') " 
    691                          u"could not be parsed." % description) 
     678        self.function, args = locale(description) 
     679        self.args = (startDate,) + args + (endDate,) 
     680        # Form an initial generator, if for no other reason than to test args early. 
     681        self.reset() 
     682     
     683    def reset(self): 
     684        try: 
     685            self.generator = self.function(*self.args) 
     686        except TypeError, x: 
     687            x.args += self.args 
     688            raise 
    692689     
    693690    def __iter__(self): 
    694         self.generator = self.function(*self.args
     691        self.reset(
    695692        return self 
    696693     
    697694    def next(self): 
    698695        return self.generator.next() 
    699      
    700     def interval(self): 
    701         return interval(self) 
    702  
    703  
    704 def interval(recurrence): 
    705     """Seconds between the current datetime and the next recurrence. 
    706      
    707     This is particularly handy for creating threading.Timer objects 
    708     on a recurring schedule, in the form: 
    709         threading.Timer(recurrence.interval(), function).start() 
    710     See the Worker class (below) for an example of this. 
    711      
    712     Notice that this function advances the generator each time it 
    713     is called. If the generator completes, the StopIteration exception 
    714     is not trapped here; it is passed up to the caller to handle. 
    715      
    716     On a rare occasion, the return value will be off by one second; 
    717     this is probably due to the seconds "rolling over" while we are 
    718     inside this function. If you feel that this is a drastic oversight, 
    719     feel free to submit a patch. 
    720     """ 
    721     diff = recurrence.next() - datetime.datetime.now() 
    722     return (diff.days * 86400) + diff.seconds 
    723696 
    724697 
     
    728701    You must override work(), which is called at each interval. 
    729702     
    730     active: a boolean flag indicating whether or not the Worker's run() 
     703    paused: a boolean flag indicating whether or not the Worker's run() 
    731704        method should be executed at each interval. Notice that, even if 
    732         active is False, recurring Workers will continue to schedule new 
     705        paused is True, recurring Workers will continue to schedule new 
    733706        threads--they simply won't do anything at run() time. 
    734707     
     
    745718                recurrence = None 
    746719        self.recurrence = recurrence 
    747         if self.recurrence: 
    748             # Throw away the first occurrence value, 
    749             # which is almost always .now() 
    750             self.recurrence.next() 
    751720         
    752721        self.createdate = datetime.datetime.now() 
    753722        self.lastrun = None 
    754         self.active = True 
     723        self.nextrun = None 
     724        self.paused = False 
    755725        self.terminated = False 
    756726     
    757727    def motivate(self): 
    758728        """Start a new immediate or recurring thread for work.""" 
     729        self.nextrun = None 
    759730        if not self.terminated: 
    760731            if self.recurrence: 
    761                 # Start a recurring, timed Thread. 
    762                 self.curthread = threading.Timer(self.recurrence.interval(), 
    763                                                  self._cycle) 
     732                # Start a recurring, timed thread. 
     733                now = datetime.datetime.now() 
     734                while True: 
     735                    try: 
     736                        next = self.recurrence.next() 
     737                    except StopIteration: 
     738                        # The recurrence series was exhausted. 
     739                        return 
     740                    diff = next - now 
     741                    diff = (diff.days * 86400) + diff.seconds 
     742                    if diff >= 0: 
     743                        self.nextrun = next 
     744                        break 
     745                iv = diff 
     746                func = self._cycle 
    764747            else: 
    765748                # Start a single, non-recurring thread. 
    766                 self.curthread = threading.Thread(None, self.run) 
     749                iv = 0 
     750                func = self.run 
     751            self.curthread = threading.Timer(iv, func) 
    767752            self.curthread.start() 
    768753     
     
    774759    def run(self): 
    775760        """Prepare for work.""" 
    776         if self.active and not self.terminated: 
     761        if not self.paused and not self.terminated: 
    777762            self.work() 
    778763            self.lastrun = datetime.datetime.now() 
     
    781766        """Stop work.""" 
    782767        self.terminated = True 
    783         try: 
    784             self.curthread.cancel() 
    785         except AttributeError: 
    786             pass 
     768        self.curthread.cancel() 
    787769     
    788770    def work(self):