Skip to content
Snippets Groups Projects
Commit 542cf99a authored by Piotr Maślanka's avatar Piotr Maślanka
Browse files

v2.17.8

parent ced639a6
No related branches found
No related tags found
No related merge requests found
# v2.17.8
* bugfix release for 2.17.8
* some modules were unimportable
__version__ = '2.17.8a1'
__version__ = '2.17.8'
......@@ -3,7 +3,7 @@ import typing as tp
from .monitor import Monitor
from .thread import Condition
from ...exceptions import WouldWaitMore
from ...time import measure
from ...time.measure import measure
Number = tp.Union[int, float]
......
......@@ -7,7 +7,7 @@ from satella.coding.recast_exceptions import rethrow_as
from satella.coding.concurrent.thread import Condition
from satella.coding.typing import T
from satella.exceptions import WouldWaitMore, Empty
from satella.time import measure
from satella.time.measure import measure
class PeekableQueue(tp.Generic[T]):
......
......@@ -6,7 +6,7 @@ from .atomic import AtomicNumber
from .futures import ExecutorWrapper
from .thread import Condition
from ...exceptions import WouldWaitMore
from ...time import measure
from ...time.measure import measure
def sync_threadpool(tpe: tp.Union[ExecutorWrapper, ThreadPoolExecutor],
......
......@@ -11,6 +11,7 @@ from threading import Condition as PythonCondition
from satella.coding.decorators import wraps
from ..typing import ExceptionList
from ...exceptions import ResourceLocked, WouldWaitMore
from satella.time.measure import measure
def call_in_separate_thread(*t_args, no_thread_attribute: bool = False,
......@@ -100,7 +101,7 @@ class Condition(PythonCondition):
:raises ResourceLocked: unable to acquire the underlying lock within specified timeout.
:raises WouldWaitMore: wait's timeout has expired
"""
from satella.time import measure, parse_time_string
from satella.time.misc import parse_time_string
if timeout is not None:
timeout = parse_time_string(timeout)
......@@ -481,8 +482,6 @@ class IntervalTerminableThread(TerminableThread, metaclass=ABCMeta):
"""
def run(self):
from satella.time import measure
self.prepare()
while not self._terminating:
with measure() as measurement:
......
......@@ -7,7 +7,7 @@ from satella.coding.recast_exceptions import log_exceptions
from .monitor import Monitor
from ..structures.heaps.time import TimeBasedHeap
from ..structures.singleton import Singleton
from ...time import parse_time_string
from ...time.parse import parse_time_string
logger = logging.getLogger(__name__)
......
from .measure import measure, TimeSignal
from .misc import ExponentialBackoff, time_us, time_ms, time_as_int, sleep
from .parse import parse_time_string
__all__ = ['measure', 'TimeSignal', 'ExponentialBackoff', 'time_us', 'time_ms',
'time_as_int', 'parse_time_string', 'sleep']
import copy
import inspect
import time
from ..exceptions import WouldWaitMore
from concurrent.futures._base import Future
import typing as tp
import warnings
from concurrent.futures import Future
from functools import wraps # import from functools to prevent circular import exception
__all__ = ['measure', 'time_as_int', 'time_ms', 'sleep', 'time_us', 'ExponentialBackoff',
'parse_time_string']
import time
from satella.coding.concurrent.thread import Condition
from satella.exceptions import WouldWaitMore
TimeSignal = tp.Callable[[], float]
def sleep(y: tp.Union[str, float], abort_on_interrupt: bool = False) -> bool:
"""
Sleep for given interval.
This won't be interrupted by KeyboardInterrupted, and always will sleep for given time interval.
This will return at once if x is negative
:param y: the interval to wait in seconds, can be also a time string
:param abort_on_interrupt: whether to abort at once when KeyboardInterrupt is seen
:returns: whether the function has completed its sleep naturally. False is seen on
aborts thanks to KeyboardInterrupt only if abort_on_interrupt is True
"""
y = parse_time_string(y)
if y < 0:
return
with measure() as measurement:
while measurement() < y:
try:
time.sleep(y - measurement())
except KeyboardInterrupt:
if abort_on_interrupt:
return False
return True
def time_as_int() -> int:
"""
Syntactic sugar for
>>> from time import time
>>> int(time())
"""
return int(time.time())
def time_ms() -> int:
"""
Syntactic sugar for
>>> from time import time
>>> int(time()*1000)
This will try to use time.time_ns() if available
"""
try:
return time.time_ns() // 1000000
except AttributeError:
return int(time.time() * 1000)
def time_us() -> int:
"""
Syntactic sugar for
>>> from time import time
>>> int(time()*1000000)
This will try to use time.time_ns() if available
"""
try:
return time.time_ns() // 1000
except AttributeError:
return int(time.time() * 1000000)
class measure:
"""
A class used to measure time elapsed. Use for example like this:
......@@ -333,152 +259,3 @@ class measure:
if self.stop_on_stop:
self.stop()
return False
class ExponentialBackoff:
"""
A class that will sleep increasingly longer on errors. Meant to be used in such a way:
>>> eb = ExponentialBackoff(start=2, limit=30)
>>> while not connect():
>>> eb.failed()
>>> eb.sleep()
>>> eb.success()
Also a structure that will mark an object (eg. the Internet access) as inaccessible for some
duration. Usage is that case is like this:
>>> eb = ExponentialBackoff(start=2, limit=30)
>>> eb.failed()
>>> self.assertFalse(eb.available)
>>> time.sleep(2)
>>> self.assertTrue(eb.available)
Note that this structure is thread safe only when a single object is doing
the :code:`success` or :code:`failed` calls, and other utilize
:meth:`~satella.time.ExponentialBackoff.wait_until_available`.
:param start: value at which to start
:param limit: maximum sleep timeout
:param sleep_fun: function used to sleep. Will accept a single argument - number of
seconds to wait
"""
__slots__ = 'start', 'limit', 'counter', 'sleep_fun', 'unavailable_until', 'condition'
def __init__(self, start: float = 1, limit: float = 30,
sleep_fun: tp.Callable[[float], None] = sleep):
self.start = start
self.limit = limit
self.counter = start
self.sleep_fun = sleep_fun
self.condition = Condition()
self.unavailable_until = None
def sleep(self):
"""
Called when sleep is expected.
"""
self.sleep_fun(self.counter)
def failed(self):
"""
Called when something fails.
"""
if self.counter == 0:
self.counter = self.start
else:
self.counter = min(self.limit, self.counter * 2)
self.unavailable_until = time.monotonic() + self.counter
def wait_until_available(self, timeout: tp.Optional[float] = None) -> None:
"""
Waits until the service is available
:param timeout: maximum amount of seconds to wait. If waited more than that,
WouldWaitMore will be raised
:param sleep_function: a function which will be called with a single argument,
the number of seconds to sleep. This should sleep by that many seconds.
:raises WouldWaitMore: waited for timeout and service still was not healthy
"""
with measure(timeout=timeout) as m:
while not m.timeouted:
tn = self.time_until_next_check()
if tn is None:
return
self.condition.wait(timeout=tn, dont_raise=True)
raise WouldWaitMore('timeouted while waiting for service to become healthy')
def time_until_next_check(self) -> tp.Optional[float]:
"""Return the time until next health check, or None if the service is healthy"""
if self.unavailable_until is None:
return None
else:
t = time.monotonic()
if t > self.unavailable_until:
self.unavailable_until = None
return None
else:
return self.unavailable_until - t
@property
def ready_for_next_check(self) -> bool:
"""
:return: Has :meth:`~satella.time.ExponentialBackoff.failure` been called only
in the last waiting period?
"""
if self.unavailable_until is None:
return True
elif time.monotonic() > self.unavailable_until:
self.unavailable_until = None
return True
else:
return False
@property
def available(self) -> bool:
"""Was the status of the last call :code:`success`?"""
return self.counter == 0
def success(self):
"""
Called when something successes.
"""
self.counter = 0
self.unavailable_until = None
self.condition.notify_all()
TIME_MODIFIERS = [
('s', 1),
('m', 60),
('h', 60 * 60),
('d', 24 * 60 * 60),
('w', 7 * 24 * 60 * 60)
]
def parse_time_string(s: tp.Union[int, float, str]) -> float:
"""
Parse a time string into seconds, so eg. '30m' will be equal to 1800, and so will
be '30 min'.
This will correctly parse:
- seconds
- minutes
- hours
- days
- weeks
.. warning:: This does not handle fractions of a second!
:param s: time string or time value in seconds
:return: value in seconds
"""
if isinstance(s, (int, float)):
return s
for modifier, multiple in TIME_MODIFIERS:
if modifier in s:
return float(s[:s.index(modifier)]) * multiple
return float(s)
import copy
import inspect
import time
import typing as tp
import warnings
from concurrent.futures import Future
from functools import wraps # import from functools to prevent circular import exception
__all__ = ['measure', 'time_as_int', 'time_ms', 'sleep', 'time_us', 'ExponentialBackoff',
'parse_time_string']
from satella.coding.concurrent.thread import Condition
from satella.exceptions import WouldWaitMore
def sleep(y: tp.Union[str, float], abort_on_interrupt: bool = False) -> bool:
"""
Sleep for given interval.
This won't be interrupted by KeyboardInterrupted, and always will sleep for given time interval.
This will return at once if x is negative
:param y: the interval to wait in seconds, can be also a time string
:param abort_on_interrupt: whether to abort at once when KeyboardInterrupt is seen
:returns: whether the function has completed its sleep naturally. False is seen on
aborts thanks to KeyboardInterrupt only if abort_on_interrupt is True
"""
y = parse_time_string(y)
if y < 0:
return
with measure() as measurement:
while measurement() < y:
try:
time.sleep(y - measurement())
except KeyboardInterrupt:
if abort_on_interrupt:
return False
return True
def time_as_int() -> int:
"""
Syntactic sugar for
>>> from time import time
>>> int(time())
"""
return int(time.time())
def time_ms() -> int:
"""
Syntactic sugar for
>>> from time import time
>>> int(time()*1000)
This will try to use time.time_ns() if available
"""
try:
return time.time_ns() // 1000000
except AttributeError:
return int(time.time() * 1000)
def time_us() -> int:
"""
Syntactic sugar for
>>> from time import time
>>> int(time()*1000000)
This will try to use time.time_ns() if available
"""
try:
return time.time_ns() // 1000
except AttributeError:
return int(time.time() * 1000000)
class ExponentialBackoff:
"""
A class that will sleep increasingly longer on errors. Meant to be used in such a way:
>>> eb = ExponentialBackoff(start=2, limit=30)
>>> while not connect():
>>> eb.failed()
>>> eb.sleep()
>>> eb.success()
Also a structure that will mark an object (eg. the Internet access) as inaccessible for some
duration. Usage is that case is like this:
>>> eb = ExponentialBackoff(start=2, limit=30)
>>> eb.failed()
>>> self.assertFalse(eb.available)
>>> time.sleep(2)
>>> self.assertTrue(eb.available)
Note that this structure is thread safe only when a single object is doing
the :code:`success` or :code:`failed` calls, and other utilize
:meth:`~satella.time.ExponentialBackoff.wait_until_available`.
:param start: value at which to start
:param limit: maximum sleep timeout
:param sleep_fun: function used to sleep. Will accept a single argument - number of
seconds to wait
"""
__slots__ = 'start', 'limit', 'counter', 'sleep_fun', 'unavailable_until', 'condition'
def __init__(self, start: float = 1, limit: float = 30,
sleep_fun: tp.Callable[[float], None] = sleep):
self.start = start
self.limit = limit
self.counter = start
self.sleep_fun = sleep_fun
self.condition = Condition()
self.unavailable_until = None
def sleep(self):
"""
Called when sleep is expected.
"""
self.sleep_fun(self.counter)
def failed(self):
"""
Called when something fails.
"""
if self.counter == 0:
self.counter = self.start
else:
self.counter = min(self.limit, self.counter * 2)
self.unavailable_until = time.monotonic() + self.counter
def wait_until_available(self, timeout: tp.Optional[float] = None) -> None:
"""
Waits until the service is available
:param timeout: maximum amount of seconds to wait. If waited more than that,
WouldWaitMore will be raised
:param sleep_function: a function which will be called with a single argument,
the number of seconds to sleep. This should sleep by that many seconds.
:raises WouldWaitMore: waited for timeout and service still was not healthy
"""
with measure(timeout=timeout) as m:
while not m.timeouted:
tn = self.time_until_next_check()
if tn is None:
return
self.condition.wait(timeout=tn, dont_raise=True)
raise WouldWaitMore('timeouted while waiting for service to become healthy')
def time_until_next_check(self) -> tp.Optional[float]:
"""Return the time until next health check, or None if the service is healthy"""
if self.unavailable_until is None:
return None
else:
t = time.monotonic()
if t > self.unavailable_until:
self.unavailable_until = None
return None
else:
return self.unavailable_until - t
@property
def ready_for_next_check(self) -> bool:
"""
:return: Has :meth:`~satella.time.ExponentialBackoff.failure` been called only
in the last waiting period?
"""
if self.unavailable_until is None:
return True
elif time.monotonic() > self.unavailable_until:
self.unavailable_until = None
return True
else:
return False
@property
def available(self) -> bool:
"""Was the status of the last call :code:`success`?"""
return self.counter == 0
def success(self):
"""
Called when something successes.
"""
self.counter = 0
self.unavailable_until = None
self.condition.notify_all()
import typing as tp
TIME_MODIFIERS = [
('s', 1),
('m', 60),
('h', 60 * 60),
('d', 24 * 60 * 60),
('w', 7 * 24 * 60 * 60)
]
def parse_time_string(s: tp.Union[int, float, str]) -> float:
"""
Parse a time string into seconds, so eg. '30m' will be equal to 1800, and so will
be '30 min'.
This will correctly parse:
- seconds
- minutes
- hours
- days
- weeks
.. warning:: This does not handle fractions of a second!
:param s: time string or time value in seconds
:return: value in seconds
"""
if isinstance(s, (int, float)):
return s
for modifier, multiple in TIME_MODIFIERS:
if modifier in s:
return float(s[:s.index(modifier)]) * multiple
return float(s)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment