alisaifee / flask-limiter Goto Github PK
View Code? Open in Web Editor NEWRate Limiting extension for Flask
Home Page: https://flask-limiter.readthedocs.org
License: MIT License
Rate Limiting extension for Flask
Home Page: https://flask-limiter.readthedocs.org
License: MIT License
When installing on 3.4 it produces an error at some point (unsure if it works or not though)
byte-compiling build/bdist.macosx-10.10-x86_64/egg/flask_limiter/backports/counter.py to counter.cpython-34.pyc File "build/bdist.macosx-10.10-x86_64/egg/flask_limiter/backports/counter.py", line 193 print doctest.testmod() ^ SyntaxError: invalid syntax
Heyah,
just tried your plugin, installed via pip install
it failed because of the missing import of limits
. It appears that your setup.py/egg file doesn't declare this dependency right.
Is there a built-in function where it will reset the limit upon log-in?
Let's say I have a limit of 10.. May failed attempt is now 5 but on my 6th I logged-in and I want my limit remainings to reset to 0
You've released this with a changelog, https://pypi.python.org/pypi/Flask-Limiter/0.9.3
Is there a better public repo to track than this GH repo?
The current implementation does not allow for the In-Memory-Fallback to be enabled unless you provide a list of fallback rate limits. I'm not sure I understand the design decision here, but it's fine if that fix the use case of most people.
However, I would like to know if I made a PR that would allow for In-Memory-Fallback that simply added a Memory Storage, but still used the per-route based limits, if you would be in favor or merging this?
The code change doesn't seem hard and I can do it in a backward compatible way but simply adding another configuration option to the Limiter constructor and the init code.
Basically theres no mapping for code==429, which was only introduced in 0.9
I have seen the documentation for flask-limiter with blueprints, but it seems to assume that the blueprint is defined inside the same file as the app object.
A common pattern with blueprints is to set up each blueprint in its own separate module, to create a module app. Then the main app module imports all the blueprints and registers them. In this case, there's no app
object to use to construct the limiter. I can't import it (or the limiter) from the main module, as that will create a circular import. Is there a way to use per-route limits with a modular setup like this?
Is there any way for setting customized request limit for different user token? for example suppose for a specific endpoint like /test
we have 1000 per day
limit for token A and 100000 per day
limit for token B.
Hi!
I was wondering how I could apply the limit decorator to a blueprint route.
I couldn't manage to get it working.
Any ideas?
I can't find any documentation on how to apply user specific rate limits. For example, my admins should have an unlimited rate limit, employees have a limit of 500/hour and verified users have 250/hour. How can we do something like this?
I think there's something wrong with this version check and patching of werkzeug here: https://github.com/alisaifee/flask-limiter/blob/master/flask_limiter/errors.py#L14
When running Werkzeug 0.10 I get this:
File "/home/peplin/dev/stratos/http-api/src/env/lib/python2.7/site-packages/nose/loader.py", line 414, in loadTestsFromName
addr.filename, addr.module)
File "/home/peplin/dev/stratos/http-api/src/env/lib/python2.7/site-packages/nose/importer.py", line 47, in importFromPath
return self.importFromDir(dir_path, fqname)
File "/home/peplin/dev/stratos/http-api/src/env/lib/python2.7/site-packages/nose/importer.py", line 94, in importFromDir
mod = load_module(part_fqname, fh, filename, desc)
File "/home/peplin/dev/stratos/http-api/src/tests/test_version.py", line 5, in <module>
from test import BaseTestCase
File "/home/peplin/dev/stratos/http-api/src/tests/test.py", line 14, in <module>
from app import create_app
File "/home/peplin/dev/stratos/http-api/src/app/__init__.py", line 15, in <module>
from flask.ext.limiter import Limiter
File "/home/peplin/dev/stratos/http-api/src/env/lib/python2.7/site-packages/flask/exthook.py", line 62, in load_module
__import__(realname)
File "/home/peplin/dev/stratos/http-api/src/env/lib/python2.7/site-packages/flask_limiter/__init__.py", line 8, in <module>
from .errors import ConfigurationError, RateLimitExceeded
File "/home/peplin/dev/stratos/http-api/src/env/lib/python2.7/site-packages/flask_limiter/errors.py", line 14, in <module>
_patch_werkzeug()
File "/home/peplin/dev/stratos/http-api/src/env/lib/python2.7/site-packages/flask_limiter/errors.py", line 12, in _patch_werkzeug
werkzeug._internal.HTTP_STATUS_CODES[429] = 'Too Many Requests' # pragma: no cover
AttributeError: 'module' object has no attribute 'HTTP_STATUS_CODES'
but it should not be attempting to apply this patch unless it's an older version.
Hi,
I am having a slight issue with the Retry-After headers on my current project. If the user triggers too many requests to my rate limited endpoint, I am using the Retry-After header to wait for the right amount of time before letting their request through.
However, it almost seems that the header is a little "optimistic" timewise, because if I retry after that exact number of seconds, it triggers the rate limit again. However, on this request, the rate limiter actually says Retry-After zero seconds. It says this several times in quick succession, before then saying Retry-After -1484742195
seconds (exact value varies, but it's always large and negative).
I don't know whether I've misunderstood something, or if I'm doing something else daft, but I wondered how this value was calculated, and how reliable it was? If I arbitrarily add a few seconds to the Retry-After time it works fine, but I am reluctant to add a fixed value such as 5 seconds in case there are times when 5 seconds aren't enough. Adding 1 second doesn't seem to be sufficient, but adding 2 seconds seems to do the trick.
Once that Retry-After is up, what actually happens internally? Is it simply that I am observing the rate limit resetting itself and taking a non-zero amount of time to do so?
Thanks!
expirations
, events
, and counters
stored in MemoryStorage
aren't getting expired. The rate limits are still correct, however, keys are left in memory long after their expirations.
At the moment, Flask-Limiter creates keys like:
LIMITER/foo_client/admin_api.post_root_node/1/1/minute
To allow sharing same Redis instance with multiple apps, would be nice if Flask-Limiter supported something like:
Limiter(..., key_prefix='env_name')
That would generate keys like:
env_name:LIMITER/foo_client/admin_api.post_root_node/1/1/minute # or
LIMITER/env_name/foo_client/admin_api.post_root_node/1/1/minute
I guess this feature would be beneficial for memcached users as well?
Note: Redis can run multiple "databases", but databases are not supported in Redis Cluster so it's not the safe way to go.
Wondering what it would take to simplify the RedisStorage
adapter to avoid methods twemproxy doesn't support - (lock, pipeline, script load, etc). Those looking to solve a high availability story will no doubt want to employ twemproxy for redis replication.
https://github.com/alisaifee/flask-limiter/blob/master/flask_limiter/storage.py#L268
Thoughts?
Hello,
it wasn't clear to me how to add limits to extension routes in the documentation. Maybe this will help someone else...
Perhaps you want to use limits on a flask extension?
Here is an example of adding limits to flask-security-fork blueprint and views.
security = flask_security.Security(app, ...)
# add to a particular view function.
login = app.view_functions['security.login']
limiter.limit("6/hour")(login)
# add to a whole blueprint.
limiter.limit("4/hour")(security.app.blueprints['security'])
I installed the package of version 0.8.1 using pip install
and the package was unable to run since the limits
package was missing. I just added manually the limits
package to my requirements.
Can you fix it please...
Hi,
When using flask-limiter, I expected that the global limits would be a limit shared by all routes, rather than a default per-route limit.
You might want to clarify documentation on this subject :)
Thanks!
When I run this minimal test example with coverage it throws a warning:
Coverage.py warning: Trace function changed, measurement is likely wrong: None
Help appreciated! Cheers, lukas
import unittest
from flask import Flask
from flask_limiter import Limiter
app = Flask(__name__)
Limiter(app)
class Tester(unittest.TestCase):
def test_nothing(self):
pass
According to RFC 6585 a server MAY include a Retry-After header indicating how long to wait before making a new request. Looks like a proper addition/alternative to the non-standard X-RateLimit-Reset header. Would you accept a PR on this?
deprecation warning running with python 3.4.2
flask_limiter/extension.py:242: DeprecationWarning: The 'warn' method is deprecated, use 'warning' instead
, lim.limit, lim.key_func(), limit_scope
Is it possible currently to have it fall back to an in-memory storage scheme if the redis/memcached server was down? I didn't see anything in the code, so I assume the answer is "no". But just wanted to ask.
I would assume this would be opt-in, not everyone would want this behavior. But I see a huge benefit to having the rate limiting just flip back to in-memory if the remote server was down.
If the flask app was under high load, the request load could take down a memcache/redis server. So having it flip to in-memory means an endpoint could stay up and then the in-memory rate limit would get exceeded on each server and the offending client would get blocked (just would take longer as they'd have to hit limits on each individual server). While other clients would still get access.
I put a print statement in my limiter key function and I noticed that for every request it's accessed twice. Why is this done?
Also, I think this is the area of code that allows such to happen: https://github.com/alisaifee/flask-limiter/blob/master/flask_limiter/extension.py#L391-L392
I have already run pip install flask-limiter
and pip install werkzeug
but when executing my python code I get this error
Traceback (most recent call last):
File "rw_container_api/routes/v1/pricing.py", line 3, in
from flask_limiter import Limiter
File "/usr/lib/python2.6/site-packages/flask_limiter/init.py", line 8, in
from .errors import ConfigurationError, RateLimitExceeded
File "/usr/lib/python2.6/site-packages/flask_limiter/errors.py", line 14, in
_patch_werkzeug()
File "/usr/lib/python2.6/site-packages/flask_limiter/errors.py", line 9, in _patch_werkzeug
if pkg_resources.get_distribution("werkzeug").version < "0.9":
File "/usr/lib/python2.6/site-packages/pkg_resources.py", line 292, in get_distribution
if isinstance(dist,Requirement): dist = get_provider(dist)
File "/usr/lib/python2.6/site-packages/pkg_resources.py", line 176, in get_provider
return working_set.find(moduleOrReq) or require(str(moduleOrReq))[0]
File "/usr/lib/python2.6/site-packages/pkg_resources.py", line 648, in require
needed = self.resolve(parse_requirements(requirements))
File "/usr/lib/python2.6/site-packages/pkg_resources.py", line 546, in resolve
raise DistributionNotFound(req)
pkg_resources.DistributionNotFound: werkzeug
I am running python 2.6. Is there a problem with this setup or theres something that I have missed?
I am unable to disable the rate limiter while running my unit tests.
Setting the RATELIMIT_ENABLED = False
directly in config.py however works. But I'd like to do it programatically.
Considering my app setup is like this:
app.py:
app = Flask(__name__)
app.config.from_object('config')
limiter = Limiter(app, global_limits=["500 per day", "100 per hour"])
tests.py:
import app
def setUp(self):
app.app.config['TESTING'] = True
app.app.config['RATELIMIT_ENABLED'] = False
bouncer.limiter.enabled = False
I have disabled auto_check
because I want to trigger rate-limit check at a specific point during request processing.
My routes are something like this:
@app.route(...)
@auth.auth(...)
...
def route():
...
And I want limiter to run after auth check. So I added something like:
class ExtLimiter(Limiter):
def rate_limit(self):
def wrapper(f):
@functools.wraps(f)
def wrapped(*args, **kwargs):
self.check()
return f(*args, **kwargs)
return wrapped
return wrapper
And now I:
@app.route(...)
@auth.auth(...)
@limiter.rate_limit()
...
def route():
...
I think this simple decorator could be bundled in Flask-Limiter?
(I was kinda surprised when @limiter.limit()
only registered the route and did not run the check()
at all. So, added this decorator.)
It looks like the current implementation does not allow sharing a single custom rate limit among several routes (global limits excluded).
For instance, take the example where you have a /slow route limited to 1 request per day. What if I have a second route which I would like to share the limit of 1 request per day along the other /slow route? Right now the code will limit each route to 1 req per day, while it would be useful to allow 1 req per day for all the slow routes combined.
I couldn't find a way to override the default 429 error page. I can override the error message via this method. I was looking to make a template for the 429 page.
@limiter.limit("1 per minute", error_message='Already Submitted Answer')
Ideally OPTIONS requests just server static content and shouldn't be rate limited. This is a concern because Browser JavaScript can't handle those and only gets a -1
back.
So what is the best practise here? I'm assuming flask doesn't actually generate the response on an OPTIONS requests but just serves static?
Any one else got experience with this? How would I disable the limiter for OPTIONS requests?
According to the documentation of werkzeug, request.access_route
is computed by looking at forwarded header in the request. However, as malicious users can submit request with X-Forwarded-For
of any value, they can bypass limiters easily.
I think that this security problem should be documented clearly, and utils.get_ipaddr
should be used with caution.
Here is an article discussing this problem: http://esd.io/blog/flask-apps-heroku-real-ip-spoofing.html.
While doing a pip install with flask-limiter, my code broke with a Value Error "couldn't parse rate limit string '2000/month,30/second' pointing to line 41 of limits/utils.py. It looks like the limits library as of 1.2.0 no longer supports comma delimited strings for specifying strings.
alisaifee/limits@21eac8b#diff-f5a532274827e0166a372e733fef093eL40
I would suggest either pinning the version of limits to 1.1.1 in the setup.py or fixing documentation.
just a reminder to myself, tests running on travis arbitrarily fail when they deal with external dependencies.
Some times server raise 4XX error to tell client data error, Can flask limiter don't count rate if the route raise exception?
Hi,
i am trying to build ebuild for Funtoo (Gentoo based) Linux with latest version (0.9.5.tar.gz) which (in short) simple uses setup.py
, but i got warning:
...
running install_scripts
/usr/lib64/python3.5/site-packages/setuptools/dist.py:345: UserWarning: The version specified ('unknown') is an invalid version, this may not work as expected with newer versions of setuptools, pip, and PyPI. Please see PEP 440 for more details.
"details." % self.metadata.version
Then i did small script witch commands from setup.py
and i enabled verbose for version check:
versioneer.get_version(verbose=True)
It reports:
get_version():
no .git in /var/tmp/portage/dev-python/flask-limiter-0.9.5/work/flask-limiter-0.9.5
guessing rootdir is '/var/tmp/portage/dev-python/flask-limiter-0.9.5/work/flask-limiter-0.9.5', but 'flask-limiter-0.9.5' doesn't start with prefix 'flask_limiter-'
got version from default None
unknown
After i changed the versioneer.parentdir_prefix
to "flask-limiter-"
it works.
For now i don't know if there is mistake in parentdir_prefix
or in Funtoo's install workflow, but i decided to report it - you will understand it better.
File "/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/site-packages/flask/exthook.py", line 81, in load_module
reraise(exc_type, exc_value, tb.tb_next)
File "/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/site-packages/flask/_compat.py", line 32, in reraise
raise value.with_traceback(tb)
File "/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/site-packages/flask_limiter/__init__.py", line 9, in <module>
from .extension import Limiter, HEADERS
File "/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/site-packages/flask_limiter/extension.py", line 10, in <module>
from limits.errors import ConfigurationError
ImportError: No module named 'limits'
In the case where the individual requests take longer to process than the rate limit window itself, an exception is thrown when using redis as the backing storage caused by an expired key.
The rate limit headers are enabled by default, which is contrary to the docs.
It might also be a good idea to enable RTD versioning, and set the default doc version to the latest pypi version.
How can we disable the rate limiter during our unit testing? I cannot get it to work. Setting RATELIMIT_ENABLED = False
doesn't seem to work. I'd like to globally turn off the entire limiter since I use the limiter.limit()
in a lot of different locations, but don't want to add testing conditions everywhere. Here is my app
def create_app(object_config=ProdConfig):
app = Flask(__name__)
app.config.from_object(object_config)
...
limiter.init_app(app)
for handler in app.logger.handlers:
limiter.logger.addHandler(handler)
return app
and here is my testing app inside my pytest conftest.py
from myapp import create_app
import pytest
@pytest.fixture(scope='session')
def app():
app = create_app(debug=True, local=True, object_config=TestConfig)
return app
I load it with a Test Config which has RATELIMIT_ENABLED
set to False but it does not work
class TestConfig(Config):
TESTING = True
DEBUG = True
SQLALCHEMY_DATABASE_URI = 'sqlite://'
BCRYPT_LOG_ROUNDS = 1 # For faster tests
WTF_CSRF_ENABLED = False # Allows form testing
PRESERVE_CONTEXT_ON_EXCEPTION = False
USE_PROFILER = False # Turn off the Flask Profiler extension
RATELIMIT_ENABLED = False # Turn off the Flask Rate Limiter
Hello, after upgrading my codebase to use python3.5 (from python 2.7), Flask-limiter refuses to work, I am not sure what went wrong.
Traceback (most recent call last):
File "/usr/local/lib/python3.5/dist-packages/flask/app.py", line 1997, in __call__
return self.wsgi_app(environ, start_response)
File "/usr/local/lib/python3.5/dist-packages/flask/app.py", line 1985, in wsgi_app
response = self.handle_exception(e)
File "/usr/local/lib/python3.5/dist-packages/flask/app.py", line 1540, in handle_exception
reraise(exc_type, exc_value, tb)
File "/usr/local/lib/python3.5/dist-packages/flask/_compat.py", line 33, in reraise
raise value
File "/usr/local/lib/python3.5/dist-packages/flask/app.py", line 1982, in wsgi_app
response = self.full_dispatch_request()
File "/usr/local/lib/python3.5/dist-packages/flask/app.py", line 1614, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/usr/local/lib/python3.5/dist-packages/flask/app.py", line 1517, in handle_user_exception
reraise(exc_type, exc_value, tb)
File "/usr/local/lib/python3.5/dist-packages/flask/_compat.py", line 33, in reraise
raise value
File "/usr/local/lib/python3.5/dist-packages/flask/app.py", line 1610, in full_dispatch_request
rv = self.preprocess_request()
File "/usr/local/lib/python3.5/dist-packages/flask/app.py", line 1831, in preprocess_request
rv = func()
File "/usr/local/lib/python3.5/dist-packages/flask_limiter/extension.py", line 434, in __check_request_limit
six.reraise(*sys.exc_info())
File "/usr/local/lib/python3.5/dist-packages/six.py", line 686, in reraise
raise value
File "/usr/local/lib/python3.5/dist-packages/flask_limiter/extension.py", line 399, in __check_request_limit
if not self.limiter.hit(lim.limit, *filter(None, [self._key_prefix, limit_key, limit_scope])):
File "/usr/local/lib/python3.5/dist-packages/limits/strategies.py", line 124, in hit
self.storage().incr(item.key_for(*identifiers), item.get_expiry())
File "/usr/local/lib/python3.5/dist-packages/limits/limits.py", line 74, in key_for
str(self.amount), str(self.multiples), self.granularity[1]
TypeError: sequence item 0: expected str instance, bytes found
Hello,
werkzeug released a 0.10 version and now flask-limiter no longer works. This is because there is some logic for patching HTTP_STATUS_CODE when werkzeug is older than "0.9", the problem is that such dict doesn't exist at 0.10 and "0.10" < "0.9".
Hi,
We're seeing some very strange behavior when running flask-limiter in the following environment (backed by memcache):
With a limit of 30 requests per minute, I see the following when printing out the flask-limiter debug headers:
iteration: 0, remaining 29, headers {'x-ratelimit-limit': '30', 'x-ratelimit-reset': '1435256060'}
iteration: 1, remaining 29, headers {'x-ratelimit-limit': '30', 'x-ratelimit-reset': '1435256061'}
iteration: 2, remaining 28, headers {'x-ratelimit-limit': '30', 'x-ratelimit-reset': '1435256061'}
iteration: 3, remaining 27, headers {'x-ratelimit-limit': '30', 'x-ratelimit-reset': '1435256061'}
iteration: 4, remaining 26, headers {'x-ratelimit-limit': '30', 'x-ratelimit-reset': '1435256061'}
iteration: 5, remaining 25, headers {'x-ratelimit-limit': '30', 'x-ratelimit-reset': '1435256061'}
iteration: 6, remaining 24, headers {'x-ratelimit-limit': '30', 'x-ratelimit-reset': '1435256061'}
iteration: 7, remaining 28, headers {'x-ratelimit-limit': '30', 'x-ratelimit-reset': '1435256060'}
iteration: 8, remaining 29, headers {'x-ratelimit-limit': '30', 'x-ratelimit-reset': '1435256072'}
iteration: 9, remaining 23, headers {'x-ratelimit-limit': '30', 'x-ratelimit-reset': '1435256061'}
iteration: 10, remaining 27, headers {'x-ratelimit-limit': '30', 'x-ratelimit-reset': '1435256060'}
iteration: 11, remaining 26, headers {'x-ratelimit-limit': '30', 'x-ratelimit-reset': '1435256060'}
iteration: 12, remaining 22, headers {'x-ratelimit-limit': '30', 'x-ratelimit-reset': '1435256061'}
iteration: 13, remaining 21, headers {'x-ratelimit-limit': '30', 'x-ratelimit-reset': '1435256061'}
iteration: 14, remaining 20, headers {'x-ratelimit-limit': '30', 'x-ratelimit-reset': '1435256061'}
iteration: 15, remaining 19, headers {'x-ratelimit-limit': '30', 'x-ratelimit-reset': '1435256061'}
iteration: 16, remaining 28, headers {'x-ratelimit-limit': '30', 'x-ratelimit-reset': '1435256072'}
iteration: 17, remaining 27, headers {'x-ratelimit-limit': '30', 'x-ratelimit-reset': '1435256072'}
iteration: 18, remaining 26, headers {'x-ratelimit-limit': '30', 'x-ratelimit-reset': '1435256072'}
iteration: 19, remaining 18, headers {'x-ratelimit-limit': '30', 'x-ratelimit-reset': '1435256061'}
iteration: 20, remaining 17, headers {'x-ratelimit-limit': '30', 'x-ratelimit-reset': '1435256061'}
iteration: 21, remaining 16, headers {'x-ratelimit-limit': '30', 'x-ratelimit-reset': '1435256061'}
iteration: 22, remaining 15, headers {'x-ratelimit-limit': '30', 'x-ratelimit-reset': '1435256061'}
iteration: 23, remaining 14, headers {'x-ratelimit-limit': '30', 'x-ratelimit-reset': '1435256061'}
iteration: 24, remaining 25, headers {'x-ratelimit-limit': '30', 'x-ratelimit-reset': '1435256060'}
iteration: 25, remaining 25, headers {'x-ratelimit-limit': '30', 'x-ratelimit-reset': '1435256072'}
iteration: 26, remaining 24, headers {'x-ratelimit-limit': '30', 'x-ratelimit-reset': '1435256060'}
iteration: 27, remaining 23, headers {'x-ratelimit-limit': '30', 'x-ratelimit-reset': '1435256060'}
iteration: 28, remaining 13, headers {'x-ratelimit-limit': '30', 'x-ratelimit-reset': '1435256061'}
iteration: 29, remaining 12, headers {'x-ratelimit-limit': '30', 'x-ratelimit-reset': '1435256061'}
iteration: 30, remaining 22, headers {'x-ratelimit-limit': '30', 'x-ratelimit-reset': '1435256060'}
iteration: 31, remaining 24, headers {'x-ratelimit-limit': '30', 'x-ratelimit-reset': '1435256072'}
iteration: 32, remaining 11, headers {'x-ratelimit-limit': '30', 'x-ratelimit-reset': '1435256061'}
iteration: 33, remaining 21, headers {'x-ratelimit-limit': '30', 'x-ratelimit-reset': '1435256060'}
iteration: 34, remaining 20, headers {'x-ratelimit-limit': '30', 'x-ratelimit-reset': '1435256060'}
iteration: 35, remaining 10, headers {'x-ratelimit-limit': '30', 'x-ratelimit-reset': '1435256061'}
iteration: 36, remaining 9, headers {'x-ratelimit-limit': '30', 'x-ratelimit-reset': '1435256061'}
iteration: 37, remaining 19, headers {'x-ratelimit-limit': '30', 'x-ratelimit-reset': '1435256060'}
iteration: 38, remaining 8, headers {'x-ratelimit-limit': '30', 'x-ratelimit-reset': '1435256061'}
iteration: 39, remaining 7, headers {'x-ratelimit-limit': '30', 'x-ratelimit-reset': '1435256061'}
iteration: 40, remaining 18, headers {'x-ratelimit-limit': '30', 'x-ratelimit-reset': '1435256060'}
iteration: 41, remaining 6, headers {'x-ratelimit-limit': '30', 'x-ratelimit-reset': '1435256061'}
iteration: 42, remaining 5, headers {'x-ratelimit-limit': '30', 'x-ratelimit-reset': '1435256061'}
iteration: 43, remaining 29, headers {'x-ratelimit-limit': '30', 'x-ratelimit-reset': '1435256120'}
iteration: 44, remaining 28, headers {'x-ratelimit-limit': '30', 'x-ratelimit-reset': '1435256120'}
iteration: 45, remaining 23, headers {'x-ratelimit-limit': '30', 'x-ratelimit-reset': '1435256072'}
iteration: 46, remaining 27, headers {'x-ratelimit-limit': '30', 'x-ratelimit-reset': '1435256120'}
iteration: 47, remaining 22, headers {'x-ratelimit-limit': '30', 'x-ratelimit-reset': '1435256072'}
iteration: 48, remaining 21, headers {'x-ratelimit-limit': '30', 'x-ratelimit-reset': '1435256072'}
iteration: 49, remaining 29, headers {'x-ratelimit-limit': '30', 'x-ratelimit-reset': '1435256128'}
iteration: 50, remaining 26, headers {'x-ratelimit-limit': '30', 'x-ratelimit-reset': '1435256120'}
Any idea what might be causing this?
Thanks!
In the shared limit docs, the scoping function doesn't have an argument:
def host_scope():
return request.host
host_limit = limiter.shared_limit("100/hour", scope=host_scope)
When I tried writing my own function with that signature, I got an error:
Traceback (most recent call last):
File "/usr/local/lib/python2.7/site-packages/flask_limiter/extension.py", line 383, in __check_request_limit
limit_scope = lim.scope or endpoint
File "/usr/local/lib/python2.7/site-packages/flask_limiter/extension.py", line 66, in scope
return self._scope(request.endpoint) if callable(self._scope) else self._scope
TypeError: test_scope() takes no arguments (1 given)
The docs should be updated to say that scope functions have one argument: the request endpoint being called.
The proposal is to add the following
X-RateLimit-Limit
: number of request allowedX-RateLimit-Remaining
: number of requests remainingX-RateLimit-Reset
: UTC epoch seconds when the window will be resetSince multiple rate limits can be applied to different routes (i.e. 5/second; 10/minute; 100/hour
) - the rate limit with either the highest or lowest time granularity can be chosen in the scenario when the request does not exceed the rate limit. However, for scenarios where the request does breach, the breached limit should be reflected in the headers.
Hi, I haven't been able to get flask_limiter to work as expected in my particular use case. I'm using flask.blueprint to instantiate the routes, but I create classes for my routes that inherit from Resource and would like to apply rate limiting to the "get", "post", etc procedures that get called (as in the example below). What I'm finding is that the custom rate limits set in the decorator applied to the "get" function, in this case, are always ignored and the defaults are used. I've also tried the custom decorators that others have posted and they're also ignored. Is there a way to apply custom rate limiting in my use case that I'm missing? It seems like it's not possible to apply rate limit per the request type, but where would I apply the decorator such that it covers the entire endpoint?
Also per the other issue comments I've read, it was my expectation also that adding the "@limiter.limit()" decorator would not just set a custom limit but also trigger the "check()" action.
`class PropertyList(Resource):
def __init__(self, permission_type = None):
g.permission_type = permission_type
self.parser = reqparse.RequestParser()
self.parser.add_argument('access_token', type=str, required=True, help='No \'access_token\' provided')
self.parser.add_argument('city_id', type=int, required=False, help='No \'city\' provided')
self.parser.add_argument('region_id', type=int, required=False, help='No \'zipcode\' provided')
self.parser.add_argument('room_types', type=str, action='append')
self.parser.add_argument('property_types', type=str, action='append')
self.context = self.parser.parse_args()
@authentication
@dblogging
@market_limit #shared_limit doesn't work
@limiter.limit("10/minute") #this gets ignored, defaults are still applied
# @limiter.rate_limit() # i tried some of the decorators listed in the issues comments here, but they didn't work in this case
# @limit_and_check("10 per minute") # i tried some of the decorators listed in the issues comments here, but they didn't work in this case
def get(self):
if not self.context.get('city_id') and not self.context.get('region_id'):
return response_bad_request("Must provide 'city_id' or 'region_id' parameter")
....`
How would I go about resetting all limits for the limiter? I do need this for testing. Thanks!
For example, when I set this up with flask-potion
to rate-limit a specific route it ends up rate-limiting all of my flask-potion
routes because they were all generated by the same route factory: they all have the same "view_func.__module__"."viewfunc.__name__"
(e.g. "flask_potion.routes.view"). I think this may be true for any kind of Flask framework that autogenerates views using a factory (because limiter
is detecting the factory's module path instead of the resource's/endpoint's).
I'm not sure the best way to fix this, but I have put together a hacky fix on a branch (bovee@08a5fc2) so I can ship my API. These changes break compatibility with the module-based limiting approach (and also require passing in an endpoint to the decorator, but I don't think there's any way around that).
The Python Limits package allows extending their base storage class and add my own storage types (eg redislite). The problem I am facing is that when I implement my own storage, flask-limiter gives a greenlight on all requests, even if exceeding the rate limit. I am not sure how to fix that issue.
My storage looks something like this:
import urlparse
from limits.storage import Storage
from redislite import Redis
import time
class LimitsRedisLite(Storage): # For Python Limits
STORAGE_SCHEME = "redislite"
def __init__(self, uri, **options):
self.redis_instance = Redis(urlparse.urlparse(uri).netloc)
def check(self):
return True
def get_expiry(self, key):
return (self.redis_instance.ttl(key) or 0) + time.time()
def incr(self, key, expiry, elastic_expiry=False):
if not self.redis_instance.exists(key):
self.redis_instance.set(key, 1)
self.redis_instance.expireat(key, int(time.time() + expiry))
else:
oldexp = self.get_expiry(key)
self.redis_instance.set(key, int(self.redis_instance.get(key))+1)
self.redis_instance.expireat(key, int(time.time() + oldexp))
return
def get(self, key):
return int(self.redis_instance.get(key))
def reset(self):
return self.redis_instance.flushdb()`
I'm trying to implement the rate limiter for my flask routes and I want to test it out and see if it works properly once the rate limit has been exceeded. When I issue a request on a route beyond the rate limit, I'm getting this crash. Since six
is being called, this looks like a Python 2 to 3 bug. I'm using Python 2.7.
I'm using a Class-based View routing system so I'm using the rate limiter like so, e.g.
class QueryView(BaseView):
"""Class describing API calls related to queries."""
decorators = [limiter.limit("1/minute")]
def index(self):
output = {'status': 1}
return jsonify(result=output)
When if run one of these routes more than once per minute, it should raise a 429 but crashes with the below error. Is this a real bug or is this related to my class-based view?
Traceback (most recent call last):
File "/Users/Brian/anaconda2/lib/python2.7/site-packages/flask/app.py", line 1994, in __call__
return self.wsgi_app(environ, start_response)
File "/Users/Brian/anaconda2/lib/python2.7/site-packages/flask/app.py", line 1985, in wsgi_app
response = self.handle_exception(e)
File "/Users/Brian/anaconda2/lib/python2.7/site-packages/flask/app.py", line 1540, in handle_exception
reraise(exc_type, exc_value, tb)
File "/Users/Brian/anaconda2/lib/python2.7/site-packages/flask/app.py", line 1982, in wsgi_app
response = self.full_dispatch_request()
File "/Users/Brian/anaconda2/lib/python2.7/site-packages/flask/app.py", line 1614, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/Users/Brian/anaconda2/lib/python2.7/site-packages/flask/app.py", line 1517, in handle_user_exception
reraise(exc_type, exc_value, tb)
File "/Users/Brian/anaconda2/lib/python2.7/site-packages/flask/app.py", line 1610, in full_dispatch_request
rv = self.preprocess_request()
File "/Users/Brian/anaconda2/lib/python2.7/site-packages/flask/app.py", line 1831, in preprocess_request
rv = func()
File "/Users/Brian/anaconda2/lib/python2.7/site-packages/flask_limiter/extension.py", line 427, in __check_request_limit
six.reraise(*sys.exc_info())
File "/Users/Brian/anaconda2/lib/python2.7/site-packages/flask_limiter/extension.py", line 395, in __check_request_limit
, lim.limit, lim.key_func(), limit_scope
TypeError: warning() takes at most 3 arguments (5 given)
Current situation:
When the decorator is loaded, a route is added to route_limits or dynamic_route_limits. The name of the route is defined like this:
name = "%s.%s" % (fn.__module__, fn.__name__)
Then for every 'before request', __check_request_limit is called. This function tries to find the correct name for the end point and see if there is a limit defined for it in either dictionaries:
endpoint = request.endpoint or ""
view_func = current_app.view_functions.get(endpoint, None)
name = ("%s.%s" % (
view_func.__module__, view_func.__name__
) if view_func else ""
This works well for standard routing. But now I want to use Flask-Limiter for Flask-Restful routes. And this creates a problem because the endpoint name when checking the limit will be different than the name that is determined when the decorator is defined.
For example, module myapp.api:
class HelloWorld(restful.Resource):
@limiter.limit("1/minute")
def get(self):
return {'hello': 'world'}
api.add_resource(HelloWorld, '/')
would create something like myapp.api.get as name for the limiter route, but is actually defined as myapp.api.helloworld by Flask-Restful. Of course this is not specific to Flask-Restful, and can occur any time the limiter is used on a function that is not also the actual endpoint, I think.
Any thoughts on how to fix this in a nice way? Maybe the checking of the rate limiting could be better done directly in the decorator instead of 1 central before_request?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.