vutran1710 / PyrateLimiter

⚔️Python Rate-Limiter using Leaky-Bucket Algorithm Family
https://pyratelimiter.readthedocs.io
MIT License
338 stars 36 forks source link

Feature request: Add an option to set a default identity for a Limiter #35

Closed JWCook closed 3 years ago

JWCook commented 3 years ago

My current use case only requires one bucket/identity, which I imagine is fairly common. It would be convenient to be able to set a default identity on a Limiter object, which would be used if you don't specify one in try_acquire() or ratelimit().

This would be especially useful if you're using the limiter in multiple locations. For example, instead of this:

limiter = RequestRate(5, Duration.SECOND)

def function_1():
    with limiter.ratelimit('my_bucket'):
        # ...

def function_2():
    with limiter.ratelimit('my_bucket'):
        # ...

def function_3():
    with limiter.ratelimit('my_bucket'):
        # ...

I'd like to do this:

limiter = RequestRate(5, Duration.SECOND, default_identity='my_bucket')

def function_1():
    with limiter.ratelimit():
        # ...

def function_2():
    with limiter.ratelimit():
        # ...

def function_3():
    with limiter.ratelimit():
        # ...

I think this would just require updating Limiter._init_buckets(). If that sounds good to you, I can make a PR for this.

vutran1710 commented 3 years ago

If I were you in this case, I would either

Meanwhile, I really don't want to touch the constructor. So if you can go with one of the two points above in a PR, I think we can have such feature.

JWCook commented 3 years ago

Fair enough. Maybe this doesn't need changes in pyrate-limiter, then, and I can just add a subclass in my own project(s).

Just curious, what is the intended use case for multiple buckets on the same Limiter? Would that be for tracking separate limits for requests to multiple hosts? Or for some use case other than HTTP requests?

vutran1710 commented 3 years ago

Fair enough. Maybe this doesn't need changes in pyrate-limiter, then, and I can just add a subclass in my own project(s).

Just curious, what is the intended use case for multiple buckets on the same Limiter? Would that be for tracking separate limits for requests to multiple hosts? Or for some use case other than HTTP requests?

Yes. Back in the day I started developing this, I was working on a microservice architecture, where a single backend app might send multiple requests to different apps.

JWCook commented 3 years ago

Okay, that makes sense. I asked because I was considering making an extension to integrate this with the requests library, probably as a separate package. It would work as a transport adapter that would run requests within a Limiter.ratelimit contextmanager. That would potentially allow setting different rate limits for different hosts.

Usage might look something like:

from requests import Session
from requests_pyrate_limiter import LimiterAdapter  # or some other package name
from pyrate_limiter import Duration, RequestRate

adapter = LimiterAdapter(RequestRate(10, Duration.SECOND))
session = Session()

# Apply rate-limiting to all requests (maybe using a separate bucket per host?)
session.mount('http://', adapter)
session.mount('https://', adapter)

# Apply a different rate limit to a specific host
adapter_2 = LimiterAdapter(RequestRate(2, Duration.SECOND))
session.mount('https://api.some_site.com', adapter_2)

# Make rate-limited requests that stay within 2 requests per second
for user_id in range(100):
    response = session.get(f'https://api.some_site.com/v1/users/{user_id}')

Personally I would find something like that convenient because all my settings would then be contained in one session object, along with other things like retry behavior, cookies, hooks, etc.

What do you think?

vutran1710 commented 3 years ago

Okay, that makes sense. I asked because I was considering making an extension to integrate this with the requests library, probably as a separate package. It would work as a transport adapter that would run requests within a Limiter.ratelimit contextmanager. That would potentially allow setting different rate limits for different hosts.

Usage might look something like:


from requests import Session

from requests_pyrate_limiter import LimiterAdapter  # or some other package name

from pyrate_limiter import Duration, RequestRate

adapter = LimiterAdapter(RequestRate(10, Duration.SECOND))

session = Session()

# Apply rate-limiting to all requests (maybe using a separate bucket per host?)

session.mount('http://', adapter)

session.mount('https://', adapter)

# Apply a different rate limit to a specific host

adapter_2 = LimiterAdapter(RequestRate(2, Duration.SECOND))

session.mount('https://api.some_site.com', adapter_2)

# Make rate-limited requests that stay within 2 requests per second

for user_id in range(100):

    response = session.get(f'https://api.some_site.com/v1/users/{user_id}')

Personally I would find something like that convenient because all my settings would then be contained in one session object, along with other things like retry behavior, cookies, hooks, etc.

What do you think?

That looks nice. Definitely something I would use on my daily work

JWCook commented 3 years ago

Cool, I'll ping you when I have something ready to test out.

JWCook commented 3 years ago

@vutran1710 Fyi, I have a work-in-progress repo for this here: https://github.com/JWCook/requests-ratelimiter