pax_global_header00006660000000000000000000000064131244114730014512gustar00rootroot0000000000000052 comment=c52af67f888b37683737915a545155417a9d3a3f python-waiting-1.4.1/000077500000000000000000000000001312441147300144765ustar00rootroot00000000000000python-waiting-1.4.1/.gitignore000066400000000000000000000004171312441147300164700ustar00rootroot00000000000000#### Python *.py[co] # Packages *.egg *.egg-info dist build eggs parts bin develop-eggs .installed.cfg # Installer logs pip-log.txt # Unit test / coverage reports .coverage .tox #### Eclipse *.pydevproject .project .metadata #### Emacs \#*\# .#* .achievements .env python-waiting-1.4.1/.travis.yml000066400000000000000000000010431312441147300166050ustar00rootroot00000000000000language: python sudo: false python: - 2.6 - 2.7 - 3.3 - 3.4 - 3.5 - 3.6 env: matrix: - ADDITIONAL_SETUP="pip install flux" - ADDITIONAL_SETUP=":" install: - pip install -e . - pip install -r test_requirements.txt - $ADDITIONAL_SETUP script: py.test tests deploy: provider: pypi user: vmalloc password: secure: ujgrZvEm1GIqAJ44g54kJol51/odRYOt3fcqobzpR1LUbiUzI/h0YeuoIKYlCJPqhdGZK3k1C6JrE2dHxZAcP5ozncRgRtBhE0mNbrmnmbkZJxlfCWg0ZHeA0tMPUk53qgB8drCFc6TstAwvDIdgUAtfvqNpQjY49ECciXlyE7Q= on: tags: true repo: vmalloc/waiting python-waiting-1.4.1/LICENSE000066400000000000000000000027071312441147300155110ustar00rootroot00000000000000Copyright (c) 2011, Rotem Yaari All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of organization nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY Rotem Yaari ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Rotem Yaari BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.python-waiting-1.4.1/MANIFEST.in000066400000000000000000000000561312441147300162350ustar00rootroot00000000000000include MANIFEST.in tests/* tox.ini scripts/* python-waiting-1.4.1/README.rst000066400000000000000000000063111312441147300161660ustar00rootroot00000000000000Overview -------- *waiting* is a small library for waiting for stuff to happen. It basically waits for a function to return **True**, in various modes. *Waiting* is compatible with `flux `_ for simulated timelines. Usage ----- The most basic usage is when you have a function you want to wait for:: >>> predicate = lambda : True Waiting forever is very simple:: >>> from waiting import wait, TimeoutExpired >>> wait(predicate) True If your predicate returns a value, it will be returned as the result of wait():: >>> result = object() >>> wait(lambda: result) is result True A *timeout* parameter can also be specified:: >>> wait(predicate, timeout_seconds=10.5) True When a timeout expires without the predicate being fullfilled, an exception is thrown:: >>> try: ... wait(lambda : False, timeout_seconds=0) ... except TimeoutExpired: ... # expired! ... pass ... else: ... assert False Sleeping polls the predicate at a certain interval (by default 1 second). The interval can be changed with the *sleep_seconds* argument:: >>> wait(predicate, sleep_seconds=20) True When waiting for multiple predicates, *waiting* provides two simple facilities to help aggregate them: **ANY** and **ALL**. They resemble Python's built-in *any()* and *all()*, except that they don't call a predicate once it has been satisfied (this is useful when the predicates are inefficient and take time to complete):: >>> from waiting import wait, ANY, ALL >>> wait(ANY([predicate, predicate])) True >>> wait(ALL([predicate, predicate])) True TimeoutExpired exceptions, by default, don't tell you much about what didn't happen that you were expecting. To fix that, use the *waiting_for* argument:: >>> try: ... wait(lambda : False, timeout_seconds=0, waiting_for="something that will never happen") #doctest: +ELLIPSIS ... except TimeoutExpired as e: ... print(e) Timeout of 0 seconds expired waiting for something that will never happen Exponential backoff is supported for the sleep interval:: >>> from waiting import wait >>> wait(predicate, sleep_seconds=(1, 100)) # sleep 1, 2, 4, 8, 16, 32, 64, 100, 100, .... True >>> wait(predicate, sleep_seconds=(1, 100, 3)) # sleep 1, 3, 9, 27, 81, 100, 100, 100 .... True >>> wait(predicate, sleep_seconds=(1, None)) # sleep 1, 2, 4, 6, .... (infinity) True >>> wait(predicate, sleep_seconds=(1, None, 4)) # sleep 1, 4, 16, 64, ... (infinity) True If your predicate might raise certain exceptions you wish to ignore, you may use ``expected_exceptions`` to ignore them:: >>> from waiting import wait >>> wait(predicate, expected_exceptions=ValueError) True >>> wait(predicate, expected_exceptions=(ValueError, AttributeError)) True If you'd like to maintain updates while waiting for a predicate to complete, you may use ``on_poll`` to pass a function to perform some behavior after every sleep. By default, this is a no-op. >>> import logging >>> from waiting import wait >>> try: ... wait(lambda: False, timeout_seconds=5, # Timeout after 5 seconds ... on_poll=lambda: logging.warn("Waiting...")) # Log "Waiting..." six times. ... except TimeoutExpired: ... pass ... else: ... assert False python-waiting-1.4.1/setup.py000066400000000000000000000015031312441147300162070ustar00rootroot00000000000000import os from setuptools import setup, find_packages with open(os.path.join(os.path.dirname(__file__), "waiting", "__version__.py")) as version_file: exec(version_file.read()) setup(name="waiting", classifiers = [ "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", ], description="Utility for waiting for stuff to happen", license="BSD", author="Rotem Yaari", author_email="vmalloc@gmail.com", version=__version__, packages=find_packages(exclude=["tests"]), install_requires=[], tests_require=["pyforge"], scripts=[], namespace_packages=[] ) python-waiting-1.4.1/test_requirements.txt000066400000000000000000000000171312441147300210170ustar00rootroot00000000000000pytest pyforge python-waiting-1.4.1/tests/000077500000000000000000000000001312441147300156405ustar00rootroot00000000000000python-waiting-1.4.1/tests/conftest.py000066400000000000000000000043651312441147300200470ustar00rootroot00000000000000from forge import Forge import logging import waiting import pytest _logger = logging.getLogger(__name__) @pytest.fixture def forge(request): returned = Forge() @request.addfinalizer def cleanup(): returned.verify() returned.restore_all_replacements() return returned @pytest.fixture def timeline(forge): class VirtualTimeline(object): class FirstTestException(Exception): pass class SecondTestException(Exception): pass def __init__(self): super(VirtualTimeline, self).__init__() self.virtual_time = 0 self.sleeps_performed = [] self.predicate_satisfied = False self.satisfy_at_time = None self.satisfy_after_time = None self.predicate_sleep = 0 def sleep(self, delta): self.sleeps_performed.append(delta) self.virtual_time += delta assert 1000 > len(self.sleeps_performed), 'Infinite loop' def time(self): return self.virtual_time def predicate(self): if self.satisfy_at_time is not None and self.satisfy_at_time == self.virtual_time: self.predicate_satisfied = True if self.satisfy_after_time is not None and self.satisfy_after_time <= self.virtual_time: self.predicate_satisfied = True self.virtual_time += self.predicate_sleep _logger.debug('Predicate: time is now %s', self.virtual_time) return self.predicate_satisfied def raising_predicate(self): if self.virtual_time == 0: raise self.FirstTestException() return self.predicate() def raising_two_exceptions_predicate(self): if self.virtual_time == 1: raise self.SecondTestException() return self.raising_predicate() returned = VirtualTimeline() forge.replace_with(waiting.time_module, "sleep", returned.sleep) forge.replace_with(waiting.time_module, "time", returned.time) forge.replace_with(waiting.deadlines.time_module, "time", returned.time) return returned @pytest.fixture def predicates(forge): return [forge.create_wildcard_mock() for i in range(5)] python-waiting-1.4.1/tests/test_predicate_sleeps.py000066400000000000000000000006741312441147300225730ustar00rootroot00000000000000import waiting import pytest @pytest.mark.parametrize('do_sleep', [True, False]) def test_predicate_sleeps_coelesce(timeline, do_sleep): timeline.predicate_sleep = 0.5 if do_sleep else 0 sleep_seconds = 2 expected_sleep = sleep_seconds - timeline.predicate_sleep timeline.satisfy_after_time = 10 waiting.wait(timeline.predicate, sleep_seconds=2) assert timeline.sleeps_performed == [expected_sleep for i in range(5)] python-waiting-1.4.1/tests/test_readme_doctest.py000066400000000000000000000005021312441147300222300ustar00rootroot00000000000000import os import doctest def test_readme_doctests(): readme_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "README.rst")) assert os.path.exists(readme_path) result = doctest.testfile(readme_path, module_relative=False) assert result.failed == 0, ('%s tests failed!' % result.failed) python-waiting-1.4.1/tests/test_waiting.py000066400000000000000000000117241312441147300207200ustar00rootroot00000000000000# coding=utf-8 import pytest import waiting from waiting.exceptions import IllegalArgumentError, TimeoutExpired @pytest.mark.parametrize('arg', [ ([1, 2, 4, 8, 16, 32, 64, 100, 100], (1, 100, 2)), ([1, 3, 9, 27, 81, 101, 101, 101], (1, 101, 3)), ([1, 2, 4, 8, 16, 32, 64, 128, 201], (1, 201)), ([1, 3, 9, 27, 81], (1, None, 3)), ([1, 2, 4, 8, 16, 32, 64, 128, 256, 512], (1, None)), ]) def test_exponential_sleep(timeline, arg): sleeps, sleep_seconds_arg = arg timeline.satisfy_after_time = sum(sleeps) waiting.wait(timeline.predicate, sleep_seconds=sleep_seconds_arg) assert sleeps == timeline.sleeps_performed def test_waiting_does_not_expire(timeline): num_tries = 9 sleep_seconds = 10 timeline.satisfy_at_time = num_tries * sleep_seconds waiting.wait(timeline.predicate, sleep_seconds=sleep_seconds, timeout_seconds=(sleep_seconds*num_tries)+1) def test_waiting_expires(timeline): with pytest.raises(waiting.TimeoutExpired): waiting.wait(timeline.predicate, sleep_seconds=3, timeout_seconds=5) # timeout_seconds intentionally not divided by sleep assert timeline.virtual_time == 5 def test_iterwait_sleep_time(timeline): sleep_seconds = 5 num_sleeps = 7 timeline.satisfy_at_time = sleep_seconds * num_sleeps previous_time = timeline.virtual_time for retry in waiting.iterwait(timeline.predicate, sleep_seconds=sleep_seconds): assert timeline.virtual_time == previous_time previous_time = timeline.virtual_time + sleep_seconds timeline.sleep(1.5) # shouldn't affect the sleep assert timeline.virtual_time == (num_sleeps * sleep_seconds) def test__wait_any(predicates, forge, timeline): for p in predicates: p().and_return(False) predicates[0]().and_return(True) forge.replay() waiting.wait(waiting.ANY(predicates)) def test_wait_any_satisfied_on_start(predicates, forge, timeline): for index, p in enumerate(predicates): works = (index == 2) p().and_return(works) if works: break forge.replay() waiting.wait(waiting.ANY(predicates)) assert timeline.virtual_time == 0 def test_wait_all(predicates, forge, timeline): for iteration in range(len(predicates) * 2, 0, -1): iteration -= 1 for index, p in reversed(list(enumerate(predicates))): if index > iteration: continue p().and_return(index == iteration) forge.replay() waiting.wait(waiting.ALL(predicates)) assert timeline.virtual_time == ((len(predicates) * 2) - 1) def test_no_handled_exceptions(timeline): with pytest.raises(timeline.FirstTestException): waiting.wait(timeline.raising_predicate) def test_another_handled_exceptions(timeline): with pytest.raises(timeline.SecondTestException): waiting.wait(timeline.raising_two_exceptions_predicate, expected_exceptions=timeline.FirstTestException) def test_one_class(timeline): timeline.satisfy_at_time = 3 waiting.wait(timeline.raising_predicate, expected_exceptions=timeline.FirstTestException) assert timeline.virtual_time == 3 def test_correct_tuple(timeline): timeline.satisfy_at_time = 3 waiting.wait(timeline.raising_two_exceptions_predicate, expected_exceptions=(timeline.FirstTestException, timeline.SecondTestException)) assert timeline.virtual_time == 3 def test_none(timeline): with pytest.raises(IllegalArgumentError): waiting.wait(timeline.raising_predicate, expected_exceptions=None) def test_non_tuple(timeline): with pytest.raises(IllegalArgumentError): waiting.wait(timeline.raising_predicate, expected_exceptions='Not a tuple') def test_unicode_logging(): unicode_string = u'symbols 올 д' timeout = 3 instance = TimeoutExpired(timeout, unicode_string) assert u'{0}'.format(instance) == u'Timeout of {0} seconds expired waiting for {1}'.format(timeout, unicode_string) @pytest.mark.parametrize(['exception', 'expected_exceptions'], [ (StopIteration, ()), (TimeoutExpired, StopIteration)]) def test_stop_iteration_inside_predicate(exception, expected_exceptions): def predicate(): raise StopIteration() with pytest.raises(exception): waiting.wait(predicate, timeout_seconds=0, expected_exceptions=expected_exceptions) def test_nexted_stop_iteration_preserve_exception(): expected_exception = StopIteration('foo') def predicate(): raise expected_exception with pytest.raises(StopIteration) as ex: waiting.wait(predicate, timeout_seconds=0) assert ex.value is expected_exception def test_nexted_stop_iteration_preserve_traceback(): expected_exception = StopIteration('foo') def predicate(): raise expected_exception with pytest.raises(StopIteration) as ex: waiting.wait(predicate, timeout_seconds=0) assert ' raise expected_exception' in ex.traceback[-1].statement.lines python-waiting-1.4.1/tox.ini000066400000000000000000000001531312441147300160100ustar00rootroot00000000000000[tox] envlist = py26,py27,py33,py34,py35,py36 [testenv] deps= -rtest_requirements.txt commands= py.test python-waiting-1.4.1/waiting/000077500000000000000000000000001312441147300161405ustar00rootroot00000000000000python-waiting-1.4.1/waiting/__init__.py000066400000000000000000000071311312441147300202530ustar00rootroot00000000000000import inspect import sys from .__version__ import __version__ from contextlib import contextmanager try: from flux import current_timeline as time_module except ImportError: import time as time_module from .deadlines import make_deadline as _make_deadline from .exceptions import TimeoutExpired, IllegalArgumentError, NestedStopIteration def wait(*args, **kwargs): result = _Result() try: for x in iterwait(result=result, *args, **kwargs): pass except NestedStopIteration as e: e.reraise() return result.result def iterwait(predicate, timeout_seconds=None, sleep_seconds=1, result=None, waiting_for=None, on_poll=None, expected_exceptions=()): if not isinstance(expected_exceptions, tuple) and not (isinstance(expected_exceptions, type) and issubclass(expected_exceptions, Exception)): raise IllegalArgumentError( 'expected_exceptions should be tuple or Exception subclass') if on_poll is not None and not callable(on_poll): raise IllegalArgumentError( 'on_poll should be callable') timeout = _make_deadline(timeout_seconds) if result is None: result = _Result() if waiting_for is None: waiting_for = str(predicate) sleep_generator = _get_sleep_generator(timeout, sleep_seconds) while True: with _end_sleeping(next(sleep_generator)) as cancel_sleep: try: result.result = predicate() if on_poll is not None: on_poll() except expected_exceptions: pass except StopIteration: exc_info = sys.exc_info() raise NestedStopIteration(exc_info) if result.result: cancel_sleep() return if timeout.is_expired(): raise TimeoutExpired(timeout_seconds, waiting_for) yield @contextmanager def _end_sleeping(total_seconds): deadline = _make_deadline(total_seconds) sleep_toggle = _SleepToggle() yield sleep_toggle if sleep_toggle.enabled: time_module.sleep(max(0, deadline.get_num_seconds_remaining())) class _SleepToggle(object): enabled = True def __call__(self): self.enabled = False class _Result(object): result = None def _get_sleep_generator(timeout, sleep_seconds): if type(sleep_seconds) not in (tuple, list): sleep_seconds = (sleep_seconds, sleep_seconds, 1) if len(sleep_seconds) <= 2: sleep_seconds = (sleep_seconds[0], sleep_seconds[1], 2) elif len(sleep_seconds) < 2: sleep_seconds = (sleep_seconds[0], None, 2) current_sleep, end_sleep, multiplier = sleep_seconds while True: time_remaining = timeout.get_num_seconds_remaining() if time_remaining is not None: current_sleep = min(time_remaining, current_sleep) current_sleep = max(0, current_sleep) yield current_sleep current_sleep *= multiplier if end_sleep is not None: current_sleep = min(end_sleep, current_sleep) class Aggregate(object): def __init__(self, predicates): super(Aggregate, self).__init__() self.predicates = list(predicates) class ANY(Aggregate): def __call__(self): return any(p() for p in self.predicates) class ALL(Aggregate): def __call__(self): for index in range(len(self.predicates), 0, -1): index -= 1 if self.predicates[index](): self.predicates.pop(index) return not self.predicates python-waiting-1.4.1/waiting/__version__.py000066400000000000000000000000261312441147300207710ustar00rootroot00000000000000__version__ = "1.4.1" python-waiting-1.4.1/waiting/deadlines.py000066400000000000000000000014241312441147300204430ustar00rootroot00000000000000try: from flux import current_timeline as time_module except ImportError: import time as time_module class Deadline(object): def is_expired(self): raise NotImplementedError() # pragma: no cover def get_num_seconds_remaining(self): return None class Within(Deadline): def __init__(self, seconds): super(Within, self).__init__() self._deadline = time_module.time() + seconds def is_expired(self): return time_module.time() >= self._deadline def get_num_seconds_remaining(self): return self._deadline - time_module.time() class Whenever(Deadline): def is_expired(self): return False def make_deadline(seconds): if seconds is None: return Whenever() return Within(seconds) python-waiting-1.4.1/waiting/exceptions.py000066400000000000000000000020741312441147300206760ustar00rootroot00000000000000import sys class TimeoutExpired(Exception): def __init__(self, timeout_seconds, what): super(TimeoutExpired, self).__init__(timeout_seconds, what) self._timeout_seconds = timeout_seconds self._what = what def __str__(self): return "Timeout of {0} seconds expired waiting for {1}".format(self._timeout_seconds, self._what) def __repr__(self): return "{0}: {1}".format(type(self).__name__, self) def __unicode__(self): return u"Timeout of {0} seconds expired waiting for {1}".format(self._timeout_seconds, self._what) class IllegalArgumentError(ValueError): pass class NestedStopIteration(Exception): def __init__(self, exc_info): self.exc_info = exc_info def reraise(self): if sys.version_info[0] == 3: self._reraise3() else: self._reraise2() def _reraise2(self): exec("raise self.exc_info[0], self.exc_info[1], self.exc_info[2]") def _reraise3(self): _, exc_value, tb = self.exc_info raise exc_value.with_traceback(tb)