Josh.F
Josh.F

Reputation: 3806

How can I limit API calls in multithreaded program in Python 3?

After much research, I'm not sure what the best practice is, is my following idea decent?

I want to access an API that limits the total number of calls I can make to 50 per minute.

My program has multiple threads operating on their own.

How can I limit my program to stay below the threshold?

My idea is to create a queue, and add one thing to it every X seconds where X = thread_count/allowed_calls*60. Then a separate thread would be needed for handling these requests. (And a separate thread for adding at a regular interval)

What would the best practice be for something like this? Is there a way to achieve this without needing completely separate threads for each little functionality?

Upvotes: 6

Views: 5182

Answers (2)

jmrueda
jmrueda

Reputation: 1418

You can use a decorator that limits the calls:

import time, threading

# make it work nice across threads
def RateLimited(max_per_second):
  '''
  Decorator that make functions not be called faster than
  '''
  lock = threading.Lock()
  minInterval = 1.0 / float(max_per_second)
  def decorate(func):
    lastTimeCalled = [0.0]
    def rateLimitedFunction(args,*kargs):
      lock.acquire()
      elapsed = time.clock() - lastTimeCalled[0]
      leftToWait = minInterval - elapsed

      if leftToWait>0:
        time.sleep(leftToWait)

      lock.release()

      ret = func(args,*kargs)
      lastTimeCalled[0] = time.clock()
      return ret
    return rateLimitedFunction
  return decorate

Then to use the decorator:

@RateLimited(2)  # 2 per second at most
def PrintNumber(num):
    print num

if __name__ == "__main__":
    print "This should print 1,2,3... at about 2 per second."
    for i in range(1,100):
    PrintNumber(i)

Upvotes: 2

Adriano Almeida
Adriano Almeida

Reputation: 5356

Why don't you create a class that uses an internal variable to control the number of calls and the first call on that second?

ripped this code from https://github.com/lucjon/Py-StackExchange/blob/master/stackexchange/web.py

Basically, it check if you have more than the amount of calls you need and stop if this is the case. If you are using multithread (like Pool), pass the function request as the funcion to be executed.

class WebRequestManager(object):
    # When we last made a request
    window = datetime.datetime.now()
    # Number of requests since last throttle window
    num_requests = 0

    def request(self, url, params):
        now = datetime.datetime.now()

        # Before we do the actual request, are we going to be throttled?
        def halt(wait_time):
            if self.throttle_stop:
                raise TooManyRequestsError()
            else:
                # Wait the required time, plus a bit of extra padding time.
                time.sleep(wait_time + 0.1)

        if (now - WebRequestManager.window).seconds >= 1:
            WebRequestManager.window = now
            WebRequestManager.num_requests = 0

        WebRequestManager.num_requests += 1
        if WebRequestManager.num_requests > 30:
            halt(5 - (WebRequestManager.window - now).seconds)

        request = urllib.request.Request(url)
        ...

Upvotes: 5

Related Questions