Giter Site home page Giter Site logo

alisaifee / flask-limiter Goto Github PK

View Code? Open in Web Editor NEW
1.1K 12.0 123.0 1.56 MB

Rate Limiting extension for Flask

Home Page: https://flask-limiter.readthedocs.org

License: MIT License

Python 99.42% Shell 0.50% Makefile 0.07%
flask python rate-limiting memcached redis

flask-limiter's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

flask-limiter's Issues

python 3.4 compatibility

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

Egg fails to declare its dependencies on limits

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.

Reset limit upon user log-in

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

In Memory Fallback, should use, or at least allow to use per route limits

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.

Use with blueprints in a separate file

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?

Rate limit per token

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.

Fine grained blueprint limiter

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?

User Specific Rate Limits

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?

Error on import with Werkzeug v0.10

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.

Retry-After header reporting some optimistic, and occasionally strange values

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!

Memory leak when using in memory storage

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.

Support key prefix to share storage instances between apps/envs

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.

limits added to extension routes docs

Hello,

it wasn't clear to me how to add limits to extension routes in the documentation. Maybe this will help someone else...

Using limits with Blueprints or flask extension routes

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'])

Requirements missing

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...

global_limits are per-route, not shared

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!

Coverage.py warning

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

Include Retry-After header

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

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

Possible to fall back to in-memory storage?

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.

Can not install flask limiter using Python 2.6

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?

Unable to disable limiter when running unittests

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

Bundle decorator to run the check

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.)

Share limits between several routes

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.

Override 429 error page

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')

Returning 429 for OPTIONS request

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?

Security problem with `request.access_route`

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.

Comma delimited string no longer valid with limits 1.2.0

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.

version not recognized

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.

import error

  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'

Headers enabled by default

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.

unable to disable limiter during testing

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


Python 3.5 Errors

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

flask-limiter breaks with werkzeug 0.10

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".

Memcached backed limiter produces unexpected results running multi-machine

Hi,

We're seeing some very strange behavior when running flask-limiter in the following environment (backed by memcache):

compass architechture

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!

Scope function docs seem to be wrong

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.

add X-RateLimit- Headers for rate limit status.

The proposal is to add the following

  • X-RateLimit-Limit : number of request allowed
  • X-RateLimit-Remaining : number of requests remaining
  • X-RateLimit-Reset : UTC epoch seconds when the window will be reset

Since 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.

Unable to apply custom rate limit on get resource

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")
    ....`

Resetting Limiter

How would I go about resetting all limits for the limiter? I do need this for testing. Thanks!

Rate-limits all factory-generated views instead of specific ones

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).

Custom Storage

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()`

raising exception when rate limit is exceeded crashes

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)

Limit decorator for non standard routing (Flask-Restful)

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?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.