vutran1710 / PyrateLimiter

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

Make try_acquire awaitable #130

Closed RafaB6n closed 1 year ago

RafaB6n commented 1 year ago

At the moment when I run this:

import asyncio

from pyrate_limiter import Duration, Rate, Limiter

rate = Rate(5, Duration.SECOND * 2)
limiter = Limiter(rate)

async def main():
    await limiter.try_acquire('name', 1)

asyncio.run(main())

I get this error:

TypeError: object bool can't be used in 'await' expression

Not sure if this is all that's needed to fix it, but for me it seems to work.

vutran1710 commented 1 year ago

it was an error because in your code, try-acquire is indeed not awaitable because none of the underlying logic is async - and you cannot "await" a synchronous code.

therefore it is not a bug.

vutran1710 commented 1 year ago

if you like to await, you have to use something async inside the limiter (bucket, clock etc)

vutran1710 commented 1 year ago

to make your code work, just remove the await before limiter.try_acquire

RafaB6n commented 12 months ago

Should we this from the README then? grafik Link

Edit: Do you mean something like this?

import asyncio

from pyrate_limiter import Duration, Rate, Limiter, BucketAsyncWrapper, \
    InMemoryBucket, TimeClock
from pyrate_limiter.limiter import SingleBucketFactory

async def main():
    rate = Rate(5, Duration.SECOND * 2)
    bucket = BucketAsyncWrapper(InMemoryBucket([rate]))
    bucket_factory = SingleBucketFactory(bucket, TimeClock()) # Or is TimeAsyncClock needed here?
    limiter = Limiter(bucket_factory)

    await limiter.try_acquire('name', 1)

asyncio.run(main())

If yes, WDYT of adding this to the README?

vutran1710 commented 12 months ago

oh i think you are misunderstanding.

i wrote "if your bucket backend is async, you can use await".

but in the code, your bucket backend is InMemoryBucket and it is not async, so using await is not possible

vutran1710 commented 12 months ago

Regarding BucketAsyncWrapper, it is meant for testing only and it should not be used for production.

RafaB6n commented 12 months ago

Is there a suggested way to have a simple async capable in-memory bucket (for production use)?

vutran1710 commented 12 months ago

Is there a suggested way to have a simple async capable in-memory bucket (for production use)?

you can use TimeAsyncClock. But Im not sure why you want to do it.

RafaB6n commented 12 months ago

My aim is to have a simple way to be able to await try_acquire in an async function. Such that an async task is suspended, while it waits for try_acquire to allow it to continue.

await limiter.try_acquire(...)

Also:

Is there a suggested way to have a simple async capable in-memory bucket (for production use)?

you can use TimeAsyncClock. But Im not sure why you want to do it.

Are you sure about using TimeAsyncClock? It also seems to intended for testing only as well: https://github.com/vutran1710/PyrateLimiter/blob/master/pyrate_limiter/clocks.py#L25-L26

vutran1710 commented 12 months ago

My aim is to have a simple way to be able to await try_acquire in an async function. Such that an async task is suspended, while it waits for try_acquire to allow it to continue.


await limiter.try_acquire(...)

Also:

Is there a suggested way to have a simple async capable in-memory bucket (for production use)?

you can use TimeAsyncClock. But Im not sure why you want to do it.

Are you sure about using TimeAsyncClock? It also seems to intended for testing only as well:

https://github.com/vutran1710/PyrateLimiter/blob/master/pyrate_limiter/clocks.py#L25-L26

async & await are only meaningful when you deal with io-bound actions. in-memory-bucket is not io-bound because it does not make any network call other than processing computation in memory. so it does not make sense to await limiter using such bucket

of course, you still can write

async def example(): limiter.try_acquire("item")

vutran1710 commented 12 months ago

TimeAsyncClock is indeed made for testing, but it still can be used in your case without affecting performance.

RafaB6n commented 12 months ago

Ok, thanks a lot. Just to verify: This would be a possible way for my use case, (also for prod use)?

import asyncio

from pyrate_limiter import Duration, Rate, Limiter, InMemoryBucket, TimeAsyncClock
from pyrate_limiter.limiter import SingleBucketFactory

async def main():
    rate = Rate(5, Duration.SECOND * 2)
    bucket_factory = SingleBucketFactory(InMemoryBucket([rate]), TimeAsyncClock())
    limiter = Limiter(bucket_factory)

    await limiter.try_acquire('name', 1)

asyncio.run(main())
vutran1710 commented 12 months ago

Ok, thanks a lot.

Just to verify: This would be a possible way for my use case, (also for prod use)?


import asyncio

from pyrate_limiter import Duration, Rate, Limiter, InMemoryBucket, TimeAsyncClock

from pyrate_limiter.limiter import SingleBucketFactory

async def main():

    rate = Rate(5, Duration.SECOND * 2)

    bucket_factory = SingleBucketFactory(InMemoryBucket([rate]), TimeAsyncClock())

    limiter = Limiter(bucket_factory)

    await limiter.try_acquire('name', 1)

asyncio.run(main())

yes