Python API Rate Limiting - How to Limit API Calls Globally - python

I'm trying to restrict the API calls in my code. I already found a nice python library ratelimiter==1.0.2.post0
https://pypi.python.org/pypi/ratelimiter
However, this library can only limit the rate in local scope. i.e) in function and loops
# Decorator
#RateLimiter(max_calls=10, period=1)
def do_something():
pass
# Context Manager
rate_limiter = RateLimiter(max_calls=10, period=1)
for i in range(100):
with rate_limiter:
do_something()
Because I have several functions, which make API calls, in different places, I want to limit the API calls in global scope.
For example, suppose I want to limit the APIs call to one time per second. And, suppose I have functions x and y in which two API calls are made.
#rate(...)
def x():
...
#rate(...)
def y():
...
By decorating the functions with the limiter, I'm able to limit the rate against the two functions.
However, if I execute the above two functions sequentially, it looses track of the number of API calls in global scope because they are unaware of each other. So, y will be called right after the execution of x without waiting another second. And, this will violate the one time per second restriction.
Is there any way or library that I can use to limit the rate globally in python?

I had the same problem, I had a bunch of different functions that calls the same API and I wanted to make rate limiting work globally. What I ended up doing was to create an empty function with rate limiting enabled.
PS: I use a different rate limiting library found here: https://pypi.org/project/ratelimit/
from ratelimit import limits, sleep_and_retry
# 30 calls per minute
CALLS = 30
RATE_LIMIT = 60
#sleep_and_retry
#limits(calls=CALLS, period=RATE_LIMIT)
def check_limit():
''' Empty function just to check for calls to API '''
return
Then I just call that function at the beginning of every function that calls the API:
def get_something_from_api(http_session, url):
check_limit()
response = http_session.get(url)
return response
If the limit is reached, the program will sleep until the (in my case) 60 seconds have passed, and then resume normally.

After all, I implemented my own Throttler class. By proxying every API request to the request method, we can keep track of all API requests. Taking advantage of passing function as the request method parameter, it also caches the result in order to reduce API calls.
class TooManyRequestsError(Exception):
def __str__(self):
return "More than 30 requests have been made in the last five seconds."
class Throttler(object):
cache = {}
def __init__(self, max_rate, window, throttle_stop=False, cache_age=1800):
# Dict of max number of requests of the API rate limit for each source
self.max_rate = max_rate
# Dict of duration of the API rate limit for each source
self.window = window
# Whether to throw an error (when True) if the limit is reached, or wait until another request
self.throttle_stop = throttle_stop
# The time, in seconds, for which to cache a response
self.cache_age = cache_age
# Initialization
self.next_reset_at = dict()
self.num_requests = dict()
now = datetime.datetime.now()
for source in self.max_rate:
self.next_reset_at[source] = now + datetime.timedelta(seconds=self.window.get(source))
self.num_requests[source] = 0
def request(self, source, method, do_cache=False):
now = datetime.datetime.now()
# if cache exists, no need to make api call
key = source + method.func_name
if do_cache and key in self.cache:
timestamp, data = self.cache.get(key)
logging.info('{} exists in cached # {}'.format(key, timestamp))
if (now - timestamp).seconds < self.cache_age:
logging.info('retrieved cache for {}'.format(key))
return data
# <--- MAKE API CALLS ---> #
# reset the count if the period passed
if now > self.next_reset_at.get(source):
self.num_requests[source] = 0
self.next_reset_at[source] = now + datetime.timedelta(seconds=self.window.get(source))
# throttle request
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 exceed max rate, need to wait
if self.num_requests.get(source) >= self.max_rate.get(source):
logging.info('back off: {} until {}'.format(source, self.next_reset_at.get(source)))
halt((self.next_reset_at.get(source) - now).seconds)
self.num_requests[source] += 1
response = method() # potential exception raise
# cache the response
if do_cache:
self.cache[key] = (now, response)
logging.info('cached instance for {}, {}'.format(source, method))
return response

