-1

I am a bit new to python and its concepts. For my current project i need to do certain api calls at x rate/y mins. Regarding this i came across the concept of decorator and a python library for the same. Its called ratelimit and click here to go to its github link

The simplest example for this api is:

from ratelimit import rate_limited
import requests

MESSAGES=100
SECONDS= 3600

@rate_limited(MESSAGES, SECONDS)
def call_api(url):
    response = requests.get(url)

   if response.status_code != 200:
     raise ApiError('Cannot call API: {}'.format(response.status_code))
   return response

But i need to call this function call_api from another function

def send_message():
    global MESSAGES
    global SECONDS
    MESSAGES=10
    SECONDS=5
    end_time=time.time()+60 #the end time is 60 seconds from the start time
    while(time.time()<end_time):
        call_api(url)

I want the call to happen and want the arguments of decorator to be updates on runtime as the real values will be user input. But as per my understanding the decorator takes value before run time. So how can i pass dynamic value to the decorator.

Thanx in advance for helping

2 Answers2

3

The decorator can be used as any time, not just when you define your function. You just can't use the decorator syntax.

# undecorated
def call_api(url):
    response = requests.get(url)
    if response.status_code != 200:
        raise ApiError('Cannot call API: {}'.format(response.status_code))
    return response

def send_message():
    global MESSAGES
    global SECONDS
    MESSAGES=10
    SECONDS=5
    end_time=time.time()+60 #the end time is 60 seconds from the start time

    rl_api = rate_limited(MESSAGES, SECONDS)(call_api)
    while(time.time()<end_time):
        rl_api(url)

This implies you can create multiple rate-limited functions at the same time, using different arguments to rate_limited.

fast_api = rate_limited(100, 5)(call_api)
slow_api = rate_limited(10, 5)(call_api)
chepner
  • 497,756
  • 71
  • 530
  • 681
0

Your question is basically whether you can call decorators by reference rather than by value. To that the answer is yes. Executive summary: pass a mutable object.

In this particular case, it won't do you any good. As you can see in the code for the ratelimit module, the two parameters, every and period, are used to set a new variable, frequency, when the decorated function is defined:

frequency = abs(every) / float(clamp(period))

To get a variable frequency you'll have to rewrite the module to support your needs, but that should be doable. Consider the following as a minimal illustration:

def limit(limiter):
    def decorator(func):
        def wrapper(*args, **kwargs):
            print(limiter.period, limiter.every)
            return func(*args, **kwargs)
        return wrapper
    return decorator


class Limiter():
    def __init__(self, every, period):
        self.every = every
        self.period = period

Now give it a spin:

>>> l = Limiter(1, 2)
>>> 
>>> @limit(l)
... def foo():
...     pass
... 
>>> foo()
2 1
>>> l.every = 10
>>> foo()
2 10