pax_global_header00006660000000000000000000000064132132402270014506gustar00rootroot0000000000000052 comment=85f13f554cba300b0e97d3119b43f91fb0d9210e python-ratelimiter-1.2.0/000077500000000000000000000000001321324022700153465ustar00rootroot00000000000000python-ratelimiter-1.2.0/PKG-INFO000066400000000000000000000113311321324022700164420ustar00rootroot00000000000000Metadata-Version: 1.1 Name: ratelimiter Version: 1.2.0 Summary: Simple python rate limiting object Home-page: https://github.com/RazerM/ratelimiter Author: Frazer McLean Author-email: frazer@frazermclean.co.uk License: Apache Description: RateLimiter =========== |PyPI Version| |Build Status| |Python Version| |License| Simple Python module providing rate limiting. Overview -------- This package provides the ``ratelimiter`` module, which ensures that an operation will not be executed more than a given number of times on a given period. This can prove useful when working with third parties APIs which require for example a maximum of 10 requests per second. Usage ----- Decorator ~~~~~~~~~ .. code:: python from ratelimiter import RateLimiter @RateLimiter(max_calls=10, period=1) def do_something(): pass Context Manager ~~~~~~~~~~~~~~~ .. code:: python from ratelimiter import RateLimiter rate_limiter = RateLimiter(max_calls=10, period=1) for i in range(100): with rate_limiter: do_something() Callback ~~~~~~~~ The callback is called in its own thread, so your callback may use ``sleep`` without delaying the rate limiter. .. code:: python import time from ratelimiter import RateLimiter def limited(until): duration = int(round(until - time.time())) print('Rate limited, sleeping for {:d} seconds'.format(duration)) rate_limiter = RateLimiter(max_calls=2, period=3, callback=limited) for i in range(3): with rate_limiter: print('Iteration', i) Output: :: Iteration 0 Iteration 1 Rate limited, sleeping for 3 seconds Iteration 2 asyncio ~~~~~~~ The ``RateLimiter`` object can be used in an ``async with`` statement on Python 3.5+. Note that the callback must be a coroutine in this context. The coroutine callback is not called in a separate thread. .. code:: python import asyncio import time from ratelimiter import RateLimiter async def limited(until): duration = int(round(until - time.time())) print('Rate limited, sleeping for {:d} seconds'.format(duration)) async def coro(): rate_limiter = RateLimiter(max_calls=2, period=3, callback=limited) for i in range(3): async with rate_limiter: print('Iteration', i) loop = asyncio.get_event_loop() loop.run_until_complete(coro()) License ------- | Original work Copyright 2013 Arnaud Porterie | Modified work Copyright 2016 Frazer McLean Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. .. |PyPI Version| image:: http://img.shields.io/pypi/v/ratelimiter.svg?style=flat-square :target: https://pypi.python.org/pypi/ratelimiter .. |Build Status| image:: http://img.shields.io/travis/RazerM/ratelimiter/master.svg?style=flat-square :target: https://travis-ci.org/RazerM/ratelimiter .. |Python Version| image:: https://img.shields.io/badge/python-2.7%2C%203-brightgreen.svg?style=flat-square :target: https://www.python.org/downloads/ .. |License| image:: http://img.shields.io/badge/license-Apache-blue.svg?style=flat-square :target: https://github.com/RazerM/ratelimiter/blob/master/LICENSE Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: License :: OSI Approved :: Apache Software License python-ratelimiter-1.2.0/README000066400000000000000000000065601321324022700162350ustar00rootroot00000000000000RateLimiter =========== |PyPI Version| |Build Status| |Python Version| |License| Simple Python module providing rate limiting. Overview -------- This package provides the ``ratelimiter`` module, which ensures that an operation will not be executed more than a given number of times on a given period. This can prove useful when working with third parties APIs which require for example a maximum of 10 requests per second. Usage ----- Decorator ~~~~~~~~~ .. code:: python from ratelimiter import RateLimiter @RateLimiter(max_calls=10, period=1) def do_something(): pass Context Manager ~~~~~~~~~~~~~~~ .. code:: python from ratelimiter import RateLimiter rate_limiter = RateLimiter(max_calls=10, period=1) for i in range(100): with rate_limiter: do_something() Callback ~~~~~~~~ The callback is called in its own thread, so your callback may use ``sleep`` without delaying the rate limiter. .. code:: python import time from ratelimiter import RateLimiter def limited(until): duration = int(round(until - time.time())) print('Rate limited, sleeping for {:d} seconds'.format(duration)) rate_limiter = RateLimiter(max_calls=2, period=3, callback=limited) for i in range(3): with rate_limiter: print('Iteration', i) Output: :: Iteration 0 Iteration 1 Rate limited, sleeping for 3 seconds Iteration 2 asyncio ~~~~~~~ The ``RateLimiter`` object can be used in an ``async with`` statement on Python 3.5+. Note that the callback must be a coroutine in this context. The coroutine callback is not called in a separate thread. .. code:: python import asyncio import time from ratelimiter import RateLimiter async def limited(until): duration = int(round(until - time.time())) print('Rate limited, sleeping for {:d} seconds'.format(duration)) async def coro(): rate_limiter = RateLimiter(max_calls=2, period=3, callback=limited) for i in range(3): async with rate_limiter: print('Iteration', i) loop = asyncio.get_event_loop() loop.run_until_complete(coro()) License ------- | Original work Copyright 2013 Arnaud Porterie | Modified work Copyright 2016 Frazer McLean Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. .. |PyPI Version| image:: http://img.shields.io/pypi/v/ratelimiter.svg?style=flat-square :target: https://pypi.python.org/pypi/ratelimiter .. |Build Status| image:: http://img.shields.io/travis/RazerM/ratelimiter/master.svg?style=flat-square :target: https://travis-ci.org/RazerM/ratelimiter .. |Python Version| image:: https://img.shields.io/badge/python-2.7%2C%203-brightgreen.svg?style=flat-square :target: https://www.python.org/downloads/ .. |License| image:: http://img.shields.io/badge/license-Apache-blue.svg?style=flat-square :target: https://github.com/RazerM/ratelimiter/blob/master/LICENSE python-ratelimiter-1.2.0/ratelimiter.egg-info/000077500000000000000000000000001321324022700213615ustar00rootroot00000000000000python-ratelimiter-1.2.0/ratelimiter.egg-info/PKG-INFO000066400000000000000000000113311321324022700224550ustar00rootroot00000000000000Metadata-Version: 1.1 Name: ratelimiter Version: 1.2.0 Summary: Simple python rate limiting object Home-page: https://github.com/RazerM/ratelimiter Author: Frazer McLean Author-email: frazer@frazermclean.co.uk License: Apache Description: RateLimiter =========== |PyPI Version| |Build Status| |Python Version| |License| Simple Python module providing rate limiting. Overview -------- This package provides the ``ratelimiter`` module, which ensures that an operation will not be executed more than a given number of times on a given period. This can prove useful when working with third parties APIs which require for example a maximum of 10 requests per second. Usage ----- Decorator ~~~~~~~~~ .. code:: python from ratelimiter import RateLimiter @RateLimiter(max_calls=10, period=1) def do_something(): pass Context Manager ~~~~~~~~~~~~~~~ .. code:: python from ratelimiter import RateLimiter rate_limiter = RateLimiter(max_calls=10, period=1) for i in range(100): with rate_limiter: do_something() Callback ~~~~~~~~ The callback is called in its own thread, so your callback may use ``sleep`` without delaying the rate limiter. .. code:: python import time from ratelimiter import RateLimiter def limited(until): duration = int(round(until - time.time())) print('Rate limited, sleeping for {:d} seconds'.format(duration)) rate_limiter = RateLimiter(max_calls=2, period=3, callback=limited) for i in range(3): with rate_limiter: print('Iteration', i) Output: :: Iteration 0 Iteration 1 Rate limited, sleeping for 3 seconds Iteration 2 asyncio ~~~~~~~ The ``RateLimiter`` object can be used in an ``async with`` statement on Python 3.5+. Note that the callback must be a coroutine in this context. The coroutine callback is not called in a separate thread. .. code:: python import asyncio import time from ratelimiter import RateLimiter async def limited(until): duration = int(round(until - time.time())) print('Rate limited, sleeping for {:d} seconds'.format(duration)) async def coro(): rate_limiter = RateLimiter(max_calls=2, period=3, callback=limited) for i in range(3): async with rate_limiter: print('Iteration', i) loop = asyncio.get_event_loop() loop.run_until_complete(coro()) License ------- | Original work Copyright 2013 Arnaud Porterie | Modified work Copyright 2016 Frazer McLean Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. .. |PyPI Version| image:: http://img.shields.io/pypi/v/ratelimiter.svg?style=flat-square :target: https://pypi.python.org/pypi/ratelimiter .. |Build Status| image:: http://img.shields.io/travis/RazerM/ratelimiter/master.svg?style=flat-square :target: https://travis-ci.org/RazerM/ratelimiter .. |Python Version| image:: https://img.shields.io/badge/python-2.7%2C%203-brightgreen.svg?style=flat-square :target: https://www.python.org/downloads/ .. |License| image:: http://img.shields.io/badge/license-Apache-blue.svg?style=flat-square :target: https://github.com/RazerM/ratelimiter/blob/master/LICENSE Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: License :: OSI Approved :: Apache Software License python-ratelimiter-1.2.0/ratelimiter.egg-info/SOURCES.txt000066400000000000000000000003361321324022700232470ustar00rootroot00000000000000README ratelimiter.py setup.py ratelimiter.egg-info/PKG-INFO ratelimiter.egg-info/SOURCES.txt ratelimiter.egg-info/dependency_links.txt ratelimiter.egg-info/requires.txt ratelimiter.egg-info/top_level.txt tests/__init__.pypython-ratelimiter-1.2.0/ratelimiter.egg-info/dependency_links.txt000066400000000000000000000000011321324022700254270ustar00rootroot00000000000000 python-ratelimiter-1.2.0/ratelimiter.egg-info/requires.txt000066400000000000000000000001011321324022700237510ustar00rootroot00000000000000 [test] pytest>=3.0 [test:python_version>="3.5"] pytest-asyncio python-ratelimiter-1.2.0/ratelimiter.egg-info/top_level.txt000066400000000000000000000000141321324022700241060ustar00rootroot00000000000000ratelimiter python-ratelimiter-1.2.0/ratelimiter.py000066400000000000000000000112471321324022700202460ustar00rootroot00000000000000# Original work Copyright 2013 Arnaud Porterie # Modified work Copyright 2016 Frazer McLean # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import collections import functools import threading import time import sys from textwrap import dedent try: import asyncio except ImportError: asyncio = None __author__ = 'Frazer McLean ' __version__ = '1.2.0' __license__ = 'Apache' __description__ = 'Simple python rate limiting object' PY35 = sys.version_info >= (3, 5) class RateLimiter(object): """Provides rate limiting for an operation with a configurable number of requests for a time period. """ def __init__(self, max_calls, period=1.0, callback=None): """Initialize a RateLimiter object which enforces as much as max_calls operations on period (eventually floating) number of seconds. """ if period <= 0: raise ValueError('Rate limiting period should be > 0') if max_calls <= 0: raise ValueError('Rate limiting number of calls should be > 0') # We're using a deque to store the last execution timestamps, not for # its maxlen attribute, but to allow constant time front removal. self.calls = collections.deque() self.period = period self.max_calls = max_calls self.callback = callback self._lock = threading.Lock() self._alock = None # Lock to protect creation of self._alock self._init_lock = threading.Lock() def _init_async_lock(self): with self._init_lock: if self._alock is None: self._alock = asyncio.Lock() def __call__(self, f): """The __call__ function allows the RateLimiter object to be used as a regular function decorator. """ @functools.wraps(f) def wrapped(*args, **kwargs): with self: return f(*args, **kwargs) return wrapped def __enter__(self): with self._lock: # We want to ensure that no more than max_calls were run in the allowed # period. For this, we store the last timestamps of each call and run # the rate verification upon each __enter__ call. if len(self.calls) >= self.max_calls: until = time.time() + self.period - self._timespan if self.callback: t = threading.Thread(target=self.callback, args=(until,)) t.daemon = True t.start() sleeptime = until - time.time() if sleeptime > 0: time.sleep(sleeptime) return self def __exit__(self, exc_type, exc_val, exc_tb): with self._lock: # Store the last operation timestamp. self.calls.append(time.time()) # Pop the timestamp list front (ie: the older calls) until the sum goes # back below the period. This is our 'sliding period' window. while self._timespan >= self.period: self.calls.popleft() if PY35: # We have to exec this due to syntax errors on earlier versions. aenter_code = dedent(""" async def __aenter__(self): if self._alock is None: self._init_async_lock() with await self._alock: # We want to ensure that no more than max_calls were run in the allowed # period. For this, we store the last timestamps of each call and run # the rate verification upon each __enter__ call. if len(self.calls) >= self.max_calls: until = time.time() + self.period - self._timespan if self.callback: asyncio.ensure_future(self.callback(until)) sleeptime = until - time.time() if sleeptime > 0: await asyncio.sleep(sleeptime) return self """) exec(aenter_code) __aexit__ = asyncio.coroutine(__exit__) @property def _timespan(self): return self.calls[-1] - self.calls[0] python-ratelimiter-1.2.0/setup.cfg000066400000000000000000000000731321324022700171670ustar00rootroot00000000000000[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 python-ratelimiter-1.2.0/setup.py000066400000000000000000000030621321324022700170610ustar00rootroot00000000000000# Original work Copyright 2013 Arnaud Porterie # Modified work Copyright 2015 Frazer McLean # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from setuptools import setup import re FILE = 'ratelimiter.py' init_data = open(FILE).read() metadata = dict(re.findall("__([a-z]+)__ = '([^']+)'", init_data)) AUTHOR_EMAIL = metadata['author'] VERSION = metadata['version'] LICENSE = metadata['license'] DESCRIPTION = metadata['description'] AUTHOR, EMAIL = re.match(r'(.*) <(.*)>', AUTHOR_EMAIL).groups() extras_require = dict() extras_require['test'] = { 'pytest>=3.0', } extras_require['test:python_version>="3.5"'] = {'pytest-asyncio'} setup( name='ratelimiter', version=VERSION, description=DESCRIPTION, long_description=open('README').read(), author=AUTHOR, author_email=EMAIL, url='https://github.com/RazerM/ratelimiter', py_modules=['ratelimiter'], classifiers=[ 'Development Status :: 5 - Production/Stable', 'License :: OSI Approved :: Apache Software License', ], license=LICENSE, extras_require=extras_require) python-ratelimiter-1.2.0/tests/000077500000000000000000000000001321324022700165105ustar00rootroot00000000000000python-ratelimiter-1.2.0/tests/__init__.py000066400000000000000000000000001321324022700206070ustar00rootroot00000000000000