Many API providers constrain developers from making too many API calls.
Python ratelimit packages introduces a function decorator preventing a function from being called more often than that allowed by the API provider.
from ratelimit import limits
import requests
TIME_PERIOD = 900 # time period in seconds
#limits(calls=15, period=TIME_PERIOD)
def call_api(url):
response = requests.get(url)
if response.status_code != 200:
raise Exception('API response: {}'.format(response.status_code))
return response
Note: This function will not be able to make more then 15 API call within a 15 minute time period.

Adding to Sunil answer, you need to add #sleep_and_retry decorator, otherwise your code will break when reach the rate limit:
#sleep_and_retry
#limits(calls=0.05, period=1)
def api_call(url, api_key):
r = requests.get(
url,
headers={'X-Riot-Token': api_key}
)
if r.status_code != 200:
raise Exception('API Response: {}'.format(r.status_code))
return r

There are lots of fancy libraries that will provide nice decorators, and special safety features, but the below should work with django.core.cache or any other cache with a get and set method:
def hit_rate_limit(key, max_hits, max_hits_interval):
'''Implement a basic rate throttler. Prevent more than max_hits occurring
within max_hits_interval time period (seconds).'''
# Use the django cache, but can be any object with get/set
from django.core.cache import cache
hit_count = cache.get(key) or 0
logging.info("Rate Limit: %s --> %s", key, hit_count)
if hit_count > max_hits:
return True
cache.set(key, hit_count + 1, max_hits_interval)
return False

Using the Python standard library:
import threading
from time import time, sleep
b = threading.Barrier(2)
def belay(s=1):
"""Block the main thread for `s` seconds."""
while True:
b.wait()
sleep(s)
def request_something():
b.wait()
print(f'something at {time()}')
def request_other():
b.wait()
print(f'or other at {time()}')
if __name__ == '__main__':
thread = threading.Thread(target=belay)
thread.daemon = True
thread.start()
# request a lot of things
i = 0
while (i := i+1) < 5:
request_something()
request_other()
There's about s seconds between each timestamp printed. Because the main thread waits rather than sleeps, time it spends responding to requests is unrelated to the (minimum) time between requests.

Related

Testing a function based on third party service

I'm trying to figure out how to create unit tests for a function, which behavior is based on a third party service.
Suppose a function like this:
def sync_check():
delta_secs = 90
now = datetime.datetime.now().utcnow()
res = requests.get('<url>')
alert = SlackAlert()
last_value = res[-1]['date'] # Last element of the array is the most recent
secs = (now - last_value).seconds
if secs >= delta_secs:
alert.notify("out of sync. Delay: {} seconds".format(secs))
else:
alert.notify('in sync')
What's best practice to write unit test for this function? I need to test both if and else branches, but this depends on the third party service.
The first thing that come to my mind is to create a fake webserver and point to that one (changing url) but this way the codebase would include testing logic, like:
if test:
url = <mock_web_server_url>
else:
url = <third_party_service_url>
Moreover, unit testing would trigger slack alerts, which doesn't have to happen.
So there I shoulde change again the codebase like:
if secs >= delta_secs:
if test:
logging.debug("out of sync alert sent - testing mode")
else:
alert.notify("out of sync. Delay: {} seconds".format(secs))
else:
if test:
logging.debug("in sync alert sent - testing mode")
else:
alert.notify('in sync')
Which I don't really like.
Am I missing any design to solve this problem?
Check out Dependency Injection to test code that depends on third party services, without having to check whether you're running in test mode, like in your example. The basic idea is to have the slack alert service be an argument of your function, so for unit testing you can use a fake service that acts the way you want it to for each test.
Your code would end up looking something like this:
def sync_check(alert):
delta_secs = 90
now = datetime.datetime.now().utcnow()
res = requests.get('<url>')
last_value = res[-1]['date'] # Last element of the array is the most recent
secs = (now - last_value).seconds
if secs >= delta_secs:
alert.notify("out of sync. Delay: {} seconds".format(secs))
else:
alert.notify('in sync')
and in a test case, you could have your alert object be something as simple as:
class TestAlert:
def __init__(self):
self.message = None
def notify(self, message):
self.message = message
You could then test your function by passing on an instance of your TestAlert class, and check the logged output if you want to, by accessing the message attribute. This code would not access any third party services.
def test_sync_check():
alert = TestAlert()
sync_check(alert)
assert alert.message == 'in sync'

