Skip to content

Commit

Permalink
Refactor shift methods (#253)
Browse files Browse the repository at this point in the history
  • Loading branch information
asvetlov authored Nov 1, 2021
1 parent 9ea9f9b commit 655b5d0
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 42 deletions.
40 changes: 25 additions & 15 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
CHANGES
=======

4.0.0 (2019-11-XX)
4.0.0 (2021-11-01)
------------------

* Add the elapsed property (#24)
* Implemented ``timeout_at(deadline)`` (#117)

* Implement `timeout.at(when)` (#117)
* Supported ``timeout.deadline`` and ``timeout.expired`` properties.

* Deprecate synchronous context manager usage
* Drooped ``timeout.remaining`` property: it can be calculated as
``timeout.deadline - loop.time()``

* Dropped ``timeout.timeout`` property that returns a relative timeout based on the
timeout object creation time; the absolute ``timeout.deadline`` should be used
instead.

* Added the deadline modification methods: ``timeout.reject()``,
``timeout.shift(delay)``, ``timeout.update(deadline)``.

* Deprecated synchronous context manager usage

3.0.1 (2018-10-09)
------------------
Expand All @@ -31,29 +41,29 @@ CHANGES
2.0.0 (2017-10-09)
------------------

* Changed `timeout <= 0` behaviour
* Changed ``timeout <= 0`` behaviour

* Backward incompatibility change, prior this version `0` was
shortcut for `None`
* when timeout <= 0 `TimeoutError` raised faster
* Backward incompatibility change, prior this version ``0`` was
shortcut for ``None``
* when timeout <= 0 ``TimeoutError`` raised faster

1.4.0 (2017-09-09)
------------------

* Implement `remaining` property (#20)
* Implement ``remaining`` property (#20)

* If timeout is not started yet or started unconstrained:
`remaining` is `None`
* If timeout is expired: `remaining` is `0.0`
* All others: roughly amount of time before `TimeoutError` is triggered
``remaining`` is ``None``
* If timeout is expired: ``remaining`` is ``0.0``
* All others: roughly amount of time before ``TimeoutError`` is triggered

1.3.0 (2017-08-23)
------------------

* Don't suppress nested exception on timeout. Exception context points
on cancelled line with suspended `await` (#13)
on cancelled line with suspended ``await`` (#13)

* Introduce `.timeout` property (#16)
* Introduce ``.timeout`` property (#16)

* Add methods for using as async context manager (#9)

Expand All @@ -74,7 +84,7 @@ CHANGES
1.1.0 (2016-10-20)
------------------

* Rename to `async-timeout`
* Rename to ``async-timeout``

1.0.0 (2016-09-09)
------------------
Expand Down
4 changes: 2 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@ Not finished yet timeout can be rescheduled by ``shift_by()``
or ``shift_to()`` methods::

async with timeout(1.5) as cm:
cm.shift_by(1) # add another second on waiting
cm.shift_to(loop.time() + 5) # reschedule to now+5 seconds
cm.shift(1) # add another second on waiting
cm.update(loop.time() + 5) # reschedule to now+5 seconds

Rescheduling is forbidden if the timeout is expired or after exit from ``async with``
code block.
Expand Down
27 changes: 18 additions & 9 deletions async_timeout/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def timeout(delay: Optional[float]) -> "Timeout":
def timeout_at(deadline: Optional[float]) -> "Timeout":
"""Schedule the timeout at absolute time.
deadline arguments points on the time in the same clock system
deadline argument points on the time in the same clock system
as loop.time().
Please note: it is not POSIX time but a time with
Expand Down Expand Up @@ -95,7 +95,7 @@ def __init__(
if deadline is None:
self._deadline = None # type: Optional[float]
else:
self.shift_to(deadline)
self.update(deadline)

def __enter__(self) -> "Timeout":
warnings.warn(
Expand Down Expand Up @@ -150,19 +150,28 @@ def _reject(self) -> None:
self._timeout_handler.cancel()
self._timeout_handler = None

def shift_by(self, delay: float) -> None:
def shift(self, delay: float) -> None:
"""Advance timeout on delay seconds.
The delay can be negative.
Raise RuntimeError if shift is called when deadline is not scheduled
"""
now = self._loop.time()
self.shift_to(now + delay)
deadline = self._deadline
if deadline is None:
raise RuntimeError("cannot shift timeout if deadline is not scheduled")
self.update(deadline + delay)

def update(self, deadline: float) -> None:
"""Set deadline to absolute value.
deadline argument points on the time in the same clock system
as loop.time().
def shift_to(self, deadline: float) -> None:
"""Advance timeout on the abdelay seconds.
If new deadline is in the past the timeout is raised immediatelly.
If new deadline is in the past
the timeout is raised immediatelly.
Please note: it is not POSIX time but a time with
undefined starting base, e.g. the time of the system power on.
"""
if self._state == _State.EXIT:
raise RuntimeError("cannot reschedule after exit from context manager")
Expand Down
30 changes: 14 additions & 16 deletions tests/test_timeout.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,34 +286,32 @@ async def test_async_no_timeout() -> None:


@pytest.mark.asyncio
async def test_shift_to() -> None:
async def test_shift() -> None:
loop = asyncio.get_event_loop()
t0 = loop.time()
async with timeout(1) as cm:
t1 = loop.time()
assert cm.deadline is not None
assert t0 + 1 <= cm.deadline <= t1 + 1
cm.shift_to(t1 + 1)
assert t1 + 1 <= cm.deadline <= t1 + 1.1
cm.shift(1)
assert t0 + 2 <= cm.deadline <= t0 + 2.1


@pytest.mark.asyncio
async def test_shift_by() -> None:
loop = asyncio.get_event_loop()
t0 = loop.time()
async with timeout(1) as cm:
t1 = loop.time()
assert cm.deadline is not None
assert t0 + 1 <= cm.deadline <= t1 + 1
cm.shift_by(1)
assert t1 + 0.999 <= cm.deadline <= t1 + 1.1
async def test_shift_nonscheduled() -> None:
async with timeout(None) as cm:
with pytest.raises(
RuntimeError,
match="cannot shift timeout if deadline is not scheduled",
):
cm.shift(1)


@pytest.mark.asyncio
async def test_shift_by_negative_expired() -> None:
async with timeout(1) as cm:
with pytest.raises(asyncio.CancelledError):
cm.shift_by(-1)
cm.shift(-1)


@pytest.mark.asyncio
Expand All @@ -322,7 +320,7 @@ async def test_shift_by_expired() -> None:
with pytest.raises(asyncio.CancelledError):
await asyncio.sleep(10)
with pytest.raises(RuntimeError, match="cannot reschedule expired timeout"):
cm.shift_by(10)
cm.shift(10)


@pytest.mark.asyncio
Expand All @@ -333,7 +331,7 @@ async def test_shift_to_expired() -> None:
with pytest.raises(asyncio.CancelledError):
await asyncio.sleep(10)
with pytest.raises(RuntimeError, match="cannot reschedule expired timeout"):
cm.shift_to(t0 + 10)
cm.update(t0 + 10)


@pytest.mark.asyncio
Expand All @@ -343,7 +341,7 @@ async def test_shift_by_after_cm_exit() -> None:
with pytest.raises(
RuntimeError, match="cannot reschedule after exit from context manager"
):
cm.shift_by(1)
cm.shift(1)


@pytest.mark.asyncio
Expand Down

0 comments on commit 655b5d0

Please sign in to comment.