<s> import threading <EOL> class SingleRateLimiter ( object ) : <EOL> """<STR_LIT>""" <EOL> def __init__ ( self , calls_per_epoch , seconds_per_epoch ) : <EOL> """<STR_LIT>""" <EOL> self . seconds_per_epoch = seconds_per_epoch <EOL> self . semaphore = threading . Semaphore ( calls_per_epoch ) <EOL> self . lock = threading . Lock ( ) <EOL> self . current = <NUM_LIT:0> <EOL> self . limit = calls_per_epoch <EOL> self . resetter = None <EOL> self . _total_calls = <NUM_LIT:0> <EOL> self . _successful_calls = <NUM_LIT:0> <EOL> def call ( self , method = None , * args ) : <EOL> """<STR_LIT>""" <EOL> self . semaphore . acquire ( ) <EOL> with self . lock : <EOL> self . current += <NUM_LIT:1> <EOL> successful_call = True <EOL> try : <EOL> return method ( * args ) if method else None <EOL> except : <EOL> successful_call = False <EOL> raise <EOL> finally : <EOL> with self . lock : <EOL> if not self . resetter : <EOL> self . resetter = threading . Timer ( self . seconds_per_epoch , self . _reset ) <EOL> self . resetter . daemon = True <EOL> self . resetter . start ( ) <EOL> self . current -= <NUM_LIT:1> <EOL> self . _total_calls += <NUM_LIT:1> <EOL> if successful_call : <EOL> self . _successful_calls += <NUM_LIT:1> <EOL> def _drain ( self ) : <EOL> """<STR_LIT>""" <EOL> while self . semaphore . acquire ( False ) : <EOL> pass <EOL> def _reset ( self ) : <EOL> """<STR_LIT>""" <EOL> self . lock . acquire ( ) <EOL> self . _drain ( ) <EOL> for _ in range ( self . limit - self . current ) : <EOL> self . semaphore . release ( ) <EOL> self . resetter = None <EOL> self . lock . release ( ) <EOL> def wait ( self ) : <EOL> """<STR_LIT>""" <EOL> self . semaphore . acquire ( ) <EOL> self . semaphore . release ( ) <EOL> def reset_in ( self , seconds ) : <EOL> """<STR_LIT>""" <EOL> with self . lock : <EOL> if self . resetter : <EOL> self . resetter . cancel ( ) <EOL> self . _drain ( ) <EOL> self . resetter = threading . Timer ( seconds , self . _reset ) <EOL> self . resetter . daemon = True <EOL> self . resetter . start ( ) <EOL> def _decrease_successful_calls ( self ) : <EOL> with self . lock : <EOL> self . _successful_calls -= <NUM_LIT:1> <EOL> @ property <EOL> def calls ( self ) : <EOL> """<STR_LIT>""" <EOL> with self . lock : <EOL> return ( self . _successful_calls , self . _total_calls ) <EOL> class MultiRateLimiter ( object ) : <EOL> """<STR_LIT>""" <EOL> def __init__ ( self , * limits ) : <EOL> """<STR_LIT>""" <EOL> self . limits = [ ] <EOL> for limit in limits : <EOL> self . limits . append ( SingleRateLimiter ( limit [ <NUM_LIT:0> ] , limit [ <NUM_LIT:1> ] ) ) <EOL> def call ( self , method = None , * args ) : <EOL> """<STR_LIT>""" <EOL> self . wait ( ) <EOL> successful_call = True <EOL> try : <EOL> return method ( * args ) if method else None <EOL> except : <EOL> successful_call = False <EOL> raise <EOL> finally : <EOL> for limit in self . limits : <EOL> limit . call ( ) <EOL> if not successful_call : <EOL> limit . _decrease_successful_calls ( ) <EOL> def wait ( self ) : <EOL> """<STR_LIT>""" <EOL> for limit in self . limits : <EOL> limit . wait ( ) <EOL> def reset_in ( self , seconds ) : <EOL> """<STR_LIT>""" <EOL> for limit in self . limits : <EOL> limit . reset_in ( seconds ) <EOL> @ property <EOL> def calls ( self ) : <EOL> """<STR_LIT>""" <EOL> try : <EOL> return self . limits [ <NUM_LIT:0> ] . calls <EOL> except IndexError : <EOL> return <NUM_LIT:0> </s>