Python: Flask Cache for a range of time

My Flask app, will get data from an url only from certain time. If it is outside the range of time, it will used the last query data from the url that save in Cache. Outside the range of time, the url will return no data. Thus, I want to reuse the last data in cache
from flask_app import app
from flask import jsonify,abort,make_response,request
from flask.ext.sqlalchemy import SQLAlchemy
from flask.ext.cache import Cache
from datetime import datetime, time
app.config['CACHE_TYPE'] = 'simple'
app.cache = Cache(app)
#app.route('/top', methods=['GET'])
#app.cache.cached(timeout=60)
def top():
now = datetime.now()
now_time = now.time()
if now_time >= time(10,30) and now_time <= time(16,30):
print "within time, use data save in cache"
# function that use last data query from url, save in cache
else:
page = requests.get('http://www.abvfc.com')
data = re.findall(r'items:(.*)',page.content)
return jsonify(data)
The problem is I can't get the last Cache data. If there is no access to the api /top in the last 60 seconds, there will be no data.
Cache the data one minutes before the url return no data at 16.30
User can use the cache data outside range of time
I am not familiar with cache, so may be my current idea is not the best way.
i am not a flask user but perhaps this is your wanted decorator
def timed_cache(cache_time:int, nullable:bool=False):
result = ''
timeout = 0
def decorator(function):
def wrapper(*args, **kwargs):
nonlocal result
nonlocal timeout
if timeout <= time.time() or not (nullable or result):
result = function(*args, **kwargs)
timeout = time.time() + cache_time
return result
return wrapper
return decorator
Assuming that you only want your cache to be working between 10:30h and 16:30h, I'd change a bit the approach you are using with the cache.
The problem I see with your current implementation is, apart from the undesirable cache expiration during the critical time range, that you'll also need to disable it at the moments you want to actually return an updated response.
That said, I'll use a different strategy: saving to cache every time I compute an updated response and retrieving the response from the cache in the critical time period.
Regarding the Flask-Cache documentation and the information in this tutorial, I'd modify your code as follows:
from flask_app import app
from flask import jsonify,abort,make_response,request
from flask.ext.sqlalchemy import SQLAlchemy
from flask.ext.cache import Cache
from datetime import datetime, time
app.config['CACHE_TYPE'] = 'simple'
app.cache = Cache(app)
#app.route('/top', methods=['GET'])
def top():
now = datetime.now()
now_time = now.time()
if now_time >= time(10,30) and now_time <= time(16,30):
print "within time, use data save in cache"
return app.cache.get('last_top_response')
page = requests.get('http://www.abvfc.com')
data = re.findall(r'items:(.*)',page.content)
response = jsonify(data)
app.cache.set('last_top_response', response)
return response
I hope this to suit your needs

Simultaneously modify different keys in ZODB

I'm using ZODB as a persistent storage for objects that are going to be modified through a webservice.
Below is an example to which I reduced the issue.
The increment-function is what is called from multiple threads.
My problem is, that when increment is called simultaneously from two threads, for different keys, I'm getting the conflict-error.
I imagine it should be possible to resolve this, at least as long different keys are modified, in a proper way?
If so, I didn't manage to find an example on how to... (the zodb-documentation seems to be somewhat spread across different sites :/ )
Glad about any ideas...
import time
import transaction
from ZODB.FileStorage import FileStorage
from ZODB.DB import DB
from ZODB.POSException import ConflictError
def test_db():
store = FileStorage('zodb_storage.fs')
return DB(store)
db_test = test_db()
# app here is a flask-app
#app.route('/increment/<string:key>')
def increment(key):
'''increment the value of a certain key'''
# open connection
conn = db_test.open()
# get the current value:
root = conn.root()
val = root.get(key,0)
# calculate new value
# in the real application this might take some seconds
time.sleep(0.1)
root[key] = val + 1
try:
transaction.commit()
return '%s = %g' % (key, val)
except ConflictError:
transaction.abort()
return 'ConflictError :-('
You have two options here: implement conflict resolution, or retry the commit with fresh data.
Conflict resolution only applies to custom types you store in the ZODB, and can only be applied if you know how to merge your change into the newly-changed state.
The ZODB looks for a _p_resolveConflict() method on custom types and calls that method with the old state, the saved state you are in conflict with, and the new state you tried to commit; you are supposed to return the merged state. For a simple counter, like in your example, that'd be a as simple as updating the saved state with the change between the old and new states:
class Counter(Persistent):
def __init__(self, start=0):
self._count = start
def increment(self):
self._count += 1
return self._count
def _p_resolveConflict(self, old, saved, new):
# default __getstate__ returns a dictionary of instance attributes
saved['_count'] += new['_count'] - old['_count']
return saved
The other option is to retry the commit; you want to limit the number of retries, and you probably want to encapsulate this in a decorator on your method, but the basic principle is that you loop up to a limit, make your calculations based on ZODB data (which, after a conflict error, will auto-read fresh data where needed), then attempt to commit. If the commit is successful you are done:
max_retries = 10
retry = 0
conn = db_test.open()
root = conn.root()
while retry < max_retries:
val = root.get(key,0)
time.sleep(0.1)
root[key] = val + 1
try:
transaction.commit()
return '%s = %g' % (key, val)
except ConflictError:
retry += 1
raise CustomExceptionIndicatingTooManyRetries

Flask + Jinja2: how to measure performances

I need to compare the performances of a Flask application that uses jinja2.Template.render against jinja2.Template.stream to be sure that there is no loss of performances using the streaming of templates.
My (very simple) idea was to print the timestamp before and after the rendering of the template, but since the webpage is returned by a function, I'm not quite sure how to implement this...
My function is:
def index():
"""main function"""
env = Environment(loader=FileSystemLoader(basedir+'templates'))
#I modify the global variables
env.globals['foo'] = 'bar'
env.globals['build_response']=build_response
get_url = request.args.get('to_print', None)
message = "the input was \"%s\"" % (get_url,)
template = env.get_template('body.html')
return Response(template.stream(message=message))
#return template.render(message=message)
And what I need to measure is the time spent by
return template.render(message=message)
vs
return Response(template.stream(message=message))
thanks!
You need to look at the system clock before and after the call.
It's easier to do this if you don't immediately return the response from template.render or template.stream.
IE:
from time import time
def index():
# THE REST OF YOUR CODE GOES HERE
start = time.time()
resp = template.render(message=message)
end = time.time()
processing_time = end - start
# LOG PROCESSING TIME SOMEPLACE
return resp

Responding to httpRequest after using threading.Timer to delay response

I'm trying to patch a testing framework built in python for javascript called mootools-test-runner (i'm a front end developer by day, so my python skills are pretty weak... really weak.)
The use case is we want to be able to make a json request to the server and have it delay x amount of time before it returns -- originally it was written to use a sleep method, but that prevented multiple simultaneous requests. Sooo... after poking around for about a day i arrived at the code below. The problem i'm seeing (although there could well be many problems with my code) is:
The view test_runner.views.echo_json didn't return an HttpResponse object.
if anyone could offer any advice or point me in the right direction I would be super grateful -- thanks!
def echo_json(req, wasDelayed=False):
if req.REQUEST.get('delay') and wasDelayed == False:
sleeper(req, echo_jsonp)
else:
response = {}
callback = req.REQUEST.get('callback', False)
noresponse_eys = ['callback', 'delay']
for key, value in req.REQUEST.items():
if key not in noresponse_keys:
response.update({key: value})
response = simplejson.dumps(response)
if callback:
response = '%s(%s);' % (callback, response)
return HttpResponse(response, mimetype='application/javascript')
def sleeper(req, callback)
delay = float(req.REQUEST.get('delay'))
t = threading.Timer(delay, functools.partial(callback, req, true))
t.start()
Are you sure you want the return statement inside the for key, value loop? You're only allowing a single iteration, and returning.
Also, check the flow of the function. There are cases in which it will return None. Easiest way to do this is printing out your request object and examining it in the cases in which the function doesn't return an HttpResponse object.
See that your function will return None if:
req.request contains the key 'delay' and wasDelayed is True
req.REQUEST.items() is empty
I can't be sure, but I think the 2 problems are the else: and the return there. Shouldn't the code below the else: be executing whether the response is delayed or not? And shouldn't the return statement be outside the for loop?

Categories

Resources